import test from 'node:test'; import assert from 'node:assert/strict'; import { runSignalBacktest, runTechnicalSignalBacktest, BacktestInput } from '../lib/backtest'; import { WeeklyQuote } from '../lib/signal-engine'; function weeklyQuote(week: number, close: number, overrides: Partial = {}): WeeklyQuote { return { date: new Date(Date.UTC(2020, 0, 3 + week * 7)), open: close - 1, high: close + 1, low: close - 1, close, ...overrides, }; } function weekIso(week: number): string { const date = weeklyQuote(week, 0).date; assert.ok(date instanceof Date); return date.toISOString(); } function inputWithFundamentals(asOf: string): BacktestInput { const bars = Array.from({ length: 210 }, (_, index) => weeklyQuote(index, 100)); bars[204] = weeklyQuote(204, 101, { open: 98, low: 99, high: 102 }); bars[205] = weeklyQuote(205, 103); bars[209] = weeklyQuote(209, 110); return { symbols: [ { symbol: 'TEST', quotes: bars, fundamentals: [ { asOf: new Date('2023-01-01T00:00:00.000Z'), roe: 0.05, debtToEquity: 10, marketCap: 25_000_000_000 }, { asOf: new Date(asOf), roe: 0.25, debtToEquity: 10, marketCap: 25_000_000_000 }, ], }, ], horizonsWeeks: [4], }; } test('runSignalBacktest does not use fundamentals published after the signal date', () => { const result = runSignalBacktest(inputWithFundamentals('2024-01-01T00:00:00.000Z')); assert.equal(result.events.length, 0); }); test('runSignalBacktest labels 4-week outcomes using only bars after the signal date', () => { const result = runSignalBacktest(inputWithFundamentals('2023-11-01T00:00:00.000Z')); assert.equal(result.events.length, 1); const event = result.events[0]; assert.ok(event); assert.equal(event.symbol, 'TEST'); assert.equal(event.signalDate.toISOString(), weekIso(204)); assert.equal(event.entryDate.toISOString(), weekIso(205)); assert.equal(event.horizonWeeks, 4); assert.equal(event.exitDate.toISOString(), weekIso(209)); assert.equal(Number(event.forwardReturn.toFixed(4)), Number(((110 - 103) / 103).toFixed(4))); assert.equal(result.summary.eventCount, 1); assert.equal(result.summary.winRate, 1); }); test('runTechnicalSignalBacktest reports quality metrics for 4 and 6 week horizons without fundamentals', () => { const bars = Array.from({ length: 214 }, (_, index) => weeklyQuote(index, 100)); bars[204] = weeklyQuote(204, 101, { open: 98, low: 99, high: 102 }); bars[205] = weeklyQuote(205, 103); bars[209] = weeklyQuote(209, 110); bars[211] = weeklyQuote(211, 99); const result = runTechnicalSignalBacktest({ symbols: [{ symbol: 'TECH', quotes: bars }], horizonsWeeks: [4, 6], }); assert.equal(result.summary.eventCount, 2); assert.equal(result.summary.byHorizon[4].eventCount, 1); assert.equal(result.summary.byHorizon[4].winRate, 1); assert.equal(Number(result.summary.byHorizon[4].meanReturn.toFixed(4)), Number(((110 - 103) / 103).toFixed(4))); assert.equal(result.summary.byHorizon[6].eventCount, 1); assert.equal(result.summary.byHorizon[6].winRate, 0); assert.equal(Number(result.summary.byHorizon[6].meanReturn.toFixed(4)), Number(((99 - 103) / 103).toFixed(4))); });