stock-bot/apps/stock/orchestrator/test-rust-backtest.ts
2025-07-03 20:10:33 -04:00

145 lines
No EOL
5.6 KiB
TypeScript

import { createRustBacktest } from './src/backtest/RustBacktestEngine';
import { SimpleMovingAverageCrossoverRust } from './src/strategies/rust/SimpleMovingAverageCrossoverRust';
import { IServiceContainer } from '@stock-bot/di';
// Mock StorageService
class MockStorageService {
async getHistoricalBars(symbol: string, startDate: Date, endDate: Date, frequency: string) {
// Generate mock data
const bars = [];
const msPerDay = 24 * 60 * 60 * 1000;
let currentDate = new Date(startDate);
let price = 100 + Math.random() * 50; // Start between 100-150
while (currentDate <= endDate) {
// Random walk
const change = (Math.random() - 0.5) * 2; // +/- 1%
price *= (1 + change / 100);
bars.push({
symbol,
timestamp: new Date(currentDate),
open: price * (1 + (Math.random() - 0.5) * 0.01),
high: price * (1 + Math.random() * 0.02),
low: price * (1 - Math.random() * 0.02),
close: price,
volume: 1000000 + Math.random() * 500000,
});
currentDate = new Date(currentDate.getTime() + msPerDay);
}
return bars;
}
}
async function testRustBacktest() {
console.log('🚀 Testing Rust Backtest Engine with TypeScript Strategy\n');
// Create minimal container
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args),
error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args),
warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args),
debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args),
} as any,
custom: {
StorageService: new MockStorageService(),
}
};
// Backtest configuration
const config = {
mode: 'backtest' as const,
name: 'Rust Engine Test',
strategy: 'sma-crossover',
symbols: ['AAPL'], // Just one symbol for testing
startDate: '2023-01-01T00:00:00Z',
endDate: '2023-01-31T00:00:00Z', // Just one month for testing
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d',
speed: 'max' as const,
};
// Create strategy
const strategy = new SimpleMovingAverageCrossoverRust({
fastPeriod: 10,
slowPeriod: 30,
minHoldingBars: 5,
});
console.log('Configuration:');
console.log(` Symbols: ${config.symbols.join(', ')}`);
console.log(` Period: ${config.startDate} to ${config.endDate}`);
console.log(` Initial Capital: $${config.initialCapital.toLocaleString()}`);
console.log(` Strategy: ${strategy.constructor.name}`);
console.log('');
try {
console.log('Running backtest in Rust engine...\n');
const startTime = Date.now();
try {
const result = await createRustBacktest(container, config, [strategy]);
console.log('Raw result:', result);
const duration = (Date.now() - startTime) / 1000;
console.log(`\n✅ Backtest completed in ${duration.toFixed(2)} seconds`);
if (!result || !result.metrics) {
console.error('Invalid result structure:', result);
return;
}
console.log('\n=== PERFORMANCE METRICS ===');
console.log(`Total Return: ${result.metrics.totalReturn?.toFixed(2) || 'N/A'}%`);
console.log(`Sharpe Ratio: ${result.metrics.sharpeRatio?.toFixed(2) || 'N/A'}`);
console.log(`Max Drawdown: ${result.metrics.maxDrawdown ? (result.metrics.maxDrawdown * 100).toFixed(2) : 'N/A'}%`);
console.log(`Win Rate: ${result.metrics.winRate?.toFixed(1) || 'N/A'}%`);
console.log(`Total Trades: ${result.metrics.totalTrades || 0}`);
console.log(`Profit Factor: ${result.metrics.profitFactor?.toFixed(2) || 'N/A'}`);
console.log('\n=== TRADE STATISTICS ===');
console.log(`Profitable Trades: ${result.metrics.profitableTrades || 0}`);
console.log(`Average Win: $${result.metrics.avgWin?.toFixed(2) || '0.00'}`);
console.log(`Average Loss: $${result.metrics.avgLoss?.toFixed(2) || '0.00'}`);
console.log(`Total P&L: $${result.metrics.totalPnl?.toFixed(2) || '0.00'}`);
console.log('\n=== EQUITY CURVE ===');
if (result.equityCurve.length > 0) {
const firstValue = result.equityCurve[0].value;
const lastValue = result.equityCurve[result.equityCurve.length - 1].value;
console.log(`Starting Value: $${firstValue.toLocaleString()}`);
console.log(`Ending Value: $${lastValue.toLocaleString()}`);
console.log(`Growth: ${((lastValue / firstValue - 1) * 100).toFixed(2)}%`);
}
console.log('\n=== FINAL POSITIONS ===');
const positions = Object.entries(result.finalPositions);
if (positions.length > 0) {
for (const [symbol, position] of positions) {
console.log(`${symbol}: ${position.quantity} shares @ $${position.averagePrice}`);
}
} else {
console.log('No open positions');
}
// Compare with TypeScript engine performance
console.log('\n=== PERFORMANCE COMPARISON ===');
console.log('TypeScript Engine: ~5-10 seconds for 1 year backtest');
console.log(`Rust Engine: ${duration.toFixed(2)} seconds`);
console.log(`Speed Improvement: ${(10 / duration).toFixed(1)}x faster`);
} catch (innerError) {
console.error('Result processing error:', innerError);
}
} catch (error) {
console.error('❌ Backtest failed:', error);
}
}
// Run the test
testRustBacktest().catch(console.error);