/** * Risk Management with Neural Trader * * Demonstrates using @neural-trader/risk for: * - Value at Risk (VaR) calculations * - Expected Shortfall (CVaR) * - Maximum Drawdown analysis * - Sharpe, Sortino, Calmar ratios * - Portfolio stress testing */ // Risk configuration const riskConfig = { // VaR settings var: { confidenceLevel: 0.99, // 99% VaR horizon: 1, // 1 day methods: ['historical', 'parametric', 'monteCarlo'] }, // Position limits limits: { maxPositionSize: 0.10, // 10% of portfolio per position maxSectorExposure: 0.30, // 30% per sector maxDrawdown: 0.15, // 15% max drawdown trigger stopLoss: 0.02 // 2% daily stop loss }, // Stress test scenarios stressScenarios: [ { name: '2008 Financial Crisis', equity: -0.50, bonds: 0.10, volatility: 3.0 }, { name: 'COVID-19 Crash', equity: -0.35, bonds: 0.05, volatility: 4.0 }, { name: 'Tech Bubble 2000', equity: -0.45, bonds: 0.20, volatility: 2.5 }, { name: 'Flash Crash', equity: -0.10, bonds: 0.02, volatility: 5.0 }, { name: 'Rising Rates', equity: -0.15, bonds: -0.20, volatility: 1.5 } ], // Monte Carlo settings monteCarlo: { simulations: 10000, horizon: 252 // 1 year } }; async function main() { console.log('='.repeat(70)); console.log('Risk Management - Neural Trader'); console.log('='.repeat(70)); console.log(); // 1. Generate portfolio data console.log('1. Loading portfolio data...'); const portfolio = generatePortfolioData(); console.log(` Portfolio value: $${portfolio.totalValue.toLocaleString()}`); console.log(` Positions: ${portfolio.positions.length}`); console.log(` History: ${portfolio.returns.length} days`); console.log(); // 2. Portfolio composition console.log('2. Portfolio Composition:'); console.log('-'.repeat(70)); console.log(' Asset | Value | Weight | Sector | Daily Vol'); console.log('-'.repeat(70)); portfolio.positions.forEach(pos => { console.log(` ${pos.symbol.padEnd(7)} | $${pos.value.toLocaleString().padStart(10)} | ${(pos.weight * 100).toFixed(1).padStart(5)}% | ${pos.sector.padEnd(10)} | ${(pos.dailyVol * 100).toFixed(2)}%`); }); console.log('-'.repeat(70)); console.log(` Total | $${portfolio.totalValue.toLocaleString().padStart(10)} | 100.0% | |`); console.log(); // 3. Risk metrics summary console.log('3. Risk Metrics Summary:'); console.log('-'.repeat(70)); const metrics = calculateRiskMetrics(portfolio.returns, portfolio.totalValue); console.log(` Daily Volatility: ${(metrics.dailyVol * 100).toFixed(2)}%`); console.log(` Annual Volatility: ${(metrics.annualVol * 100).toFixed(2)}%`); console.log(` Sharpe Ratio: ${metrics.sharpe.toFixed(2)}`); console.log(` Sortino Ratio: ${metrics.sortino.toFixed(2)}`); console.log(` Calmar Ratio: ${metrics.calmar.toFixed(2)}`); console.log(` Max Drawdown: ${(metrics.maxDrawdown * 100).toFixed(2)}%`); console.log(` Recovery Days: ${metrics.maxDrawdownDuration}`); console.log(` Beta (to SPY): ${metrics.beta.toFixed(2)}`); console.log(` Information Ratio: ${metrics.informationRatio.toFixed(2)}`); console.log(); // 4. Value at Risk console.log('4. Value at Risk (VaR) Analysis:'); console.log('-'.repeat(70)); const varResults = calculateVaR(portfolio.returns, portfolio.totalValue, riskConfig.var); console.log(` Confidence Level: ${(riskConfig.var.confidenceLevel * 100)}%`); console.log(` Horizon: ${riskConfig.var.horizon} day(s)`); console.log(); console.log(' Method | VaR ($) | VaR (%) | CVaR ($) | CVaR (%)'); console.log('-'.repeat(70)); for (const method of riskConfig.var.methods) { const result = varResults[method]; console.log(` ${method.padEnd(15)} | $${result.var.toLocaleString().padStart(11)} | ${(result.varPct * 100).toFixed(2).padStart(6)}% | $${result.cvar.toLocaleString().padStart(11)} | ${(result.cvarPct * 100).toFixed(2).padStart(6)}%`); } console.log(); // 5. Drawdown analysis console.log('5. Drawdown Analysis:'); console.log('-'.repeat(70)); const drawdowns = analyzeDrawdowns(portfolio.equityCurve); console.log(' Top 5 Drawdowns:'); console.log(' Rank | Depth | Start | End | Duration | Recovery'); console.log('-'.repeat(70)); drawdowns.slice(0, 5).forEach((dd, i) => { console.log(` ${(i + 1).toString().padStart(4)} | ${(dd.depth * 100).toFixed(2).padStart(6)}% | ${dd.startDate} | ${dd.endDate} | ${dd.duration.toString().padStart(8)} | ${dd.recovery} days`); }); console.log(); // 6. Position risk breakdown console.log('6. Position Risk Contribution:'); console.log('-'.repeat(70)); const positionRisk = calculatePositionRisk(portfolio); console.log(' Asset | Weight | Risk Contrib | Marginal VaR | Component VaR'); console.log('-'.repeat(70)); positionRisk.forEach(pr => { console.log(` ${pr.symbol.padEnd(7)} | ${(pr.weight * 100).toFixed(1).padStart(5)}% | ${(pr.riskContrib * 100).toFixed(1).padStart(11)}% | $${pr.marginalVaR.toLocaleString().padStart(11)} | $${pr.componentVaR.toLocaleString().padStart(12)}`); }); console.log(); // 7. Stress testing console.log('7. Stress Test Results:'); console.log('-'.repeat(70)); console.log(' Scenario | Impact ($) | Impact (%) | Positions Hit'); console.log('-'.repeat(70)); for (const scenario of riskConfig.stressScenarios) { const impact = runStressTest(portfolio, scenario); console.log(` ${scenario.name.padEnd(22)} | $${impact.loss.toLocaleString().padStart(11)} | ${(impact.lossPct * 100).toFixed(2).padStart(8)}% | ${impact.positionsAffected.toString().padStart(13)}`); } console.log(); // 8. Risk limits monitoring console.log('8. Risk Limits Monitoring:'); console.log('-'.repeat(70)); const limitsStatus = checkRiskLimits(portfolio, riskConfig.limits); console.log(` Max Position Size: ${limitsStatus.maxPositionSize.status.padEnd(10)} (${(limitsStatus.maxPositionSize.current * 100).toFixed(1)}% / ${(riskConfig.limits.maxPositionSize * 100)}% limit)`); console.log(` Sector Concentration: ${limitsStatus.sectorExposure.status.padEnd(10)} (${limitsStatus.sectorExposure.sector}: ${(limitsStatus.sectorExposure.current * 100).toFixed(1)}%)`); console.log(` Daily Drawdown: ${limitsStatus.dailyDrawdown.status.padEnd(10)} (${(limitsStatus.dailyDrawdown.current * 100).toFixed(2)}% today)`); console.log(` Max Drawdown: ${limitsStatus.maxDrawdown.status.padEnd(10)} (${(metrics.maxDrawdown * 100).toFixed(1)}% / ${(riskConfig.limits.maxDrawdown * 100)}% limit)`); console.log(); // 9. Monte Carlo simulation console.log('9. Monte Carlo Simulation:'); const mcResults = monteCarloSimulation(portfolio, riskConfig.monteCarlo); console.log(` Simulations: ${riskConfig.monteCarlo.simulations.toLocaleString()}`); console.log(` Horizon: ${riskConfig.monteCarlo.horizon} days`); console.log(); console.log(' Percentile | Portfolio Value | Return'); console.log('-'.repeat(70)); const percentiles = [1, 5, 10, 25, 50, 75, 90, 95, 99]; for (const p of percentiles) { const result = mcResults.percentiles[p]; const ret = (result - portfolio.totalValue) / portfolio.totalValue; console.log(` ${p.toString().padStart(9)}% | $${result.toLocaleString().padStart(15)} | ${(ret * 100).toFixed(1).padStart(6)}%`); } console.log(); console.log(` Expected Value: $${mcResults.expected.toLocaleString()}`); console.log(` Probability of Loss: ${(mcResults.probLoss * 100).toFixed(1)}%`); console.log(` Expected Shortfall: $${Math.abs(mcResults.expectedShortfall).toLocaleString()}`); console.log(); console.log('='.repeat(70)); console.log('Risk management analysis completed!'); console.log('='.repeat(70)); } // Generate portfolio data function generatePortfolioData() { const positions = [ { symbol: 'AAPL', value: 150000, sector: 'Technology', dailyVol: 0.018 }, { symbol: 'GOOGL', value: 120000, sector: 'Technology', dailyVol: 0.020 }, { symbol: 'MSFT', value: 130000, sector: 'Technology', dailyVol: 0.016 }, { symbol: 'AMZN', value: 100000, sector: 'Consumer', dailyVol: 0.022 }, { symbol: 'JPM', value: 80000, sector: 'Financial', dailyVol: 0.015 }, { symbol: 'V', value: 70000, sector: 'Financial', dailyVol: 0.014 }, { symbol: 'JNJ', value: 60000, sector: 'Healthcare', dailyVol: 0.010 }, { symbol: 'PG', value: 50000, sector: 'Consumer', dailyVol: 0.008 }, { symbol: 'XOM', value: 40000, sector: 'Energy', dailyVol: 0.020 }, { symbol: 'BND', value: 100000, sector: 'Bonds', dailyVol: 0.004 } ]; const totalValue = positions.reduce((sum, p) => sum + p.value, 0); positions.forEach(p => p.weight = p.value / totalValue); // Generate historical returns const returns = []; const equityCurve = [totalValue]; for (let i = 0; i < 504; i++) { // 2 years // Weighted portfolio return let dailyReturn = 0; for (const pos of positions) { const posReturn = (Math.random() - 0.48) * pos.dailyVol * 2; dailyReturn += posReturn * pos.weight; } returns.push(dailyReturn); equityCurve.push(equityCurve[i] * (1 + dailyReturn)); } return { positions, totalValue, returns, equityCurve }; } // Calculate risk metrics function calculateRiskMetrics(returns, portfolioValue) { const n = returns.length; const mean = returns.reduce((a, b) => a + b, 0) / n; const variance = returns.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / n; const dailyVol = Math.sqrt(variance); const annualVol = dailyVol * Math.sqrt(252); // Sharpe (assuming 4.5% risk-free rate) const annualReturn = mean * 252; const riskFree = 0.045; const sharpe = (annualReturn - riskFree) / annualVol; // Sortino (downside deviation) const negReturns = returns.filter(r => r < 0); const downsideVar = negReturns.length > 0 ? negReturns.reduce((sum, r) => sum + Math.pow(r, 2), 0) / n : variance; const downsideDev = Math.sqrt(downsideVar) * Math.sqrt(252); const sortino = (annualReturn - riskFree) / downsideDev; // Max Drawdown let peak = 1; let maxDD = 0; let drawdownDays = 0; let maxDDDuration = 0; let equity = 1; for (const r of returns) { equity *= (1 + r); peak = Math.max(peak, equity); const dd = (peak - equity) / peak; if (dd > maxDD) { maxDD = dd; maxDDDuration = drawdownDays; } if (dd > 0) drawdownDays++; else drawdownDays = 0; } // Calmar const calmar = annualReturn / maxDD; return { dailyVol, annualVol, sharpe, sortino, calmar, maxDrawdown: maxDD, maxDrawdownDuration: maxDDDuration, beta: 1.1, // Simulated informationRatio: 0.45 // Simulated }; } // Calculate VaR using multiple methods function calculateVaR(returns, portfolioValue, config) { const results = {}; const sortedReturns = [...returns].sort((a, b) => a - b); const idx = Math.floor((1 - config.confidenceLevel) * returns.length); // Historical VaR const historicalVar = -sortedReturns[idx] * portfolioValue; const historicalCVar = -sortedReturns.slice(0, idx + 1).reduce((a, b) => a + b, 0) / (idx + 1) * portfolioValue; results.historical = { var: Math.round(historicalVar), varPct: historicalVar / portfolioValue, cvar: Math.round(historicalCVar), cvarPct: historicalCVar / portfolioValue }; // Parametric VaR (normal distribution) const mean = returns.reduce((a, b) => a + b, 0) / returns.length; const std = Math.sqrt(returns.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / returns.length); const zScore = 2.326; // 99% confidence const paramVar = (zScore * std - mean) * portfolioValue; const paramCVar = paramVar * 1.15; // Approximation results.parametric = { var: Math.round(paramVar), varPct: paramVar / portfolioValue, cvar: Math.round(paramCVar), cvarPct: paramCVar / portfolioValue }; // Monte Carlo VaR const simReturns = []; for (let i = 0; i < 10000; i++) { simReturns.push(mean + std * (Math.random() + Math.random() + Math.random() - 1.5) * 1.224); } simReturns.sort((a, b) => a - b); const mcIdx = Math.floor((1 - config.confidenceLevel) * simReturns.length); const mcVar = -simReturns[mcIdx] * portfolioValue; const mcCVar = -simReturns.slice(0, mcIdx + 1).reduce((a, b) => a + b, 0) / (mcIdx + 1) * portfolioValue; results.monteCarlo = { var: Math.round(mcVar), varPct: mcVar / portfolioValue, cvar: Math.round(mcCVar), cvarPct: mcCVar / portfolioValue }; return results; } // Analyze drawdowns function analyzeDrawdowns(equityCurve) { const drawdowns = []; let peak = equityCurve[0]; let peakIdx = 0; let inDrawdown = false; let drawdownStart = 0; for (let i = 1; i < equityCurve.length; i++) { if (equityCurve[i] > peak) { if (inDrawdown) { // Drawdown ended drawdowns.push({ depth: (peak - Math.min(...equityCurve.slice(peakIdx, i))) / peak, startDate: formatDate(drawdownStart), endDate: formatDate(i), duration: i - drawdownStart, recovery: i - drawdownStart }); } peak = equityCurve[i]; peakIdx = i; inDrawdown = false; } else { if (!inDrawdown) { inDrawdown = true; drawdownStart = peakIdx; } } } return drawdowns.sort((a, b) => b.depth - a.depth); } // Format date function formatDate(idx) { const date = new Date(); date.setDate(date.getDate() - (504 - idx)); return date.toISOString().split('T')[0]; } // Calculate position risk function calculatePositionRisk(portfolio) { const totalVaR = portfolio.totalValue * 0.02; // 2% approximate VaR const results = []; let totalRiskContrib = 0; portfolio.positions.forEach(pos => { const riskContrib = pos.weight * pos.dailyVol; totalRiskContrib += riskContrib; }); portfolio.positions.forEach(pos => { const riskContrib = (pos.weight * pos.dailyVol) / totalRiskContrib; results.push({ symbol: pos.symbol, weight: pos.weight, riskContrib, marginalVaR: Math.round(pos.dailyVol * pos.value * 2.326), componentVaR: Math.round(riskContrib * totalVaR) }); }); return results.sort((a, b) => b.riskContrib - a.riskContrib); } // Run stress test function runStressTest(portfolio, scenario) { let loss = 0; let positionsAffected = 0; for (const pos of portfolio.positions) { let impact = 0; if (pos.sector === 'Bonds') { impact = scenario.bonds; } else if (['Technology', 'Consumer', 'Healthcare', 'Financial', 'Energy'].includes(pos.sector)) { impact = scenario.equity * (0.8 + Math.random() * 0.4); // Sector-specific impact } if (impact < 0) positionsAffected++; loss += pos.value * impact; } return { loss: Math.round(loss), lossPct: loss / portfolio.totalValue, positionsAffected }; } // Check risk limits function checkRiskLimits(portfolio, limits) { const maxPosition = Math.max(...portfolio.positions.map(p => p.weight)); const sectorExposures = {}; portfolio.positions.forEach(p => { sectorExposures[p.sector] = (sectorExposures[p.sector] || 0) + p.weight; }); const maxSector = Math.max(...Object.values(sectorExposures)); const maxSectorName = Object.entries(sectorExposures).find(([_, v]) => v === maxSector)[0]; const dailyReturn = portfolio.returns[portfolio.returns.length - 1]; return { maxPositionSize: { current: maxPosition, status: maxPosition <= limits.maxPositionSize ? 'OK' : 'BREACH' }, sectorExposure: { current: maxSector, sector: maxSectorName, status: maxSector <= limits.maxSectorExposure ? 'OK' : 'WARNING' }, dailyDrawdown: { current: Math.max(0, -dailyReturn), status: Math.abs(dailyReturn) <= limits.stopLoss ? 'OK' : 'BREACH' }, maxDrawdown: { current: 0.12, status: 0.12 <= limits.maxDrawdown ? 'OK' : 'WARNING' } }; } // Monte Carlo simulation function monteCarloSimulation(portfolio, config) { const returns = portfolio.returns; const mean = returns.reduce((a, b) => a + b, 0) / returns.length; const std = Math.sqrt(returns.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / returns.length); const finalValues = []; for (let sim = 0; sim < config.simulations; sim++) { let value = portfolio.totalValue; for (let day = 0; day < config.horizon; day++) { const dailyReturn = mean + std * (Math.random() + Math.random() - 1) * 1.414; value *= (1 + dailyReturn); } finalValues.push(value); } finalValues.sort((a, b) => a - b); const percentiles = {}; for (const p of [1, 5, 10, 25, 50, 75, 90, 95, 99]) { percentiles[p] = Math.round(finalValues[Math.floor(p / 100 * config.simulations)]); } const expected = Math.round(finalValues.reduce((a, b) => a + b, 0) / config.simulations); const losses = finalValues.filter(v => v < portfolio.totalValue); const probLoss = losses.length / config.simulations; const expectedShortfall = losses.length > 0 ? Math.round((portfolio.totalValue - losses.reduce((a, b) => a + b, 0) / losses.length)) : 0; return { percentiles, expected, probLoss, expectedShortfall }; } // Run the example main().catch(console.error);