finished initial backtest / engine

This commit is contained in:
Boki 2025-07-03 12:49:22 -04:00
parent 55b4ca78c9
commit c106a719e8
18 changed files with 1571 additions and 180 deletions

View file

@ -0,0 +1,187 @@
#!/usr/bin/env bun
/**
* Test with predictable data to ensure trades are generated
*/
import { TradingEngine } from '@stock-bot/core';
import { getLogger } from '@stock-bot/logger';
async function testPredictableBacktest() {
console.log('=== Predictable Backtest Test ===\n');
const logger = getLogger('test');
// Create trading engine directly
const tradingEngine = new TradingEngine('backtest', {
startTime: new Date('2023-01-01').getTime(),
endTime: new Date('2023-03-01').getTime(),
speedMultiplier: 0
});
// Set initial capital
await tradingEngine.setCapital(100000);
// Generate predictable price data that will cause crossovers
console.log('Generating predictable market data...');
// Phase 1: Downtrend (days 1-25) - prices fall from 100 to 75
for (let i = 0; i < 25; i++) {
const price = 100 - i;
const timestamp = new Date('2023-01-01').getTime() + i * 86400000;
await tradingEngine.advanceTime(timestamp);
await tradingEngine.updateQuote('AAPL', price - 0.01, price + 0.01, 10000, 10000);
if (i % 5 === 0) {
console.log(`Day ${i + 1}: Price = $${price}`);
}
}
// Phase 2: Uptrend (days 26-50) - prices rise from 76 to 100
for (let i = 25; i < 50; i++) {
const price = 76 + (i - 25);
const timestamp = new Date('2023-01-01').getTime() + i * 86400000;
await tradingEngine.advanceTime(timestamp);
await tradingEngine.updateQuote('AAPL', price - 0.01, price + 0.01, 10000, 10000);
if (i % 5 === 0) {
console.log(`Day ${i + 1}: Price = $${price}`);
}
}
// Phase 3: Another downtrend (days 51-60) - prices fall from 99 to 90
for (let i = 50; i < 60; i++) {
const price = 100 - (i - 50);
const timestamp = new Date('2023-01-01').getTime() + i * 86400000;
await tradingEngine.advanceTime(timestamp);
await tradingEngine.updateQuote('AAPL', price - 0.01, price + 0.01, 10000, 10000);
if (i % 5 === 0) {
console.log(`Day ${i + 1}: Price = $${price}`);
}
}
// Test moving averages manually
console.log('\n=== Expected Crossovers ===');
console.log('Around day 35-40: Golden cross (10 SMA crosses above 20 SMA)');
console.log('Around day 55-60: Death cross (10 SMA crosses below 20 SMA)');
// Get results
const closedTrades = tradingEngine.getClosedTrades ? JSON.parse(tradingEngine.getClosedTrades()) : [];
const tradeCount = tradingEngine.getTradeCount ? tradingEngine.getTradeCount() : 0;
const [realizedPnl, unrealizedPnl] = tradingEngine.getTotalPnl();
console.log('\n=== Results ===');
console.log(`Total trades: ${tradeCount}`);
console.log(`Closed trades: ${closedTrades.length}`);
console.log(`Realized P&L: $${realizedPnl.toFixed(2)}`);
console.log(`Unrealized P&L: $${unrealizedPnl.toFixed(2)}`);
// Now let's test the full backtest with this data pattern
console.log('\n=== Running Full Backtest with SMA Strategy ===');
const { BacktestEngine } = await import('./src/backtest/BacktestEngine');
const { StrategyManager } = await import('./src/strategies/StrategyManager');
const { StorageService } = await import('./src/services/StorageService');
const { ModeManager } = await import('./src/core/ModeManager');
const { MarketDataService } = await import('./src/services/MarketDataService');
const { ExecutionService } = await import('./src/services/ExecutionService');
const { DataManager } = await import('./src/data/DataManager');
const container = {
logger,
custom: {}
};
const storageService = new StorageService(container as any);
const marketDataService = new MarketDataService(container as any);
const executionService = new ExecutionService(container as any);
const modeManager = new ModeManager(container as any, marketDataService, executionService, storageService);
container.custom = {
ModeManager: modeManager,
MarketDataService: marketDataService,
ExecutionService: executionService
};
const strategyManager = new StrategyManager(container as any);
const backtestEngine = new BacktestEngine(container as any, storageService, strategyManager);
// Override the data manager to return our predictable data
const dataManager = new DataManager(container as any, storageService);
(backtestEngine as any).dataManager = dataManager;
// Mock the loadHistoricalData to return our pattern
(dataManager as any).loadHistoricalData = async (symbols: string[], startDate: Date, endDate: Date) => {
const data = new Map();
const bars = [];
// Generate the same pattern as above
for (let i = 0; i < 60; i++) {
let price;
if (i < 25) {
price = 100 - i;
} else if (i < 50) {
price = 76 + (i - 25);
} else {
price = 100 - (i - 50);
}
const timestamp = startDate.getTime() + i * 86400000;
bars.push({
type: 'bar',
data: {
symbol: 'AAPL',
open: price - 0.5,
high: price + 0.5,
low: price - 0.5,
close: price,
volume: 1000000,
timestamp
}
});
}
data.set('AAPL', bars);
return data;
};
const config = {
mode: 'backtest' as const,
name: 'Predictable Test',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01',
endDate: '2023-03-01',
initialCapital: 100000,
dataFrequency: '1d',
commission: 0.001,
slippage: 0.0001,
speed: 'max' as const
};
await modeManager.initializeMode(config);
try {
const result = await backtestEngine.runBacktest(config);
console.log('\nBacktest Results:');
console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
console.log(`Total Trades: ${result.metrics.totalTrades}`);
console.log(`Trades in history: ${result.trades.length}`);
console.log(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`);
console.log('\nTrade Details:');
result.trades.forEach((trade, i) => {
console.log(`Trade ${i + 1}: ${trade.side} ${trade.quantity} @ $${trade.entryPrice.toFixed(2)} -> $${trade.exitPrice?.toFixed(2) || 'OPEN'}`);
});
} catch (error) {
console.error('Backtest failed:', error);
}
}
testPredictableBacktest().catch(console.error);