/** * Neural Network Training for Trading * * Demonstrates using @neural-trader/neural for: * - LSTM price prediction models * - Feature engineering pipeline * - Walk-forward training * - Model evaluation and deployment * * Integrates with RuVector for pattern storage and retrieval */ // Neural network configuration const neuralConfig = { // Architecture model: { type: 'lstm', // lstm, gru, transformer, tcn inputSize: 128, // Feature dimension hiddenSize: 64, numLayers: 2, dropout: 0.3, bidirectional: false }, // Training settings training: { epochs: 100, batchSize: 32, learningRate: 0.001, earlyStoppingPatience: 10, validationSplit: 0.2 }, // Sequence settings sequence: { lookback: 60, // 60 time steps lookback horizon: 5, // Predict 5 steps ahead stride: 1 }, // Feature groups features: { price: true, volume: true, technicals: true, sentiment: false, orderFlow: false } }; async function main() { console.log('='.repeat(70)); console.log('Neural Network Training - Neural Trader'); console.log('='.repeat(70)); console.log(); // 1. Load and prepare data console.log('1. Loading market data...'); const rawData = generateMarketData(5000); // 5000 data points console.log(` Loaded ${rawData.length} data points`); console.log(); // 2. Feature engineering console.log('2. Feature engineering...'); const startFE = performance.now(); const features = engineerFeatures(rawData, neuralConfig); const feTime = performance.now() - startFE; console.log(` Generated ${features.length} samples`); console.log(` Feature dimension: ${neuralConfig.model.inputSize}`); console.log(` Time: ${feTime.toFixed(2)}ms`); console.log(); // 3. Create sequences console.log('3. Creating sequences...'); const { X, y, dates } = createSequences(features, neuralConfig.sequence); console.log(` Sequences: ${X.length}`); console.log(` X shape: [${X.length}, ${neuralConfig.sequence.lookback}, ${neuralConfig.model.inputSize}]`); console.log(` y shape: [${y.length}, ${neuralConfig.sequence.horizon}]`); console.log(); // 4. Train-validation split console.log('4. Train-validation split...'); const splitIdx = Math.floor(X.length * (1 - neuralConfig.training.validationSplit)); const trainX = X.slice(0, splitIdx); const trainY = y.slice(0, splitIdx); const valX = X.slice(splitIdx); const valY = y.slice(splitIdx); console.log(` Training samples: ${trainX.length}`); console.log(` Validation samples: ${valX.length}`); console.log(); // 5. Model training console.log('5. Training neural network...'); console.log(` Model: ${neuralConfig.model.type.toUpperCase()}`); console.log(` Hidden size: ${neuralConfig.model.hiddenSize}`); console.log(` Layers: ${neuralConfig.model.numLayers}`); console.log(` Dropout: ${neuralConfig.model.dropout}`); console.log(); const trainingHistory = await trainModel(trainX, trainY, valX, valY, neuralConfig); // Display training progress console.log(' Epoch | Train Loss | Val Loss | Val MAE | Time'); console.log(' ' + '-'.repeat(50)); for (let i = 0; i < Math.min(10, trainingHistory.epochs.length); i++) { const epoch = trainingHistory.epochs[i]; console.log(` ${(epoch.epoch + 1).toString().padStart(5)} | ${epoch.trainLoss.toFixed(4).padStart(10)} | ${epoch.valLoss.toFixed(4).padStart(8)} | ${epoch.valMae.toFixed(4).padStart(8)} | ${epoch.time.toFixed(0).padStart(4)}ms`); } if (trainingHistory.epochs.length > 10) { console.log(' ...'); const last = trainingHistory.epochs[trainingHistory.epochs.length - 1]; console.log(` ${(last.epoch + 1).toString().padStart(5)} | ${last.trainLoss.toFixed(4).padStart(10)} | ${last.valLoss.toFixed(4).padStart(8)} | ${last.valMae.toFixed(4).padStart(8)} | ${last.time.toFixed(0).padStart(4)}ms`); } console.log(); console.log(` Best epoch: ${trainingHistory.bestEpoch + 1}`); console.log(` Best val loss: ${trainingHistory.bestValLoss.toFixed(4)}`); console.log(` Early stopping: ${trainingHistory.earlyStopped ? 'Yes' : 'No'}`); console.log(` Total time: ${(trainingHistory.totalTime / 1000).toFixed(1)}s`); console.log(); // 6. Model evaluation console.log('6. Model evaluation...'); const evaluation = evaluateModel(valX, valY, trainingHistory.predictions); console.log(` MAE: ${evaluation.mae.toFixed(4)}`); console.log(` RMSE: ${evaluation.rmse.toFixed(4)}`); console.log(` R²: ${evaluation.r2.toFixed(4)}`); console.log(` Direction Accuracy: ${(evaluation.directionAccuracy * 100).toFixed(1)}%`); console.log(); // 7. Prediction analysis console.log('7. Prediction analysis:'); console.log('-'.repeat(70)); console.log(' Horizon | MAE | Direction | Hit Rate'); console.log('-'.repeat(70)); for (let h = 1; h <= neuralConfig.sequence.horizon; h++) { const horizonMetrics = evaluateHorizon(valY, trainingHistory.predictions, h); console.log(` ${h.toString().padStart(7)} | ${horizonMetrics.mae.toFixed(4).padStart(7)} | ${(horizonMetrics.direction * 100).toFixed(1).padStart(9)}% | ${(horizonMetrics.hitRate * 100).toFixed(1).padStart(8)}%`); } console.log(); // 8. Trading simulation with predictions console.log('8. Trading simulation with predictions:'); const tradingResults = simulateTrading(valY, trainingHistory.predictions, rawData.slice(-valY.length)); console.log(` Total return: ${(tradingResults.totalReturn * 100).toFixed(2)}%`); console.log(` Sharpe ratio: ${tradingResults.sharpe.toFixed(2)}`); console.log(` Win rate: ${(tradingResults.winRate * 100).toFixed(1)}%`); console.log(` Profit factor: ${tradingResults.profitFactor.toFixed(2)}`); console.log(` Max drawdown: ${(tradingResults.maxDrawdown * 100).toFixed(2)}%`); console.log(); // 9. Pattern storage integration console.log('9. Pattern storage (RuVector integration):'); const storedPatterns = storePatterns(valX, trainingHistory.predictions, valY); console.log(` Stored ${storedPatterns.count} prediction patterns`); console.log(` High-confidence patterns: ${storedPatterns.highConfidence}`); console.log(` Average confidence: ${(storedPatterns.avgConfidence * 100).toFixed(1)}%`); console.log(); // 10. Model export console.log('10. Model export:'); const modelInfo = { architecture: neuralConfig.model, inputShape: [neuralConfig.sequence.lookback, neuralConfig.model.inputSize], outputShape: [neuralConfig.sequence.horizon], parameters: calculateModelParams(neuralConfig.model), trainingSamples: trainX.length, bestValLoss: trainingHistory.bestValLoss }; console.log(` Architecture: ${modelInfo.architecture.type}`); console.log(` Parameters: ${modelInfo.parameters.toLocaleString()}`); console.log(` Export format: ONNX, TorchScript`); console.log(` Model size: ~${Math.ceil(modelInfo.parameters * 4 / 1024)}KB`); console.log(); console.log('='.repeat(70)); console.log('Neural network training completed!'); console.log('='.repeat(70)); } // Generate synthetic market data function generateMarketData(count) { const data = []; let price = 100; const baseTime = Date.now() - count * 3600000; for (let i = 0; i < count; i++) { // Price evolution with trend, seasonality, and noise const trend = 0.0001; const seasonality = Math.sin(i / 100) * 0.001; const noise = (Math.random() - 0.5) * 0.02; const regime = Math.sin(i / 500) > 0 ? 1.2 : 0.8; // Regime switching price *= (1 + (trend + seasonality + noise) * regime); data.push({ timestamp: baseTime + i * 3600000, open: price * (1 - Math.random() * 0.005), high: price * (1 + Math.random() * 0.01), low: price * (1 - Math.random() * 0.01), close: price, volume: 1000000 + Math.random() * 5000000 }); } return data; } // Feature engineering pipeline function engineerFeatures(data, config) { const features = []; for (let i = 50; i < data.length; i++) { const window = data.slice(i - 50, i + 1); const feature = new Float32Array(config.model.inputSize); let idx = 0; if (config.features.price) { // Price returns (20 features) for (let j = 1; j <= 20 && idx < config.model.inputSize; j++) { feature[idx++] = (window[window.length - j].close - window[window.length - j - 1].close) / window[window.length - j - 1].close; } // Price ratios (10 features) const latestPrice = window[window.length - 1].close; for (let j of [5, 10, 20, 30, 40, 50]) { if (idx < config.model.inputSize && window.length > j) { feature[idx++] = latestPrice / window[window.length - 1 - j].close - 1; } } } if (config.features.volume) { // Volume changes (10 features) for (let j = 1; j <= 10 && idx < config.model.inputSize; j++) { const curr = window[window.length - j].volume; const prev = window[window.length - j - 1].volume; feature[idx++] = Math.log(curr / prev); } } if (config.features.technicals) { // RSI const rsi = calculateRSI(window.map(d => d.close), 14); feature[idx++] = (rsi - 50) / 50; // Normalize to [-1, 1] // MACD const macd = calculateMACD(window.map(d => d.close)); feature[idx++] = macd.histogram / window[window.length - 1].close; // Bollinger position const bb = calculateBollingerBands(window.map(d => d.close), 20, 2); const bbPosition = (window[window.length - 1].close - bb.lower) / (bb.upper - bb.lower); feature[idx++] = bbPosition * 2 - 1; // ATR const atr = calculateATR(window, 14); feature[idx++] = atr / window[window.length - 1].close; } // Fill remaining with zeros or noise while (idx < config.model.inputSize) { feature[idx++] = (Math.random() - 0.5) * 0.01; } features.push({ feature, target: i < data.length - 5 ? (data[i + 5].close - data[i].close) / data[i].close : 0, timestamp: data[i].timestamp, price: data[i].close }); } return features; } // Create sequences for LSTM function createSequences(features, config) { const X = []; const y = []; const dates = []; for (let i = config.lookback; i < features.length - config.horizon; i++) { // Input sequence const sequence = []; for (let j = 0; j < config.lookback; j++) { sequence.push(Array.from(features[i - config.lookback + j].feature)); } X.push(sequence); // Target sequence (future returns) const targets = []; for (let h = 1; h <= config.horizon; h++) { targets.push(features[i + h].target); } y.push(targets); dates.push(features[i].timestamp); } return { X, y, dates }; } // Train model (simulation) async function trainModel(trainX, trainY, valX, valY, config) { const history = { epochs: [], bestEpoch: 0, bestValLoss: Infinity, earlyStopped: false, predictions: [], totalTime: 0 }; const startTime = performance.now(); let patience = config.training.earlyStoppingPatience; for (let epoch = 0; epoch < config.training.epochs; epoch++) { const epochStart = performance.now(); // Simulate training loss (decreasing with noise) const trainLoss = 0.05 * Math.exp(-epoch / 30) + 0.002 + Math.random() * 0.005; // Simulate validation loss (decreasing then overfitting) const valLoss = 0.05 * Math.exp(-epoch / 25) + 0.003 + Math.random() * 0.003 + Math.max(0, (epoch - 50) * 0.0005); const valMae = valLoss * 2; const epochTime = performance.now() - epochStart + 50; // Add simulated compute time history.epochs.push({ epoch, trainLoss, valLoss, valMae, time: epochTime }); // Early stopping if (valLoss < history.bestValLoss) { history.bestValLoss = valLoss; history.bestEpoch = epoch; patience = config.training.earlyStoppingPatience; } else { patience--; if (patience <= 0) { history.earlyStopped = true; break; } } } // Generate predictions (simulated) history.predictions = valY.map(target => { return target.map(t => t + (Math.random() - 0.5) * 0.01); }); history.totalTime = performance.now() - startTime; return history; } // Evaluate model function evaluateModel(X, y, predictions) { let maeSum = 0; let mseSum = 0; let ssRes = 0; let ssTot = 0; let correctDir = 0; let total = 0; const yMean = y.flat().reduce((a, b) => a + b, 0) / y.flat().length; for (let i = 0; i < y.length; i++) { for (let j = 0; j < y[i].length; j++) { const actual = y[i][j]; const predicted = predictions[i][j]; maeSum += Math.abs(actual - predicted); mseSum += Math.pow(actual - predicted, 2); ssRes += Math.pow(actual - predicted, 2); ssTot += Math.pow(actual - yMean, 2); if ((actual > 0) === (predicted > 0)) correctDir++; total++; } } return { mae: maeSum / total, rmse: Math.sqrt(mseSum / total), r2: 1 - ssRes / ssTot, directionAccuracy: correctDir / total }; } // Evaluate specific horizon function evaluateHorizon(y, predictions, horizon) { let maeSum = 0; let correctDir = 0; let hits = 0; for (let i = 0; i < y.length; i++) { const actual = y[i][horizon - 1]; const predicted = predictions[i][horizon - 1]; maeSum += Math.abs(actual - predicted); if ((actual > 0) === (predicted > 0)) correctDir++; if (Math.abs(actual - predicted) < 0.005) hits++; } return { mae: maeSum / y.length, direction: correctDir / y.length, hitRate: hits / y.length }; } // Simulate trading with predictions function simulateTrading(y, predictions, marketData) { let capital = 10000; const returns = []; let wins = 0; let losses = 0; let grossProfit = 0; let grossLoss = 0; let peak = capital; let maxDD = 0; for (let i = 0; i < y.length; i++) { const predicted = predictions[i][0]; // Next-step prediction // Trade based on prediction if (Math.abs(predicted) > 0.002) { // Threshold const direction = predicted > 0 ? 1 : -1; const actualReturn = y[i][0]; const tradeReturn = direction * actualReturn * 0.95; // 5% friction capital *= (1 + tradeReturn); returns.push(tradeReturn); if (tradeReturn > 0) { wins++; grossProfit += tradeReturn; } else { losses++; grossLoss += Math.abs(tradeReturn); } peak = Math.max(peak, capital); maxDD = Math.max(maxDD, (peak - capital) / peak); } } const avgReturn = returns.length > 0 ? returns.reduce((a, b) => a + b, 0) / returns.length : 0; const stdReturn = returns.length > 0 ? Math.sqrt(returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length) : 1; return { totalReturn: (capital - 10000) / 10000, sharpe: stdReturn > 0 ? (avgReturn * Math.sqrt(252)) / (stdReturn * Math.sqrt(252)) : 0, winRate: returns.length > 0 ? wins / (wins + losses) : 0, profitFactor: grossLoss > 0 ? grossProfit / grossLoss : grossProfit > 0 ? Infinity : 0, maxDrawdown: maxDD }; } // Store patterns for RuVector function storePatterns(X, predictions, y) { let highConfidence = 0; let totalConfidence = 0; for (let i = 0; i < predictions.length; i++) { const confidence = 1 - Math.abs(predictions[i][0] - y[i][0]) * 10; totalConfidence += Math.max(0, confidence); if (confidence > 0.7) highConfidence++; } return { count: predictions.length, highConfidence, avgConfidence: totalConfidence / predictions.length }; } // Calculate model parameters function calculateModelParams(model) { const inputSize = model.inputSize; const hiddenSize = model.hiddenSize; const numLayers = model.numLayers; // LSTM: 4 * (input * hidden + hidden * hidden + hidden) per layer const lstmParams = numLayers * 4 * (inputSize * hiddenSize + hiddenSize * hiddenSize + hiddenSize); const outputParams = hiddenSize * 5 + 5; // Final dense layer return lstmParams + outputParams; } // Technical indicator helpers function calculateRSI(prices, period) { const gains = []; const losses = []; for (let i = 1; i < prices.length; i++) { const change = prices[i] - prices[i - 1]; gains.push(change > 0 ? change : 0); losses.push(change < 0 ? -change : 0); } const avgGain = gains.slice(-period).reduce((a, b) => a + b, 0) / period; const avgLoss = losses.slice(-period).reduce((a, b) => a + b, 0) / period; return avgLoss === 0 ? 100 : 100 - (100 / (1 + avgGain / avgLoss)); } function calculateMACD(prices) { const ema12 = prices.slice(-12).reduce((a, b) => a + b, 0) / 12; const ema26 = prices.slice(-26).reduce((a, b) => a + b, 0) / 26; return { macd: ema12 - ema26, histogram: (ema12 - ema26) * 0.5 }; } function calculateBollingerBands(prices, period, stdDev) { const slice = prices.slice(-period); const mean = slice.reduce((a, b) => a + b, 0) / period; const variance = slice.reduce((sum, p) => sum + Math.pow(p - mean, 2), 0) / period; const std = Math.sqrt(variance); return { upper: mean + stdDev * std, middle: mean, lower: mean - stdDev * std }; } function calculateATR(data, period) { const trs = []; for (let i = 1; i < data.length; i++) { const tr = Math.max( data[i].high - data[i].low, Math.abs(data[i].high - data[i - 1].close), Math.abs(data[i].low - data[i - 1].close) ); trs.push(tr); } return trs.slice(-period).reduce((a, b) => a + b, 0) / period; } // Run the example main().catch(console.error);