145 lines
No EOL
5.6 KiB
TypeScript
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); |