Remove executor, add buy/sell to Alpaca, move capital allocation to Bot

Replace the Executor logging placeholder with real buy/sell methods on
the Alpaca class that place market orders via createOrder and return the
fill price. Strategies now receive their capital amount directly and
place orders themselves. Bot accepts StrategyAllocation[] to decouple
capital allocation from strategy definition.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jon
2026-02-04 11:05:21 -07:00
parent e32e30af47
commit 075572a01c
12 changed files with 186 additions and 255 deletions

View File

@@ -9,6 +9,7 @@ function mockClient(overrides: Partial<AlpacaClient> = {}): AlpacaClient {
getClock: vi.fn().mockResolvedValue({ is_open: false, next_open: '2025-01-01T14:30:00Z', next_close: '2025-01-01T21:00:00Z' }),
getLatestQuote: vi.fn().mockResolvedValue({ AskPrice: 50.00, BidPrice: 49.90 }),
getLatestTrades: vi.fn().mockResolvedValue(new Map()),
createOrder: vi.fn().mockResolvedValue({ id: 'order-1', symbol: 'TQQQ', filled_avg_price: '50.25', filled_qty: '10', side: 'buy', status: 'filled' }),
...overrides,
};
}
@@ -78,4 +79,34 @@ describe('Alpaca', () => {
await alpaca.getLatestTrades(['TQQQ', 'SPY']);
expect(client.getLatestTrades).toHaveBeenCalledWith(['TQQQ', 'SPY']);
});
it('buy places a market buy order and returns fill price', async () => {
const client = mockClient();
const alpaca = new Alpaca(false, client);
const price = await alpaca.buy('TQQQ', 5000);
expect(price).toBe(50.25);
expect(client.createOrder).toHaveBeenCalledWith({
symbol: 'TQQQ',
notional: 5000,
side: 'buy',
type: 'market',
time_in_force: 'day',
});
});
it('sell places a market sell order and returns fill price', async () => {
const client = mockClient({
createOrder: vi.fn().mockResolvedValue({ id: 'order-2', symbol: 'TQQQ', filled_avg_price: '51.00', filled_qty: '10', side: 'sell', status: 'filled' }),
});
const alpaca = new Alpaca(false, client);
const price = await alpaca.sell('TQQQ', 5000);
expect(price).toBe(51.00);
expect(client.createOrder).toHaveBeenCalledWith({
symbol: 'TQQQ',
notional: 5000,
side: 'sell',
type: 'market',
time_in_force: 'day',
});
});
});

View File

@@ -36,6 +36,15 @@ export interface AlpacaQuote {
BidPrice: number;
}
export interface AlpacaOrder {
id: string;
symbol: string;
filled_avg_price: string;
filled_qty: string;
side: string;
status: string;
}
export interface AlpacaTrade {
p: number; // price
s: number; // size
@@ -49,6 +58,13 @@ export interface AlpacaClient {
getClock(): Promise<AlpacaClock>;
getLatestQuote(symbol: string): Promise<AlpacaQuote>;
getLatestTrades(symbols: string[]): Promise<Map<string, AlpacaTrade>>;
createOrder(order: {
symbol: string;
notional: number;
side: 'buy' | 'sell';
type: string;
time_in_force: string;
}): Promise<AlpacaOrder>;
}
export class Alpaca {
@@ -99,4 +115,25 @@ export class Alpaca {
return this.alpaca.getLatestTrades(symbols);
}
public async buy(symbol: string, dollarAmount: number): Promise<number> {
const order = await this.alpaca.createOrder({
symbol,
notional: dollarAmount,
side: 'buy',
type: 'market',
time_in_force: 'day',
});
return parseFloat(order.filled_avg_price);
}
public async sell(symbol: string, dollarAmount: number): Promise<number> {
const order = await this.alpaca.createOrder({
symbol,
notional: dollarAmount,
side: 'sell',
type: 'market',
time_in_force: 'day',
});
return parseFloat(order.filled_avg_price);
}
}

View File

@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { Bot } from './bot';
import { Bot, StrategyAllocation } from './bot';
import type { Alpaca } from './alpaca';
import type { Strategy, Signal } from './strategy';
import type { Strategy } from './strategy';
function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
return {
@@ -17,6 +17,8 @@ function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
getLatestBid: vi.fn(),
getLatestSpread: vi.fn(),
getLatestTrades: vi.fn(),
buy: vi.fn(),
sell: vi.fn(),
...overrides,
} as unknown as Alpaca;
}
@@ -24,8 +26,7 @@ function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
function mockStrategy(overrides: Partial<Strategy> = {}): Strategy {
return {
name: 'test-strategy',
capitalAllocation: 0.5,
execute: vi.fn().mockResolvedValue([]),
execute: vi.fn().mockResolvedValue(undefined),
...overrides,
};
}
@@ -44,83 +45,53 @@ describe('Bot', () => {
describe('constructor', () => {
it('throws when capital allocations exceed 1.0', () => {
const alpaca = mockAlpaca();
const strategies = [
mockStrategy({ capitalAllocation: 0.6 }),
mockStrategy({ capitalAllocation: 0.5 }),
const allocations: StrategyAllocation[] = [
{ strategy: mockStrategy(), capitalAllocation: 0.6 },
{ strategy: mockStrategy(), capitalAllocation: 0.5 },
];
expect(() => new Bot(alpaca, strategies)).toThrow(
expect(() => new Bot(alpaca, allocations)).toThrow(
'Capital allocations sum to 1.1, which exceeds 1.0'
);
});
it('accepts allocations that sum to exactly 1.0', () => {
const alpaca = mockAlpaca();
const strategies = [
mockStrategy({ capitalAllocation: 0.5 }),
mockStrategy({ capitalAllocation: 0.5 }),
const allocations: StrategyAllocation[] = [
{ strategy: mockStrategy(), capitalAllocation: 0.5 },
{ strategy: mockStrategy(), capitalAllocation: 0.5 },
];
expect(() => new Bot(alpaca, strategies)).not.toThrow();
expect(() => new Bot(alpaca, allocations)).not.toThrow();
});
it('accepts allocations that sum to less than 1.0', () => {
const alpaca = mockAlpaca();
const strategies = [
mockStrategy({ capitalAllocation: 0.3 }),
mockStrategy({ capitalAllocation: 0.2 }),
const allocations: StrategyAllocation[] = [
{ strategy: mockStrategy(), capitalAllocation: 0.3 },
{ strategy: mockStrategy(), capitalAllocation: 0.2 },
];
expect(() => new Bot(alpaca, strategies)).not.toThrow();
expect(() => new Bot(alpaca, allocations)).not.toThrow();
});
});
describe('runDay', () => {
it('runs multiple strategies concurrently', async () => {
it('passes correct capital amount to each strategy', async () => {
const alpaca = mockAlpaca();
const strategyA = mockStrategy({
name: 'A',
capitalAllocation: 0.3,
execute: vi.fn().mockResolvedValue([
{ symbol: 'TQQQ', direction: 'buy', allocation: 1.0 },
]),
});
const strategyB = mockStrategy({
name: 'B',
capitalAllocation: 0.2,
execute: vi.fn().mockResolvedValue([
{ symbol: 'SPY', direction: 'sell', allocation: 0.5 },
]),
});
const bot = new Bot(alpaca, [strategyA, strategyB]);
const promise = bot.runDay();
await vi.advanceTimersByTimeAsync(0);
await promise;
expect(strategyA.execute).toHaveBeenCalledWith(alpaca);
expect(strategyB.execute).toHaveBeenCalledWith(alpaca);
// A: 10000 * 0.3 * 1.0 = 3000
expect(console.log).toHaveBeenCalledWith('[A] BUY TQQQ — $3000.00');
// B: 10000 * 0.2 * 0.5 = 1000
expect(console.log).toHaveBeenCalledWith('[B] SELL SPY — $1000.00');
});
it('passes signals to executor', async () => {
const alpaca = mockAlpaca();
const signals: Signal[] = [
{ symbol: 'TQQQ', direction: 'buy', allocation: 0.5 },
const strategyA = mockStrategy({ name: 'A' });
const strategyB = mockStrategy({ name: 'B' });
const allocations: StrategyAllocation[] = [
{ strategy: strategyA, capitalAllocation: 0.3 },
{ strategy: strategyB, capitalAllocation: 0.2 },
];
const strategy = mockStrategy({
name: 'test',
capitalAllocation: 0.4,
execute: vi.fn().mockResolvedValue(signals),
});
const bot = new Bot(alpaca, [strategy]);
const bot = new Bot(alpaca, allocations);
const promise = bot.runDay();
await vi.advanceTimersByTimeAsync(0);
await promise;
// 10000 * 0.4 * 0.5 = 2000
expect(console.log).toHaveBeenCalledWith('[test] BUY TQQQ — $2000.00');
// 10000 * 0.3 = 3000
expect(strategyA.execute).toHaveBeenCalledWith(alpaca, 3000);
// 10000 * 0.2 = 2000
expect(strategyB.execute).toHaveBeenCalledWith(alpaca, 2000);
});
it('works with zero strategies', async () => {
@@ -131,9 +102,26 @@ describe('Bot', () => {
await vi.advanceTimersByTimeAsync(0);
await promise;
// Only the "waiting for open" log, no strategy logs
expect(console.log).toHaveBeenCalledTimes(1);
expect(console.log).toHaveBeenCalledWith('waiting for open');
});
it('skips waiting when market is already open', async () => {
const alpaca = mockAlpaca({
getClock: vi.fn().mockResolvedValue({
is_open: true,
next_open: new Date().toISOString(),
next_close: new Date().toISOString(),
}),
});
const strategy = mockStrategy();
const bot = new Bot(alpaca, [{ strategy, capitalAllocation: 1.0 }]);
const promise = bot.runDay();
await vi.advanceTimersByTimeAsync(0);
await promise;
expect(console.log).not.toHaveBeenCalledWith('waiting for open');
expect(strategy.execute).toHaveBeenCalledWith(alpaca, 10000);
});
});
});

View File

@@ -1,23 +1,25 @@
import { Alpaca } from "./alpaca";
import { Strategy } from "./strategy";
import { Executor } from "./executor";
import { isMarketOpen, waitForNextOpen } from "./trading";
export interface StrategyAllocation {
strategy: Strategy;
capitalAllocation: number;
}
export class Bot {
private alpaca: Alpaca;
private strategies: Strategy[];
private executor: Executor;
private allocations: StrategyAllocation[];
constructor(alpaca: Alpaca, strategies: Strategy[]) {
const totalAllocation = strategies.reduce((sum, s) => sum + s.capitalAllocation, 0);
constructor(alpaca: Alpaca, allocations: StrategyAllocation[]) {
const totalAllocation = allocations.reduce((sum, a) => sum + a.capitalAllocation, 0);
if (totalAllocation > 1.0) {
throw new Error(
`Capital allocations sum to ${totalAllocation}, which exceeds 1.0`
);
}
this.alpaca = alpaca;
this.strategies = strategies;
this.executor = new Executor(alpaca);
this.allocations = allocations;
}
async runDay(): Promise<void> {
@@ -30,10 +32,9 @@ export class Bot {
const totalCapital = parseFloat(account.cash);
await Promise.all(
this.strategies.map(async (strategy) => {
const signals = await strategy.execute(this.alpaca);
await this.executor.executeSignals(strategy, signals, totalCapital);
this.allocations.map(async ({ strategy, capitalAllocation }) => {
const capitalAmount = totalCapital * capitalAllocation;
await strategy.execute(this.alpaca, capitalAmount);
})
);
}

View File

@@ -1,83 +0,0 @@
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(),
getLatestAsk: vi.fn(),
getLatestBid: vi.fn(),
getLatestSpread: 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');
});
});

View File

@@ -1,23 +0,0 @@
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)}`
);
}
}
}

View File

@@ -4,8 +4,8 @@ import { wait } from "./trading";
import { MomentumStrategy } from "./momentum-strategy";
const alpaca = new Alpaca(false);
const momentum = new MomentumStrategy(1.0);
const bot = new Bot(alpaca, [momentum]);
const momentum = new MomentumStrategy();
const bot = new Bot(alpaca, [{ strategy: momentum, capitalAllocation: 1.0 }]);
async function main() {
while(true) {

View File

@@ -12,6 +12,8 @@ function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
getLatestBid: vi.fn(),
getLatestSpread: vi.fn(),
getLatestTrades: vi.fn(),
buy: vi.fn(),
sell: vi.fn(),
...overrides,
} as unknown as Alpaca;
}

View File

@@ -12,6 +12,8 @@ function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
getLatestBid: vi.fn(),
getLatestSpread: vi.fn(),
getLatestTrades: vi.fn(),
buy: vi.fn().mockResolvedValue(50),
sell: vi.fn().mockResolvedValue(50),
...overrides,
} as unknown as Alpaca;
}
@@ -37,110 +39,99 @@ describe('MomentumStrategy', () => {
it('buys TQQQ when QQQ goes up', async () => {
const alpaca = mockAlpaca({
getLatestAsk: vi.fn()
// indicator: QQQ before
.mockResolvedValueOnce(100)
// indicator: QQQ after (up)
.mockResolvedValueOnce(101)
// entry quote for TQQQ
.mockResolvedValueOnce(50),
getLatestBid: vi.fn()
// poll: hit target immediately
.mockResolvedValueOnce(50.50),
.mockResolvedValueOnce(101),
buy: vi.fn().mockResolvedValue(50),
getLatestBid: vi.fn().mockResolvedValue(50.50),
});
const strategy = new MomentumStrategy(1.0, fastConfig);
const promise = strategy.execute(alpaca);
const strategy = new MomentumStrategy(fastConfig);
const promise = strategy.execute(alpaca, 5000);
await vi.advanceTimersByTimeAsync(1000);
const signals = await promise;
await promise;
expect(signals[0]).toEqual({ symbol: 'TQQQ', direction: 'buy', allocation: 1.0 });
expect(alpaca.buy).toHaveBeenCalledWith('TQQQ', 5000);
});
it('buys SQQQ when QQQ goes down', async () => {
const alpaca = mockAlpaca({
getLatestAsk: vi.fn()
// indicator: QQQ before
.mockResolvedValueOnce(100)
// indicator: QQQ after (down)
.mockResolvedValueOnce(99)
// entry quote for SQQQ
.mockResolvedValueOnce(30),
getLatestBid: vi.fn()
// poll: hit target immediately
.mockResolvedValueOnce(30.30),
.mockResolvedValueOnce(99),
buy: vi.fn().mockResolvedValue(30),
getLatestBid: vi.fn().mockResolvedValue(30.30),
});
const strategy = new MomentumStrategy(1.0, fastConfig);
const promise = strategy.execute(alpaca);
const strategy = new MomentumStrategy(fastConfig);
const promise = strategy.execute(alpaca, 5000);
await vi.advanceTimersByTimeAsync(1000);
const signals = await promise;
await promise;
expect(signals[0]).toEqual({ symbol: 'SQQQ', direction: 'buy', allocation: 1.0 });
expect(alpaca.buy).toHaveBeenCalledWith('SQQQ', 5000);
});
it('sells when bid hits 1% target', async () => {
const alpaca = mockAlpaca({
getLatestAsk: vi.fn()
// indicator: QQQ samples
.mockResolvedValueOnce(100)
.mockResolvedValueOnce(101)
// entry quote: ask = 50
.mockResolvedValueOnce(50),
.mockResolvedValueOnce(101),
buy: vi.fn().mockResolvedValue(50),
getLatestBid: vi.fn()
// poll 1: not yet (target = 50.50)
.mockResolvedValueOnce(50.10)
// poll 2: hit target
.mockResolvedValueOnce(50.50),
sell: vi.fn().mockResolvedValue(50.50),
});
const strategy = new MomentumStrategy(1.0, fastConfig);
const promise = strategy.execute(alpaca);
const strategy = new MomentumStrategy(fastConfig);
const promise = strategy.execute(alpaca, 5000);
await vi.advanceTimersByTimeAsync(1000);
const signals = await promise;
await promise;
expect(signals[1]).toEqual({ symbol: 'TQQQ', direction: 'sell', allocation: 1.0 });
expect(console.log).toHaveBeenCalledWith('[momentum] exit TQQQ — reason: target');
expect(alpaca.sell).toHaveBeenCalledWith('TQQQ', 5000);
});
it('sells on timeout when target not reached', async () => {
const alpaca = mockAlpaca({
getLatestAsk: vi.fn()
// indicator: QQQ samples
.mockResolvedValueOnce(100)
.mockResolvedValueOnce(101)
// entry quote: ask = 50
.mockResolvedValueOnce(50),
getLatestBid: vi.fn()
// all polls: never reach target
.mockResolvedValue(49.90),
.mockResolvedValueOnce(101),
buy: vi.fn().mockResolvedValue(50),
getLatestBid: vi.fn().mockResolvedValue(49.90),
sell: vi.fn().mockResolvedValue(49.90),
});
const strategy = new MomentumStrategy(1.0, fastConfig);
const promise = strategy.execute(alpaca);
const strategy = new MomentumStrategy(fastConfig);
const promise = strategy.execute(alpaca, 5000);
await vi.advanceTimersByTimeAsync(2000);
const signals = await promise;
await promise;
expect(signals[1]).toEqual({ symbol: 'TQQQ', direction: 'sell', allocation: 1.0 });
expect(console.log).toHaveBeenCalledWith('[momentum] exit TQQQ — reason: timeout');
expect(alpaca.sell).toHaveBeenCalledWith('TQQQ', 5000);
});
it('returns both buy and sell signals', async () => {
it('uses actual fill price for target calculation', async () => {
const alpaca = mockAlpaca({
getLatestAsk: vi.fn()
.mockResolvedValueOnce(100)
.mockResolvedValueOnce(101)
.mockResolvedValueOnce(50),
.mockResolvedValueOnce(101),
// fill price is 50, so 1% target = 50.50
buy: vi.fn().mockResolvedValue(50),
getLatestBid: vi.fn()
// 50.49 is below target
.mockResolvedValueOnce(50.49)
// 50.50 hits target
.mockResolvedValueOnce(50.50),
sell: vi.fn().mockResolvedValue(50.50),
});
const strategy = new MomentumStrategy(1.0, fastConfig);
const promise = strategy.execute(alpaca);
const strategy = new MomentumStrategy(fastConfig);
const promise = strategy.execute(alpaca, 5000);
await vi.advanceTimersByTimeAsync(1000);
const signals = await promise;
await promise;
expect(signals).toHaveLength(2);
expect(signals[0].direction).toBe('buy');
expect(signals[1].direction).toBe('sell');
expect(console.log).toHaveBeenCalledWith('[momentum] exit TQQQ — reason: target');
});
});

View File

@@ -1,5 +1,5 @@
import { Alpaca } from "./alpaca";
import { Strategy, Signal } from "./strategy";
import { Strategy } from "./strategy";
import { MomentumIndicator, MomentumIndicatorConfig } from "./momentum-indicator";
import { wait } from "./trading";
@@ -18,23 +18,19 @@ const defaultConfig: MomentumStrategyConfig = {
export class MomentumStrategy implements Strategy {
name = 'momentum';
capitalAllocation: number;
private config: MomentumStrategyConfig;
private indicator: MomentumIndicator;
constructor(capitalAllocation: number, config: Partial<MomentumStrategyConfig> = {}) {
this.capitalAllocation = capitalAllocation;
constructor(config: Partial<MomentumStrategyConfig> = {}) {
this.config = { ...defaultConfig, ...config };
this.indicator = new MomentumIndicator(this.config.indicatorConfig);
}
async execute(alpaca: Alpaca): Promise<Signal[]> {
async execute(alpaca: Alpaca, capitalAmount: number): Promise<void> {
const result = await this.indicator.evaluate(alpaca);
const symbol = result.direction === 'up' ? 'TQQQ' : 'SQQQ';
const entryPrice = await alpaca.getLatestAsk(symbol);
const buy: Signal = { symbol, direction: 'buy', allocation: 1.0 };
const entryPrice = await alpaca.buy(symbol, capitalAmount);
const targetPrice = entryPrice * (1 + this.config.targetGain);
const deadline = Date.now() + this.config.holdTime;
@@ -53,8 +49,6 @@ export class MomentumStrategy implements Strategy {
console.log(`[${this.name}] exit ${symbol} — reason: ${reason}`);
const sell: Signal = { symbol, direction: 'sell', allocation: 1.0 };
return [buy, sell];
await alpaca.sell(symbol, capitalAmount);
}
}

View File

@@ -1,15 +1,6 @@
import { Alpaca } from "./alpaca";
export type SignalDirection = 'buy' | 'sell';
export interface Signal {
symbol: string;
direction: SignalDirection;
allocation: number; // fraction of this strategy's capital to use (0-1)
}
export interface Strategy {
name: string;
capitalAllocation: number; // fraction of total account capital (0-1)
execute(alpaca: Alpaca): Promise<Signal[]>;
execute(alpaca: Alpaca, capitalAmount: number): Promise<void>;
}

View File

@@ -12,6 +12,8 @@ function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
getLatestBid: vi.fn(),
getLatestSpread: vi.fn(),
getLatestTrades: vi.fn(),
buy: vi.fn(),
sell: vi.fn(),
...overrides,
} as unknown as Alpaca;
}