import { DataFrame } from '@stock-bot/data-frame'; import { getLogger } from '@stock-bot/logger'; import { atr, bollingerBands, ema, macd, rsi, sma } from '@stock-bot/utils'; // Vector operations interface export interface VectorOperation { name: string; inputs: string[]; output: string; operation: (inputs: number[][]) => number[]; } // Vectorized strategy context export interface VectorizedContext { data: DataFrame; lookback: number; indicators: Record; signals: Record; } // Performance metrics for vectorized backtesting export interface VectorizedMetrics { totalReturns: number; sharpeRatio: number; maxDrawdown: number; winRate: number; profitFactor: number; totalTrades: number; avgTrade: number; returns: number[]; drawdown: number[]; equity: number[]; } // Vectorized backtest result export interface VectorizedBacktestResult { metrics: VectorizedMetrics; trades: VectorizedTrade[]; equity: number[]; timestamps: number[]; signals: Record; } export interface VectorizedTrade { entryIndex: number; exitIndex: number; entryPrice: number; exitPrice: number; quantity: number; side: 'LONG' | 'SHORT'; pnl: number; return: number; duration: number; } // Vectorized strategy engine export class VectorEngine { private logger = getLogger('vector-engine'); private operations: Map = new Map(); constructor() { this.registerDefaultOperations(); } private registerDefaultOperations(): void { // Register common mathematical operations this.registerOperation({ name: 'add', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => a.map((val, i) => val + b[i]), }); this.registerOperation({ name: 'subtract', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => a.map((val, i) => val - b[i]), }); this.registerOperation({ name: 'multiply', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => a.map((val, i) => val * b[i]), }); this.registerOperation({ name: 'divide', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => a.map((val, i) => (b[i] !== 0 ? val / b[i] : NaN)), }); // Register comparison operations this.registerOperation({ name: 'greater_than', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => a.map((val, i) => (val > b[i] ? 1 : 0)), }); this.registerOperation({ name: 'less_than', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => a.map((val, i) => (val < b[i] ? 1 : 0)), }); this.registerOperation({ name: 'crossover', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => { const result = new Array(a.length).fill(0); for (let i = 1; i < a.length; i++) { if (a[i] > b[i] && a[i - 1] <= b[i - 1]) { result[i] = 1; } } return result; }, }); this.registerOperation({ name: 'crossunder', inputs: ['a', 'b'], output: 'result', operation: ([a, b]) => { const result = new Array(a.length).fill(0); for (let i = 1; i < a.length; i++) { if (a[i] < b[i] && a[i - 1] >= b[i - 1]) { result[i] = 1; } } return result; }, }); } registerOperation(operation: VectorOperation): void { this.operations.set(operation.name, operation); this.logger.debug(`Registered operation: ${operation.name}`); } // Execute vectorized strategy async executeVectorizedStrategy( data: DataFrame, strategyCode: string ): Promise { try { const context = this.prepareContext(data); const signals = this.executeStrategy(context, strategyCode); const trades = this.generateTrades(data, signals); const metrics = this.calculateMetrics(data, trades); return { metrics, trades, equity: metrics.equity, timestamps: data.getColumn('timestamp'), signals, }; } catch (error) { this.logger.error('Vectorized strategy execution failed', error); throw error; } } private prepareContext(data: DataFrame): VectorizedContext { const close = data.getColumn('close'); const high = data.getColumn('high'); const low = data.getColumn('low'); const volume = data.getColumn('volume'); // Calculate common indicators const indicators: Record = { sma_20: sma(close, 20), sma_50: sma(close, 50), ema_12: ema(close, 12), ema_26: ema(close, 26), rsi: rsi(close), }; const m = macd(close); indicators.macd = m.macd; indicators.macd_signal = m.signal; indicators.macd_histogram = m.histogram; const bb = bollingerBands(close); indicators.bb_upper = bb.upper; indicators.bb_middle = bb.middle; indicators.bb_lower = bb.lower; return { data, lookback: 100, indicators, signals: {}, }; } private executeStrategy( context: VectorizedContext, strategyCode: string ): Record { // This is a simplified strategy execution // In production, you'd want a more sophisticated strategy compiler/interpreter const signals: Record = { buy: new Array(context.data.length).fill(0), sell: new Array(context.data.length).fill(0), }; // Example: Simple moving average crossover strategy if (strategyCode.includes('sma_crossover')) { const sma20 = context.indicators.sma_20; const sma50 = context.indicators.sma_50; for (let i = 1; i < sma20.length; i++) { // Buy signal: SMA20 crosses above SMA50 if (!isNaN(sma20[i]) && !isNaN(sma50[i]) && !isNaN(sma20[i - 1]) && !isNaN(sma50[i - 1])) { if (sma20[i] > sma50[i] && sma20[i - 1] <= sma50[i - 1]) { signals.buy[i] = 1; } // Sell signal: SMA20 crosses below SMA50 else if (sma20[i] < sma50[i] && sma20[i - 1] >= sma50[i - 1]) { signals.sell[i] = 1; } } } } return signals; } private generateTrades(data: DataFrame, signals: Record): VectorizedTrade[] { const trades: VectorizedTrade[] = []; const close = data.getColumn('close'); const timestamps = data.getColumn('timestamp'); let position: { index: number; price: number; side: 'LONG' | 'SHORT' } | null = null; for (let i = 0; i < close.length; i++) { if (signals.buy[i] === 1 && !position) { // Open long position position = { index: i, price: close[i], side: 'LONG', }; } else if (signals.sell[i] === 1) { if (position && position.side === 'LONG') { // Close long position const trade: VectorizedTrade = { entryIndex: position.index, exitIndex: i, entryPrice: position.price, exitPrice: close[i], quantity: 1, // Simplified: always trade 1 unit side: 'LONG', pnl: close[i] - position.price, return: (close[i] - position.price) / position.price, duration: timestamps[i] - timestamps[position.index], }; trades.push(trade); position = null; } else if (!position) { // Open short position position = { index: i, price: close[i], side: 'SHORT', }; } } else if (signals.buy[i] === 1 && position && position.side === 'SHORT') { // Close short position const trade: VectorizedTrade = { entryIndex: position.index, exitIndex: i, entryPrice: position.price, exitPrice: close[i], quantity: 1, side: 'SHORT', pnl: position.price - close[i], return: (position.price - close[i]) / position.price, duration: timestamps[i] - timestamps[position.index], }; trades.push(trade); position = null; } } return trades; } private calculateMetrics(data: DataFrame, trades: VectorizedTrade[]): VectorizedMetrics { if (trades.length === 0) { return { totalReturns: 0, sharpeRatio: 0, maxDrawdown: 0, winRate: 0, profitFactor: 0, totalTrades: 0, avgTrade: 0, returns: [], drawdown: [], equity: [], }; } const returns = trades.map(t => t.return); const pnls = trades.map(t => t.pnl); // Calculate equity curve const equity: number[] = [10000]; // Starting capital let currentEquity = 10000; for (const trade of trades) { currentEquity += trade.pnl; equity.push(currentEquity); } // Calculate drawdown const drawdown: number[] = []; let peak = equity[0]; for (const eq of equity) { if (eq > peak) {peak = eq;} drawdown.push((peak - eq) / peak); } const totalReturns = (equity[equity.length - 1] - equity[0]) / equity[0]; const avgReturn = returns.reduce((sum, r) => sum + r, 0) / returns.length; const returnStd = Math.sqrt( returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length ); const winningTrades = trades.filter(t => t.pnl > 0); const losingTrades = trades.filter(t => t.pnl < 0); const grossProfit = winningTrades.reduce((sum, t) => sum + t.pnl, 0); const grossLoss = Math.abs(losingTrades.reduce((sum, t) => sum + t.pnl, 0)); return { totalReturns, sharpeRatio: returnStd !== 0 ? (avgReturn / returnStd) * Math.sqrt(252) : 0, maxDrawdown: Math.max(...drawdown), winRate: winningTrades.length / trades.length, profitFactor: grossLoss !== 0 ? grossProfit / grossLoss : Infinity, totalTrades: trades.length, avgTrade: pnls.reduce((sum, pnl) => sum + pnl, 0) / trades.length, returns, drawdown, equity, }; } // Utility methods for vectorized operations applyOperation(operationName: string, inputs: Record): number[] { const operation = this.operations.get(operationName); if (!operation) { throw new Error(`Operation '${operationName}' not found`); } const inputArrays = operation.inputs.map(inputName => { if (!inputs[inputName]) { throw new Error(`Input '${inputName}' not provided for operation '${operationName}'`); } return inputs[inputName]; }); return operation.operation(inputArrays); } // Batch processing for multiple strategies async batchBacktest( data: DataFrame, strategies: Array<{ id: string; code: string }> ): Promise> { const results: Record = {}; for (const strategy of strategies) { try { this.logger.info(`Running vectorized backtest for strategy: ${strategy.id}`); results[strategy.id] = await this.executeVectorizedStrategy(data, strategy.code); } catch (error) { this.logger.error(`Backtest failed for strategy: ${strategy.id}`, error); // Continue with other strategies } } return results; } }