Add executor with logging placeholder and tests
The Executor receives strategy signals and calculates dollar amounts from capital allocations. Currently logs intended trades rather than placing real orders. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
81
src/executor.test.ts
Normal file
81
src/executor.test.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { Executor } from './executor';
|
||||||
|
import type { Alpaca } from './alpaca';
|
||||||
|
import type { Strategy, Signal } from './strategy';
|
||||||
|
|
||||||
|
function mockAlpaca(): Alpaca {
|
||||||
|
return {
|
||||||
|
getAccount: vi.fn(),
|
||||||
|
getAssets: vi.fn(),
|
||||||
|
getAsset: vi.fn(),
|
||||||
|
getClock: vi.fn(),
|
||||||
|
getLatestQuote: vi.fn(),
|
||||||
|
getLatestTrades: vi.fn(),
|
||||||
|
} as unknown as Alpaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockStrategy(overrides: Partial<Strategy> = {}): Strategy {
|
||||||
|
return {
|
||||||
|
name: 'test-strategy',
|
||||||
|
capitalAllocation: 0.5,
|
||||||
|
execute: vi.fn().mockResolvedValue([]),
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Executor', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs a buy signal with correct dollar amount', async () => {
|
||||||
|
const executor = new Executor(mockAlpaca());
|
||||||
|
const strategy = mockStrategy({ name: 'momentum', capitalAllocation: 0.5 });
|
||||||
|
const signals: Signal[] = [{ symbol: 'TQQQ', direction: 'buy', allocation: 0.8 }];
|
||||||
|
|
||||||
|
await executor.executeSignals(strategy, signals, 10000);
|
||||||
|
|
||||||
|
// 10000 * 0.5 * 0.8 = 4000
|
||||||
|
expect(console.log).toHaveBeenCalledWith('[momentum] BUY TQQQ — $4000.00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs a sell signal with correct dollar amount', async () => {
|
||||||
|
const executor = new Executor(mockAlpaca());
|
||||||
|
const strategy = mockStrategy({ name: 'rebalance', capitalAllocation: 0.3 });
|
||||||
|
const signals: Signal[] = [{ symbol: 'SPY', direction: 'sell', allocation: 1.0 }];
|
||||||
|
|
||||||
|
await executor.executeSignals(strategy, signals, 20000);
|
||||||
|
|
||||||
|
// 20000 * 0.3 * 1.0 = 6000
|
||||||
|
expect(console.log).toHaveBeenCalledWith('[rebalance] SELL SPY — $6000.00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores signals with 0 allocation', async () => {
|
||||||
|
const executor = new Executor(mockAlpaca());
|
||||||
|
const strategy = mockStrategy();
|
||||||
|
const signals: Signal[] = [{ symbol: 'TQQQ', direction: 'buy', allocation: 0 }];
|
||||||
|
|
||||||
|
await executor.executeSignals(strategy, signals, 10000);
|
||||||
|
|
||||||
|
expect(console.log).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles multiple signals in one batch', async () => {
|
||||||
|
const executor = new Executor(mockAlpaca());
|
||||||
|
const strategy = mockStrategy({ name: 'multi', capitalAllocation: 0.4 });
|
||||||
|
const signals: Signal[] = [
|
||||||
|
{ symbol: 'TQQQ', direction: 'buy', allocation: 0.5 },
|
||||||
|
{ symbol: 'SPY', direction: 'sell', allocation: 0.5 },
|
||||||
|
];
|
||||||
|
|
||||||
|
await executor.executeSignals(strategy, signals, 10000);
|
||||||
|
|
||||||
|
// 10000 * 0.4 * 0.5 = 2000 each
|
||||||
|
expect(console.log).toHaveBeenCalledWith('[multi] BUY TQQQ — $2000.00');
|
||||||
|
expect(console.log).toHaveBeenCalledWith('[multi] SELL SPY — $2000.00');
|
||||||
|
});
|
||||||
|
});
|
||||||
23
src/executor.ts
Normal file
23
src/executor.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Alpaca } from "./alpaca";
|
||||||
|
import { Strategy, Signal } from "./strategy";
|
||||||
|
|
||||||
|
export class Executor {
|
||||||
|
private alpaca: Alpaca;
|
||||||
|
|
||||||
|
constructor(alpaca: Alpaca) {
|
||||||
|
this.alpaca = alpaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeSignals(strategy: Strategy, signals: Signal[], totalCapital: number): Promise<void> {
|
||||||
|
const strategyCapital = totalCapital * strategy.capitalAllocation;
|
||||||
|
|
||||||
|
for (const signal of signals) {
|
||||||
|
if (signal.allocation === 0) continue;
|
||||||
|
|
||||||
|
const dollarAmount = strategyCapital * signal.allocation;
|
||||||
|
console.log(
|
||||||
|
`[${strategy.name}] ${signal.direction.toUpperCase()} ${signal.symbol} — $${dollarAmount.toFixed(2)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user