Add backtesting entry point for offline strategy testing

Introduces a BacktestClient that replays historical 1-min bars against the
existing strategy code, plus a CLI entry point (npm run backtest) that fetches
bars from Alpaca and runs the bot over a date range. Makes wait() pluggable
so the backtest resolves delays instantly while advancing simulated time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jon
2026-02-10 00:59:22 -07:00
parent 1d3688e9ae
commit 4f1e745534
6 changed files with 593 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { printAsset, accountBalance, waitForNextOpen } from './trading';
import { printAsset, accountBalance, waitForNextOpen, wait, setWaitFn, resetWaitFn } from './trading';
import type { Alpaca } from './alpaca';
function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
@@ -25,6 +25,7 @@ beforeEach(() => {
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
resetWaitFn();
});
describe('printAsset', () => {
@@ -82,3 +83,26 @@ describe('waitForNextOpen', () => {
});
});
describe('setWaitFn / resetWaitFn', () => {
it('uses custom wait function when set', async () => {
const calls: number[] = [];
setWaitFn(async (ms) => { calls.push(ms); });
await wait(5000);
await wait(3000);
expect(calls).toEqual([5000, 3000]);
});
it('restores default wait after resetWaitFn', async () => {
setWaitFn(async () => {});
resetWaitFn();
// After reset, wait should use real setTimeout again
const promise = wait(1000);
await vi.advanceTimersByTimeAsync(1000);
await promise;
// If we got here without hanging, default wait is restored
});
});