#!/usr/bin/env bun /** * Test with very clear crossover patterns */ import { BacktestEngine } from './src/backtest/BacktestEngine'; import { StrategyManager } from './src/strategies/StrategyManager'; import { StorageService } from './src/services/StorageService'; import { getLogger } from '@stock-bot/logger'; import { ModeManager } from './src/core/ModeManager'; import { MarketDataService } from './src/services/MarketDataService'; import { ExecutionService } from './src/services/ExecutionService'; import { DataManager } from './src/data/DataManager'; async function testClearCrossovers() { console.log('=== Test with Clear Crossovers ===\n'); const logger = getLogger('test'); 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 data loading to provide clear patterns const dataManager = new DataManager(container as any, storageService); (backtestEngine as any).dataManager = dataManager; (dataManager as any).loadHistoricalData = async (symbols: string[], startDate: Date, endDate: Date) => { const data = new Map(); const bars = []; console.log('Generating clear crossover patterns...'); // Generate 100 days of data with 3 clear crossovers // Pattern: Start high, go low (death cross), go high (golden cross), go low (death cross) for (let i = 0; i < 100; i++) { let price; if (i < 20) { // Start at 100, slight upward trend price = 100 + i * 0.5; } else if (i < 40) { // Sharp downtrend from 110 to 70 (death cross around day 30) price = 110 - (i - 20) * 2; } else if (i < 60) { // Sharp uptrend from 70 to 110 (golden cross around day 50) price = 70 + (i - 40) * 2; } else if (i < 80) { // Sharp downtrend from 110 to 70 (death cross around day 70) price = 110 - (i - 60) * 2; } else { // Stabilize around 70 price = 70 + Math.sin((i - 80) * 0.3) * 2; } const timestamp = startDate.getTime() + i * 86400000; bars.push({ type: 'bar', data: { symbol: 'AAPL', open: price - 0.5, high: price + 1, low: price - 1, close: price, volume: 1000000, timestamp } }); if (i % 10 === 0) { console.log(`Day ${i + 1}: Price = $${price.toFixed(2)}`); } } console.log('\nExpected crossovers:'); console.log('- Death cross around day 30'); console.log('- Golden cross around day 50'); console.log('- Death cross around day 70\n'); data.set('AAPL', bars); return data; }; const config = { mode: 'backtest' as const, name: 'Clear Crossovers Test', strategy: 'sma-crossover', symbols: ['AAPL'], startDate: '2023-01-01T00:00:00Z', endDate: '2023-04-10T00:00:00Z', // 100 days 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('\n=== Backtest 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) => { const entry = new Date(trade.entryDate).toLocaleDateString(); const exit = trade.exitDate ? new Date(trade.exitDate).toLocaleDateString() : 'OPEN'; console.log(`Trade ${i + 1}: ${trade.side} ${trade.quantity} @ $${trade.entryPrice.toFixed(2)} (${entry}) -> ${exit === 'OPEN' ? 'OPEN' : `$${trade.exitPrice.toFixed(2)} (${exit})`} | P&L: ${trade.pnl.toFixed(2)}`); }); } catch (error) { console.error('Backtest failed:', error); } } testClearCrossovers().catch(console.error);