236 lines
7.7 KiB
TypeScript
236 lines
7.7 KiB
TypeScript
import { DataFrame } from '@stock-bot/data-frame';
|
|
import { EventBus } from '@stock-bot/event-bus';
|
|
import { getLogger } from '@stock-bot/logger';
|
|
import { VectorEngine, VectorizedBacktestResult } from '@stock-bot/vector-engine';
|
|
import { BacktestContext, BacktestResult, ExecutionMode } from '../framework/execution-mode';
|
|
|
|
export interface VectorizedModeConfig {
|
|
batchSize?: number;
|
|
enableOptimization?: boolean;
|
|
parallelProcessing?: boolean;
|
|
}
|
|
|
|
export class VectorizedMode extends ExecutionMode {
|
|
private vectorEngine: VectorEngine;
|
|
private config: VectorizedModeConfig;
|
|
private logger = getLogger('vectorized-mode');
|
|
|
|
constructor(context: BacktestContext, eventBus: EventBus, config: VectorizedModeConfig = {}) {
|
|
super(context, eventBus);
|
|
this.vectorEngine = new VectorEngine();
|
|
this.config = {
|
|
batchSize: 10000,
|
|
enableOptimization: true,
|
|
parallelProcessing: true,
|
|
...config,
|
|
};
|
|
}
|
|
|
|
async initialize(): Promise<void> {
|
|
await super.initialize();
|
|
this.logger.info('Vectorized mode initialized', {
|
|
backtestId: this.context.backtestId,
|
|
config: this.config,
|
|
});
|
|
}
|
|
|
|
async execute(): Promise<BacktestResult> {
|
|
const startTime = Date.now();
|
|
this.logger.info('Starting vectorized backtest execution');
|
|
|
|
try {
|
|
// Load all data at once for vectorized processing
|
|
const data = await this.loadHistoricalData();
|
|
|
|
// Convert to DataFrame format
|
|
const dataFrame = this.createDataFrame(data);
|
|
|
|
// Execute vectorized strategy
|
|
const strategyCode = this.generateStrategyCode();
|
|
const vectorResult = await this.vectorEngine.executeVectorizedStrategy(
|
|
dataFrame,
|
|
strategyCode
|
|
);
|
|
|
|
// Convert to standard backtest result format
|
|
const result = this.convertVectorizedResult(vectorResult, startTime);
|
|
|
|
// Emit completion event
|
|
await this.eventBus.publishBacktestUpdate(this.context.backtestId, 100, {
|
|
status: 'completed',
|
|
result,
|
|
});
|
|
|
|
this.logger.info('Vectorized backtest completed', {
|
|
backtestId: this.context.backtestId,
|
|
duration: Date.now() - startTime,
|
|
totalTrades: result.trades.length,
|
|
});
|
|
|
|
return result;
|
|
} catch (error) {
|
|
this.logger.error('Vectorized backtest failed', {
|
|
error,
|
|
backtestId: this.context.backtestId,
|
|
});
|
|
|
|
await this.eventBus.publishBacktestUpdate(this.context.backtestId, 0, {
|
|
status: 'failed',
|
|
error: error.message,
|
|
});
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async loadHistoricalData(): Promise<any[]> {
|
|
// Load all historical data at once
|
|
// This is much more efficient than loading tick by tick
|
|
const data = [];
|
|
|
|
// Simulate loading data (in production, this would be a bulk database query)
|
|
const startTime = new Date(this.context.startDate).getTime();
|
|
const endTime = new Date(this.context.endDate).getTime();
|
|
const interval = 60000; // 1 minute intervals
|
|
|
|
for (let timestamp = startTime; timestamp <= endTime; timestamp += interval) {
|
|
// Simulate OHLCV data
|
|
const basePrice = 100 + Math.sin(timestamp / 1000000) * 10;
|
|
const volatility = 0.02;
|
|
|
|
const open = basePrice + (Math.random() - 0.5) * volatility * basePrice;
|
|
const close = open + (Math.random() - 0.5) * volatility * basePrice;
|
|
const high = Math.max(open, close) + Math.random() * volatility * basePrice;
|
|
const low = Math.min(open, close) - Math.random() * volatility * basePrice;
|
|
const volume = Math.floor(Math.random() * 10000) + 1000;
|
|
|
|
data.push({
|
|
timestamp,
|
|
symbol: this.context.symbol,
|
|
open,
|
|
high,
|
|
low,
|
|
close,
|
|
volume,
|
|
});
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
private createDataFrame(data: any[]): DataFrame {
|
|
return new DataFrame(data, {
|
|
columns: ['timestamp', 'symbol', 'open', 'high', 'low', 'close', 'volume'],
|
|
dtypes: {
|
|
timestamp: 'number',
|
|
symbol: 'string',
|
|
open: 'number',
|
|
high: 'number',
|
|
low: 'number',
|
|
close: 'number',
|
|
volume: 'number',
|
|
},
|
|
});
|
|
}
|
|
|
|
private generateStrategyCode(): string {
|
|
// Convert strategy configuration to vectorized strategy code
|
|
// This is a simplified example - in production you'd have a more sophisticated compiler
|
|
const strategy = this.context.strategy;
|
|
|
|
if (strategy.type === 'sma_crossover') {
|
|
return 'sma_crossover';
|
|
}
|
|
|
|
// Add more strategy types as needed
|
|
return strategy.code || 'sma_crossover';
|
|
}
|
|
|
|
private convertVectorizedResult(
|
|
vectorResult: VectorizedBacktestResult,
|
|
startTime: number
|
|
): BacktestResult {
|
|
return {
|
|
backtestId: this.context.backtestId,
|
|
strategy: this.context.strategy,
|
|
symbol: this.context.symbol,
|
|
startDate: this.context.startDate,
|
|
endDate: this.context.endDate,
|
|
mode: 'vectorized',
|
|
duration: Date.now() - startTime,
|
|
trades: vectorResult.trades.map(trade => ({
|
|
id: `trade_${trade.entryIndex}_${trade.exitIndex}`,
|
|
symbol: this.context.symbol,
|
|
side: trade.side,
|
|
entryTime: vectorResult.timestamps[trade.entryIndex],
|
|
exitTime: vectorResult.timestamps[trade.exitIndex],
|
|
entryPrice: trade.entryPrice,
|
|
exitPrice: trade.exitPrice,
|
|
quantity: trade.quantity,
|
|
pnl: trade.pnl,
|
|
commission: 0, // Simplified
|
|
slippage: 0,
|
|
})),
|
|
performance: {
|
|
totalReturn: vectorResult.metrics.totalReturns,
|
|
sharpeRatio: vectorResult.metrics.sharpeRatio,
|
|
maxDrawdown: vectorResult.metrics.maxDrawdown,
|
|
winRate: vectorResult.metrics.winRate,
|
|
profitFactor: vectorResult.metrics.profitFactor,
|
|
totalTrades: vectorResult.metrics.totalTrades,
|
|
winningTrades: vectorResult.trades.filter(t => t.pnl > 0).length,
|
|
losingTrades: vectorResult.trades.filter(t => t.pnl <= 0).length,
|
|
avgTrade: vectorResult.metrics.avgTrade,
|
|
avgWin:
|
|
vectorResult.trades.filter(t => t.pnl > 0).reduce((sum, t) => sum + t.pnl, 0) /
|
|
vectorResult.trades.filter(t => t.pnl > 0).length || 0,
|
|
avgLoss:
|
|
vectorResult.trades.filter(t => t.pnl <= 0).reduce((sum, t) => sum + t.pnl, 0) /
|
|
vectorResult.trades.filter(t => t.pnl <= 0).length || 0,
|
|
largestWin: Math.max(...vectorResult.trades.map(t => t.pnl), 0),
|
|
largestLoss: Math.min(...vectorResult.trades.map(t => t.pnl), 0),
|
|
},
|
|
equity: vectorResult.equity,
|
|
drawdown: vectorResult.metrics.drawdown,
|
|
metadata: {
|
|
mode: 'vectorized',
|
|
dataPoints: vectorResult.timestamps.length,
|
|
signals: Object.keys(vectorResult.signals),
|
|
optimizations: this.config.enableOptimization ? ['vectorized_computation'] : [],
|
|
},
|
|
};
|
|
}
|
|
|
|
async cleanup(): Promise<void> {
|
|
await super.cleanup();
|
|
this.logger.info('Vectorized mode cleanup completed');
|
|
}
|
|
|
|
// Batch processing capabilities
|
|
async batchBacktest(
|
|
strategies: Array<{ id: string; config: any }>
|
|
): Promise<Record<string, BacktestResult>> {
|
|
this.logger.info('Starting batch vectorized backtest', {
|
|
strategiesCount: strategies.length,
|
|
});
|
|
|
|
const data = await this.loadHistoricalData();
|
|
const dataFrame = this.createDataFrame(data);
|
|
|
|
const strategyConfigs = strategies.map(s => ({
|
|
id: s.id,
|
|
code: this.generateStrategyCode(),
|
|
}));
|
|
|
|
const batchResults = await this.vectorEngine.batchBacktest(dataFrame, strategyConfigs);
|
|
const results: Record<string, BacktestResult> = {};
|
|
|
|
for (const [strategyId, vectorResult] of Object.entries(batchResults)) {
|
|
results[strategyId] = this.convertVectorizedResult(vectorResult, Date.now());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
}
|
|
|
|
export default VectorizedMode;
|