stock-bot/apps/stock/orchestrator/examples/sophisticated-backtest.ts

286 lines
No EOL
10 KiB
TypeScript

#!/usr/bin/env bun
/**
* Example of running a sophisticated backtest with all advanced features
*/
import { BacktestEngine } from '../src/backtest/BacktestEngine';
import { StrategyManager } from '../src/strategies/StrategyManager';
import { StorageService } from '../src/services/StorageService';
import { AnalyticsService } from '../src/services/AnalyticsService';
import { MeanReversionStrategy } from '../src/strategies/examples/MeanReversionStrategy';
import { MLEnhancedStrategy } from '../src/strategies/examples/MLEnhancedStrategy';
import { logger } from '@stock-bot/logger';
async function runSophisticatedBacktest() {
// Initialize services
const storageService = new StorageService();
await storageService.initialize({ mode: 'backtest' });
const analyticsService = new AnalyticsService({
analyticsUrl: process.env.ANALYTICS_SERVICE_URL || 'http://localhost:3003'
});
const strategyManager = new StrategyManager();
// Create backtest engine
const backtestEngine = new BacktestEngine(storageService, strategyManager);
// Configure backtest with advanced options
const config = {
mode: 'backtest' as const,
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-12-31T23:59:59Z',
symbols: ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'TSLA'],
initialCapital: 1_000_000,
dataFrequency: '5m' as const, // 5-minute bars for detailed analysis
// Advanced fill model configuration
fillModel: {
slippage: 'realistic' as const,
marketImpact: true,
partialFills: true,
// Use sophisticated market impact models
impactModel: 'AlmgrenChriss',
// Model hidden liquidity and dark pools
includeHiddenLiquidity: true,
darkPoolParticipation: 0.2, // 20% of volume in dark pools
// Realistic latency simulation
latencyMs: {
mean: 1,
std: 0.5,
tail: 10 // Occasional high latency
}
},
// Risk limits
riskLimits: {
maxPositionSize: 100_000,
maxDailyLoss: 50_000,
maxDrawdown: 0.20, // 20% max drawdown
maxLeverage: 2.0,
maxConcentration: 0.30 // Max 30% in single position
},
// Transaction costs
costs: {
commission: 0.0005, // 5 bps
borrowRate: 0.03, // 3% annual for shorts
slippageModel: 'volumeDependent'
},
// Strategies to test
strategies: [
{
id: 'mean_reversion_1',
name: 'Mean Reversion Strategy',
type: 'MeanReversion',
enabled: true,
allocation: 0.5,
symbols: ['AAPL', 'GOOGL', 'MSFT'],
parameters: {
lookback: 20,
entryZScore: 2.0,
exitZScore: 0.5,
minVolume: 1_000_000,
stopLoss: 0.05 // 5% stop loss
}
},
{
id: 'ml_enhanced_1',
name: 'ML Enhanced Strategy',
type: 'MLEnhanced',
enabled: true,
allocation: 0.5,
symbols: ['AMZN', 'TSLA'],
parameters: {
modelPath: './models/ml_strategy_v1',
updateFrequency: 1440, // Daily retraining
minConfidence: 0.6
}
}
]
};
logger.info('Starting sophisticated backtest...');
try {
// Run the backtest
const result = await backtestEngine.runBacktest(config);
logger.info('Backtest completed successfully');
logger.info(`Total Return: ${result.performance.totalReturn.toFixed(2)}%`);
logger.info(`Sharpe Ratio: ${result.performance.sharpeRatio.toFixed(2)}`);
logger.info(`Max Drawdown: ${result.performance.maxDrawdown.toFixed(2)}%`);
// Run statistical validation
logger.info('Running statistical validation...');
const validationResult = await analyticsService.validateBacktest({
backtestId: result.id,
returns: result.dailyReturns,
trades: result.trades,
parameters: extractParameters(config.strategies)
});
if (validationResult.is_overfit) {
logger.warn('⚠️ WARNING: Backtest shows signs of overfitting!');
logger.warn(`Confidence Level: ${(validationResult.confidence_level * 100).toFixed(1)}%`);
logger.warn('Recommendations:');
validationResult.recommendations.forEach(rec => {
logger.warn(` - ${rec}`);
});
} else {
logger.info('✅ Backtest passed statistical validation');
logger.info(`PSR: ${validationResult.psr.toFixed(3)}`);
logger.info(`DSR: ${validationResult.dsr.toFixed(3)}`);
}
// Generate comprehensive report
logger.info('Generating performance report...');
const report = await backtestEngine.exportResults('html');
// Save report
const fs = require('fs');
const reportPath = `./reports/backtest_${result.id}.html`;
fs.writeFileSync(reportPath, report);
logger.info(`Report saved to: ${reportPath}`);
// Advanced analytics
logger.info('Running advanced analytics...');
// Factor attribution
const factorAnalysis = await analyticsService.analyzeFactors({
returns: result.dailyReturns,
positions: result.finalPositions,
marketReturns: await getMarketReturns(config.startDate, config.endDate)
});
logger.info('Factor Attribution:');
logger.info(` Alpha: ${(factorAnalysis.alpha * 100).toFixed(2)}%`);
logger.info(` Beta: ${factorAnalysis.beta.toFixed(2)}`);
logger.info(` Information Ratio: ${factorAnalysis.information_ratio.toFixed(2)}`);
// Transaction cost analysis
const tcaReport = await analyticsService.analyzeTCA({
trades: result.trades,
orders: result.orders
});
logger.info('Transaction Cost Analysis:');
logger.info(` Total Costs: $${tcaReport.total_costs.toFixed(2)}`);
logger.info(` Avg Cost per Trade: ${tcaReport.avg_cost_bps.toFixed(1)} bps`);
logger.info(` Implementation Shortfall: ${tcaReport.implementation_shortfall_bps.toFixed(1)} bps`);
// Performance by time period
const periodAnalysis = analyzeByPeriod(result);
logger.info('Performance by Period:');
Object.entries(periodAnalysis).forEach(([period, metrics]) => {
logger.info(` ${period}: ${metrics.return.toFixed(2)}% (Sharpe: ${metrics.sharpe.toFixed(2)})`);
});
// Strategy correlation analysis
if (config.strategies.length > 1) {
const correlations = await calculateStrategyCorrelations(result);
logger.info('Strategy Correlations:');
correlations.forEach(({ pair, correlation }) => {
logger.info(` ${pair}: ${correlation.toFixed(3)}`);
});
}
// Monte Carlo simulation
logger.info('Running Monte Carlo simulation...');
const monteCarloResults = await runMonteCarloSimulation(result, 1000);
logger.info(`Monte Carlo 95% VaR: ${monteCarloResults.var95.toFixed(2)}%`);
logger.info(`Monte Carlo 95% CVaR: ${monteCarloResults.cvar95.toFixed(2)}%`);
// Walk-forward analysis suggestion
if (result.performance.totalTrades > 100) {
logger.info('\n💡 Suggestion: Run walk-forward analysis for more robust validation');
logger.info('Example: bun run examples/walk-forward-analysis.ts');
}
} catch (error) {
logger.error('Backtest failed:', error);
} finally {
await storageService.shutdown();
}
}
// Helper functions
function extractParameters(strategies: any[]): Record<string, any> {
const params: Record<string, any> = {};
strategies.forEach(strategy => {
Object.entries(strategy.parameters).forEach(([key, value]) => {
params[`${strategy.id}_${key}`] = value;
});
});
return params;
}
async function getMarketReturns(startDate: string, endDate: string): Promise<number[]> {
// In real implementation, would fetch SPY or market index returns
// For demo, return synthetic market returns
const days = Math.floor((new Date(endDate).getTime() - new Date(startDate).getTime()) / (1000 * 60 * 60 * 24));
return Array.from({ length: days }, () => (Math.random() - 0.5) * 0.02);
}
function analyzeByPeriod(result: any): Record<string, { return: number; sharpe: number }> {
const periods = {
'Q1': { start: 0, end: 63 },
'Q2': { start: 63, end: 126 },
'Q3': { start: 126, end: 189 },
'Q4': { start: 189, end: 252 }
};
const analysis: Record<string, { return: number; sharpe: number }> = {};
Object.entries(periods).forEach(([name, { start, end }]) => {
const periodReturns = result.dailyReturns.slice(start, end);
if (periodReturns.length > 0) {
const avgReturn = periodReturns.reduce((a, b) => a + b, 0) / periodReturns.length;
const std = Math.sqrt(periodReturns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / periodReturns.length);
analysis[name] = {
return: avgReturn * periodReturns.length * 100,
sharpe: std > 0 ? (avgReturn / std) * Math.sqrt(252) : 0
};
}
});
return analysis;
}
async function calculateStrategyCorrelations(result: any): Promise<Array<{ pair: string; correlation: number }>> {
// In real implementation, would calculate actual strategy return correlations
// For demo, return sample correlations
return [
{ pair: 'mean_reversion_1 vs ml_enhanced_1', correlation: 0.234 }
];
}
async function runMonteCarloSimulation(result: any, numSims: number): Promise<{ var95: number; cvar95: number }> {
const returns = result.dailyReturns;
const simulatedReturns: number[] = [];
for (let i = 0; i < numSims; i++) {
// Bootstrap resample returns
let cumReturn = 0;
for (let j = 0; j < returns.length; j++) {
const randomIndex = Math.floor(Math.random() * returns.length);
cumReturn += returns[randomIndex];
}
simulatedReturns.push(cumReturn * 100);
}
// Calculate VaR and CVaR
simulatedReturns.sort((a, b) => a - b);
const index95 = Math.floor(numSims * 0.05);
const var95 = Math.abs(simulatedReturns[index95]);
const cvar95 = Math.abs(simulatedReturns.slice(0, index95).reduce((a, b) => a + b, 0) / index95);
return { var95, cvar95 };
}
// Run the backtest
runSophisticatedBacktest().catch(console.error);