import { RustBacktestAdapter } from './src/backtest/RustBacktestAdapter'; import { IServiceContainer } from '@stock-bot/di'; import { BacktestConfig } from './src/types'; // Mock container const mockContainer: IServiceContainer = { logger: { info: console.log, error: console.error, warn: console.warn, debug: console.log, }, mongodb: {} as any, postgres: {} as any, redis: {} as any, custom: {}, } as IServiceContainer; // Mock storage service that returns test data class MockStorageService { async getHistoricalBars(symbol: string, startDate: Date, endDate: Date, frequency: string) { console.log(`MockStorageService: Getting bars for ${symbol} from ${startDate} to ${endDate}`); // Generate test data with mean reverting behavior const bars = []; const startTime = startDate.getTime(); const endTime = endDate.getTime(); const dayMs = 24 * 60 * 60 * 1000; let time = startTime; let dayIndex = 0; // Base prices for different symbols const basePrices = { 'AAPL': 150, 'GOOGL': 2800, 'MSFT': 400, }; const basePrice = basePrices[symbol as keyof typeof basePrices] || 100; while (time <= endTime) { // Create mean reverting price movement // Price oscillates around the base price with increasing then decreasing deviations const cycleLength = 40; // 40 day cycle const positionInCycle = dayIndex % cycleLength; const halfCycle = cycleLength / 2; let deviation; if (positionInCycle < halfCycle) { // First half: price moves away from mean deviation = (positionInCycle / halfCycle) * 0.1; // Up to 10% deviation } else { // Second half: price reverts to mean deviation = ((cycleLength - positionInCycle) / halfCycle) * 0.1; } // Alternate between above and below mean const cycleNumber = Math.floor(dayIndex / cycleLength); const multiplier = cycleNumber % 2 === 0 ? 1 : -1; const price = basePrice * (1 + multiplier * deviation); // Add some noise const noise = (Math.random() - 0.5) * 0.02 * basePrice; const finalPrice = price + noise; bars.push({ timestamp: new Date(time), open: finalPrice * 0.99, high: finalPrice * 1.01, low: finalPrice * 0.98, close: finalPrice, volume: 1000000, vwap: finalPrice, }); time += dayMs; dayIndex++; } console.log(`Generated ${bars.length} bars for ${symbol}, first close: ${bars[0].close.toFixed(2)}, last close: ${bars[bars.length - 1].close.toFixed(2)}`); return bars; } } // Test the backtest async function testMeanReversionBacktest() { console.log('=== Testing Mean Reversion Backtest ===\n'); // Create adapter with mock storage const adapter = new RustBacktestAdapter(mockContainer); (adapter as any).storageService = new MockStorageService(); const config: BacktestConfig = { name: 'Mean Reversion Test', strategy: 'mean_reversion', symbols: ['AAPL', 'GOOGL', 'MSFT'], startDate: '2024-01-01T00:00:00Z', endDate: '2024-06-01T00:00:00Z', initialCapital: 100000, commission: 0.001, slippage: 0.0001, dataFrequency: '1d', config: { lookbackPeriod: 20, entryThreshold: 2.0, positionSize: 100, }, }; try { console.log('Starting backtest...\n'); const result = await adapter.runBacktest(config); console.log('\n=== Backtest Results ==='); console.log(`Status: ${result.status}`); console.log(`Total Trades: ${result.metrics.totalTrades}`); console.log(`Profitable Trades: ${result.metrics.profitableTrades}`); console.log(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`); console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`); console.log(`Sharpe Ratio: ${result.metrics.sharpeRatio.toFixed(2)}`); console.log(`Max Drawdown: ${result.metrics.maxDrawdown.toFixed(2)}%`); console.log('\n=== Trade Analysis ==='); console.log(`Number of completed trades: ${result.trades.length}`); // Analyze trades by symbol const tradesBySymbol: Record = {}; result.trades.forEach(trade => { if (!tradesBySymbol[trade.symbol]) { tradesBySymbol[trade.symbol] = []; } tradesBySymbol[trade.symbol].push(trade); }); Object.entries(tradesBySymbol).forEach(([symbol, trades]) => { console.log(`\n${symbol}: ${trades.length} trades`); const longTrades = trades.filter(t => t.side === 'long'); const shortTrades = trades.filter(t => t.side === 'short'); console.log(` - Long trades: ${longTrades.length}`); console.log(` - Short trades: ${shortTrades.length}`); // Count buy/sell pairs const buyTrades = trades.filter(t => t.side === 'buy'); const sellTrades = trades.filter(t => t.side === 'sell'); console.log(` - Buy trades: ${buyTrades.length}`); console.log(` - Sell trades: ${sellTrades.length}`); // Show first few trades console.log(` - First 3 trades:`); trades.slice(0, 3).forEach((trade, idx) => { console.log(` ${idx + 1}. Trade:`, JSON.stringify(trade, null, 2)); }); }); // Analyze trade pairing console.log('\n=== Trade Pairing Analysis ==='); console.log(`Total fills: ${result.trades.length}`); console.log(`Expected pairs: ${result.trades.length / 2}`); // Look for patterns that show instant buy/sell let instantPairs = 0; for (let i = 1; i < result.trades.length; i++) { const prev = result.trades[i-1]; const curr = result.trades[i]; if (prev.symbol === curr.symbol && prev.side === 'buy' && curr.side === 'sell' && new Date(curr.timestamp).getTime() - new Date(prev.timestamp).getTime() < 86400000) { instantPairs++; } } console.log(`Instant buy/sell pairs (< 1 day): ${instantPairs}`); // Final positions console.log('\n=== Final Positions ==='); Object.entries(result.finalPositions).forEach(([symbol, position]) => { console.log(`${symbol}: ${position}`); }); } catch (error) { console.error('Backtest failed:', error); } } // Run the test testMeanReversionBacktest().catch(console.error);