01
Financial Portfolio Risk Analysis
Builds a synthetic eight-asset portfolio and reports its risk: VaR and CVaR, Sharpe and Sortino ratios, drawdown, correlations, optimized weights, a Monte Carlo forecast and a few stress scenarios.
Features
- Portfolio construction from asset return series
- Risk metrics: historical, parametric and Cornish-Fisher VaR, CVaR, Sharpe, Sortino, Calmar, maximum drawdown
- Correlation and covariance matrices, with a Pearson, Spearman and Shapiro-Wilk check on the returns
- Optimization: minimum variance, maximum Sharpe and risk parity weights, plus an efficient frontier. The first two use the inverse of the covariance matrix (
inv), the closed form of Markowitz mean-variance optimization - Monte Carlo simulation of 12-month portfolio returns, using the Cholesky factor of the covariance matrix to correlate the shocks
- Bootstrap confidence interval for the mean return, stress scenarios and a rolling VaR backtest
What you will learn
- Portfolio construction from asset return series
- Risk metrics: historical, parametric and Cornish-Fisher VaR, CVaR, Sharpe, Sortino, Calmar, maximum drawdown
- Correlation and covariance matrices, with a Pearson, Spearman and Shapiro-Wilk check on the returns
- Optimization: minimum variance, maximum Sharpe and risk parity weights, plus an efficient frontier. The first two use the inverse of the covariance matrix (
inv), the closed form of Markowitz mean-variance optimization - Use deepbox/ndarray for
tensor,toArray,item. - Use deepbox/linalg for
inv,cholesky,det,trace.
Project layout
01-financial-risk-analysis/
├── index.ts # Main entry point
├── README.md # This file
├── output/ # Generated SVGs
└── src/
├── portfolio.ts # Portfolio class and synthetic asset data
├── risk-metrics.ts # VaR, CVaR, Sharpe, drawdown, backtest
├── optimization.ts # Mean-variance, risk parity, efficient frontier
└── monte-carlo.ts # Monte Carlo, bootstrap, stress scenariosSource
/** * Financial Portfolio Risk Analysis System * * Analyzes a synthetic eight-asset portfolio and prints a risk report: * - Portfolio construction and management * - Risk metrics (VaR, CVaR, Sharpe, Sortino) * - Mean-variance optimization * - Monte Carlo simulation * - Correlation analysis * - Stress testing * * Deepbox Modules Used: * - deepbox/ndarray: Tensor operations * - deepbox/linalg: Matrix inverse, Cholesky, determinant/trace * - deepbox/stats: Correlation, covariance, statistical measures * - deepbox/dataframe: Data manipulation * - deepbox/plot: Visualization */ import { existsSync, mkdirSync, writeFileSync } from "node:fs";import { DataFrame } from "deepbox/dataframe";import { det, trace } from "deepbox/linalg";import { tensor } from "deepbox/ndarray";import { Figure } from "deepbox/plot";import { chi2, pearsonr, shapiro, spearmanr } from "deepbox/stats";import { bootstrapConfidenceInterval, bootstrapReturns, getHistoricalStressScenarios, runStressTests, simulatePortfolioReturns,} from "./src/monte-carlo";import { generateEfficientFrontier, maxSharpePortfolio, minimumVariancePortfolio, riskParityPortfolio,} from "./src/optimization";import { generateSyntheticAssets, Portfolio } from "./src/portfolio";import { backtestVaR, calculateRiskMetrics, calculateRollingVaR, calculateVaR,} from "./src/risk-metrics"; // ============================================================================// Configuration// ============================================================================ const OUTPUT_DIR = "docs/projects/01-financial-risk-analysis/output";const NUM_PERIODS = 60; // 5 years of monthly dataconst RISK_FREE_RATE = 0.02; // 2% annual risk-free rate // ============================================================================// Main Execution// ============================================================================ console.log("═".repeat(70));console.log(" FINANCIAL PORTFOLIO RISK ANALYSIS SYSTEM");console.log(" Built with Deepbox: TypeScript toolkit for AI & numerical computing");console.log("═".repeat(70)); // Create output directoryif (!existsSync(OUTPUT_DIR)) { mkdirSync(OUTPUT_DIR, { recursive: true });} // ============================================================================// Step 1: Generate Synthetic Asset Data// ============================================================================ console.log("\nSTEP 1: Generating Asset Data");console.log("─".repeat(70)); const assets = generateSyntheticAssets(NUM_PERIODS); console.log(`Generated ${assets.length} assets with ${NUM_PERIODS} periods of data:\n`);const assetSummary = new DataFrame({ Symbol: assets.map((a) => a.symbol), Name: assets.map((a) => a.name), Sector: assets.map((a) => a.sector), "Avg Monthly Return (%)": assets.map((a) => { const avg = a.returns.reduce((s, r) => s + r, 0) / a.returns.length; return (avg * 100).toFixed(3); }), "Volatility (%)": assets.map((a) => { const avg = a.returns.reduce((s, r) => s + r, 0) / a.returns.length; const variance = a.returns.reduce((s, r) => s + (r - avg) ** 2, 0) / a.returns.length; return (Math.sqrt(variance) * 100).toFixed(3); }),}); console.log(assetSummary.toString()); // ============================================================================// Step 2: Build Initial Portfolio// ============================================================================ console.log("\nSTEP 2: Building Initial Portfolio");console.log("─".repeat(70)); // Start with equal weightsconst equalWeights = Array(assets.length).fill(1 / assets.length);const portfolio = new Portfolio(assets, equalWeights, RISK_FREE_RATE); console.log("\nInitial Equal-Weight Portfolio Allocation:");const portfolioDF = portfolio.toDataFrame();console.log(portfolioDF.toString()); const initialMetrics = portfolio.getMetrics();console.log("\nInitial Portfolio Metrics:");console.log(` Expected Annual Return: ${(initialMetrics.expectedReturn * 100).toFixed(2)}%`);console.log(` Annual Volatility: ${(initialMetrics.volatility * 100).toFixed(2)}%`);console.log(` Sharpe Ratio: ${initialMetrics.sharpeRatio.toFixed(3)}`);console.log(` Sortino Ratio: ${initialMetrics.sortinoRatio.toFixed(3)}`);console.log(` Maximum Drawdown: ${(initialMetrics.maxDrawdown * 100).toFixed(2)}%`); // ============================================================================// Step 3: Risk Analysis// ============================================================================ console.log("\nSTEP 3: Risk Analysis");console.log("─".repeat(70)); const portfolioReturns = portfolio.getPortfolioReturns();const returnsArray = portfolioReturns.toArray() as number[]; const riskMetrics = calculateRiskMetrics(returnsArray); console.log("\nValue at Risk (VaR):");console.log(` 95% VaR (Historical): ${(riskMetrics.var95 * 100).toFixed(2)}%`);console.log(` 99% VaR (Historical): ${(riskMetrics.var99 * 100).toFixed(2)}%`); console.log("\nConditional Value at Risk (CVaR / Expected Shortfall):");console.log(` 95% CVaR: ${(riskMetrics.cvar95 * 100).toFixed(2)}%`);console.log(` 99% CVaR: ${(riskMetrics.cvar99 * 100).toFixed(2)}%`); console.log("\nOther Risk Metrics:");console.log(` Annualized Volatility: ${(riskMetrics.volatility * 100).toFixed(2)}%`);console.log(` Downside Deviation: ${(riskMetrics.downsideDeviation * 100).toFixed(2)}%`);console.log(` Maximum Drawdown: ${(riskMetrics.maxDrawdown * 100).toFixed(2)}%`);console.log(` Calmar Ratio: ${riskMetrics.calmarRatio.toFixed(3)}`); // VaR comparison across methodsconsole.log("\nVaR Method Comparison (95% Confidence):");console.log( ` Historical: ${(calculateVaR(returnsArray, 0.95, "historical") * 100).toFixed(2)}%`);console.log( ` Parametric: ${(calculateVaR(returnsArray, 0.95, "parametric") * 100).toFixed(2)}%`);console.log( ` Cornish-Fisher: ${(calculateVaR(returnsArray, 0.95, "cornish-fisher") * 100).toFixed(2)}%`); // ============================================================================// Step 4: Correlation Analysis// ============================================================================ console.log("\nSTEP 4: Correlation Analysis");console.log("─".repeat(70)); const corrMatrix = portfolio.getCorrelationMatrix();const covMatrix = portfolio.getCovarianceMatrix(); console.log("\nCorrelation Matrix:");const symbols = portfolio.getSymbols();const corrRows = corrMatrix.toArray() as number[][];const corrData: { [key: string]: number[] } = {};symbols.forEach((symbol, i) => { corrData[symbol] = corrRows[i].map((value) => Number(value.toFixed(3)));});const corrDF = new DataFrame(corrData);console.log(corrDF.toString()); // Covariance matrix propertiesconsole.log("\nCovariance Matrix Properties:");console.log(` Determinant: ${det(covMatrix).toExponential(4)}`);console.log(` Trace: ${Number(trace(covMatrix).item()).toFixed(6)}`); // Statistical tests on returnsconsole.log("\nStatistical Tests (First Asset vs Last Asset):");const asset1Returns = tensor(assets[0]?.returns);const asset8Returns = tensor(assets[7]?.returns); const [pearsonCorr, pearsonP] = pearsonr(asset1Returns, asset8Returns);const [spearmanCorr, spearmanP] = spearmanr(asset1Returns, asset8Returns); console.log(` Pearson Correlation: ${pearsonCorr.toFixed(4)} (p-value: ${pearsonP.toFixed(4)})`);console.log( ` Spearman Correlation: ${spearmanCorr.toFixed(4)} (p-value: ${spearmanP.toFixed(4)})`); // Normality testconst shapiroResult = shapiro(asset1Returns);console.log( ` Shapiro-Wilk Test (TECH): W=${shapiroResult.statistic.toFixed( 4 )}, p=${shapiroResult.pvalue.toFixed(4)}`); // ============================================================================// Step 5: Portfolio Optimization// ============================================================================ console.log("\nSTEP 5: Portfolio Optimization");console.log("─".repeat(70)); const expectedReturns = assets.map((a) => a.returns.reduce((s, r) => s + r, 0) / a.returns.length); // Minimum Variance Portfolioconsole.log("\n1. Minimum Variance Portfolio:");const minVarWeights = minimumVariancePortfolio(covMatrix);const minVarPortfolio = new Portfolio(assets, minVarWeights, RISK_FREE_RATE);const minVarMetrics = minVarPortfolio.getMetrics();console.log(` Weights: [${minVarWeights.map((w) => `${(w * 100).toFixed(1)}%`).join(", ")}]`);console.log(` Expected Return: ${(minVarMetrics.expectedReturn * 100).toFixed(2)}%`);console.log(` Volatility: ${(minVarMetrics.volatility * 100).toFixed(2)}%`);console.log(` Sharpe Ratio: ${minVarMetrics.sharpeRatio.toFixed(3)}`); // Maximum Sharpe Portfolioconsole.log("\n2. Maximum Sharpe Ratio Portfolio:");const maxSharpeResult = maxSharpePortfolio(expectedReturns, covMatrix, RISK_FREE_RATE);console.log( ` Weights: [${maxSharpeResult.weights.map((w) => `${(w * 100).toFixed(1)}%`).join(", ")}]`);console.log(` Expected Return: ${(maxSharpeResult.expectedReturn * 100).toFixed(2)}%`);console.log(` Volatility: ${(maxSharpeResult.volatility * 100).toFixed(2)}%`);console.log(` Sharpe Ratio: ${maxSharpeResult.sharpeRatio.toFixed(3)}`); // Risk Parity Portfolioconsole.log("\n3. Risk Parity Portfolio:");const riskParityWeights = riskParityPortfolio(covMatrix);const riskParityPortfolioObj = new Portfolio(assets, riskParityWeights, RISK_FREE_RATE);const riskParityMetrics = riskParityPortfolioObj.getMetrics();console.log(` Weights: [${riskParityWeights.map((w) => `${(w * 100).toFixed(1)}%`).join(", ")}]`);console.log(` Expected Return: ${(riskParityMetrics.expectedReturn * 100).toFixed(2)}%`);console.log(` Volatility: ${(riskParityMetrics.volatility * 100).toFixed(2)}%`);console.log(` Sharpe Ratio: ${riskParityMetrics.sharpeRatio.toFixed(3)}`); // Generate Efficient Frontierconsole.log("\n4. Efficient Frontier (sample points):");const frontier = generateEfficientFrontier(expectedReturns, covMatrix, 10);const frontierDF = new DataFrame({ "Return (%)": frontier.map((p) => (p.return * 100).toFixed(2)), "Volatility (%)": frontier.map((p) => (p.volatility * 100).toFixed(2)),});console.log(frontierDF.toString()); // ============================================================================// Step 6: Monte Carlo Simulation// ============================================================================ console.log("\nSTEP 6: Monte Carlo Simulation");console.log("─".repeat(70)); const optimalWeights = maxSharpeResult.weights;const mcResult = simulatePortfolioReturns( expectedReturns, covMatrix, optimalWeights, 10000, // 10,000 simulations 12, // 12 month horizon 42 // seed); console.log("\nMonte Carlo Simulation Results (10,000 scenarios, 12-month horizon):");console.log(` Mean Return: ${(mcResult.meanReturn * 100).toFixed(2)}%`);console.log(` Median Return: ${(mcResult.medianReturn * 100).toFixed(2)}%`);console.log(` Volatility: ${(mcResult.volatility * 100).toFixed(2)}%`);console.log(` 95% VaR: ${(mcResult.var95 * 100).toFixed(2)}%`);console.log(` 99% VaR: ${(mcResult.var99 * 100).toFixed(2)}%`); console.log("\nReturn Distribution Percentiles:");console.log(` 5th percentile: ${(mcResult.percentile5 * 100).toFixed(2)}%`);console.log(` 25th percentile: ${(mcResult.percentile25 * 100).toFixed(2)}%`);console.log(` 75th percentile: ${(mcResult.percentile75 * 100).toFixed(2)}%`);console.log(` 95th percentile: ${(mcResult.percentile95 * 100).toFixed(2)}%`); // Bootstrap confidence interval for Sharpe ratioconsole.log("\nBootstrap Confidence Interval for Mean Return:");const bootstrapMeans = bootstrapReturns( returnsArray, 1000, (data) => data.reduce((a, b) => a + b, 0) / data.length, 42);const ci = bootstrapConfidenceInterval(bootstrapMeans, 0.95);console.log(` 95% CI: [${(ci.lower * 100).toFixed(3)}%, ${(ci.upper * 100).toFixed(3)}%]`);console.log(` Bootstrap Mean: ${(ci.mean * 100).toFixed(3)}%`); // ============================================================================// Step 7: Stress Testing// ============================================================================ console.log("\nSTEP 7: Stress Testing");console.log("─".repeat(70)); const stressScenarios = getHistoricalStressScenarios();const stressResults = runStressTests(optimalWeights, expectedReturns, stressScenarios); console.log("\nStress Test Results (Optimal Portfolio):\n");const stressDF = new DataFrame({ Scenario: stressResults.map((r) => r.scenario), "Portfolio Impact (%)": stressResults.map((r) => (r.portfolioImpact * 100).toFixed(2)),});console.log(stressDF.toString()); // ============================================================================// Step 8: VaR Backtesting// ============================================================================ console.log("\nSTEP 8: VaR Backtesting");console.log("─".repeat(70)); // Calculate rolling VaRconst rollingVaR = calculateRollingVaR(returnsArray, 20, 0.95); // Backtest against actual returns (offset by window size)const backtestReturns = returnsArray.slice(20);const backtestResult = backtestVaR(backtestReturns, rollingVaR.slice(0, backtestReturns.length)); console.log("\nVaR Backtesting Results:");console.log(` Number of Periods: ${backtestReturns.length}`);console.log(` VaR Exceedances: ${backtestResult.exceedances}`);console.log(` Exceedance Rate: ${(backtestResult.rate * 100).toFixed(2)}%`);console.log(` Expected Rate (95%): ${(backtestResult.expected * 100).toFixed(2)}%`);// Kupiec's proportion-of-failures test: a likelihood-ratio test of whether the// observed exceedance rate is consistent with the expected 5%. A fixed cutoff// would ignore how few periods there are; the test accounts for sample size.const nPeriods = backtestReturns.length;const nExceed = backtestResult.exceedances;const pExpected = backtestResult.expected;const pObserved = nExceed / nPeriods;const logLik = (rate: number) => (nPeriods - nExceed) * Math.log(1 - rate) + (nExceed > 0 ? nExceed * Math.log(rate) : 0);const kupiecLR = nExceed === 0 || nExceed === nPeriods ? 0 : -2 * (logLik(pExpected) - logLik(pObserved));const kupiecP = chi2(1).sf(kupiecLR);console.log(` Kupiec POF statistic: ${kupiecLR.toFixed(3)} (p-value ${kupiecP.toFixed(3)})`);console.log( ` ${kupiecP >= 0.05 ? "The exceedance rate is consistent with the 95% VaR model" : "The exceedance rate is not consistent with the 95% VaR model"} at the 5% level.`); // ============================================================================// Step 9: Generate Visualizations// ============================================================================ console.log("\nSTEP 9: Generating Visualizations");console.log("─".repeat(70)); // 1. Efficient Frontier Plottry { const frontierFig = new Figure({ width: 800, height: 600 }); const frontierAx = frontierFig.addAxes(); const frontierReturns = frontier.map((p) => p.return * 100); const frontierVols = frontier.map((p) => p.volatility * 100); frontierAx.plot(tensor(frontierVols), tensor(frontierReturns), { color: "#2196F3", linewidth: 2, }); // Mark special portfolios frontierAx.scatter( tensor([minVarMetrics.volatility * 100]), tensor([minVarMetrics.expectedReturn * 100]), { color: "#4CAF50", size: 12 } ); frontierAx.scatter( tensor([maxSharpeResult.volatility * 100]), tensor([maxSharpeResult.expectedReturn * 100]), { color: "#FF5722", size: 12 } ); frontierAx.setTitle("Efficient Frontier"); frontierAx.setXLabel("Volatility (%)"); frontierAx.setYLabel("Expected Return (%)"); const frontierSvg = frontierFig.renderSVG(); writeFileSync(`${OUTPUT_DIR}/efficient-frontier.svg`, frontierSvg.svg); console.log(` Saved: ${OUTPUT_DIR}/efficient-frontier.svg`);} catch (e) { console.log(` Warning: could not generate efficient frontier plot: ${e}`);} // 2. Monte Carlo Distribution Plottry { const mcFig = new Figure({ width: 800, height: 600 }); const mcAx = mcFig.addAxes(); // Create histogram data const numBins = 50; const minReturn = Math.min(...mcResult.scenarios); const maxReturn = Math.max(...mcResult.scenarios); const binWidth = (maxReturn - minReturn) / numBins; const bins: number[] = []; const counts: number[] = []; for (let i = 0; i < numBins; i++) { const binStart = minReturn + i * binWidth; const binEnd = binStart + binWidth; const binCenter = (binStart + binEnd) / 2; const count = mcResult.scenarios.filter((s) => s >= binStart && s < binEnd).length; bins.push(binCenter * 100); counts.push(count); } mcAx.bar(tensor(bins), tensor(counts), { color: "#9C27B0" }); mcAx.setTitle("Monte Carlo Return Distribution"); mcAx.setXLabel("Return (%)"); mcAx.setYLabel("Frequency"); const mcSvg = mcFig.renderSVG(); writeFileSync(`${OUTPUT_DIR}/monte-carlo-distribution.svg`, mcSvg.svg); console.log(` Saved: ${OUTPUT_DIR}/monte-carlo-distribution.svg`);} catch (e) { console.log(` Warning: could not generate Monte Carlo plot: ${e}`);} // 3. Correlation Heatmaptry { const heatFig = new Figure({ width: 800, height: 700 }); const heatAx = heatFig.addAxes(); heatAx.heatmap(corrMatrix); heatAx.setTitle("Asset Correlation Matrix"); const heatSvg = heatFig.renderSVG(); writeFileSync(`${OUTPUT_DIR}/correlation-heatmap.svg`, heatSvg.svg); console.log(` Saved: ${OUTPUT_DIR}/correlation-heatmap.svg`);} catch (e) { console.log(` Warning: could not generate correlation heatmap: ${e}`);} // ============================================================================// Step 10: Final Summary// ============================================================================ console.log(`\n${"═".repeat(70)}`);console.log(" ANALYSIS COMPLETE - SUMMARY");console.log("═".repeat(70)); console.log("\nKey Findings:\n");console.log(" 1. Portfolio Comparison:");console.log(` • Equal-Weight Sharpe: ${initialMetrics.sharpeRatio.toFixed(3)}`);console.log(` • Min-Variance Sharpe: ${minVarMetrics.sharpeRatio.toFixed(3)}`);console.log(` • Max-Sharpe Sharpe: ${maxSharpeResult.sharpeRatio.toFixed(3)}`);console.log(` • Risk-Parity Sharpe: ${riskParityMetrics.sharpeRatio.toFixed(3)}`); console.log("\n 2. Risk Assessment:");console.log(` • 95% VaR: ${(riskMetrics.var95 * 100).toFixed(2)}%`);console.log(` • 95% CVaR: ${(riskMetrics.cvar95 * 100).toFixed(2)}%`);console.log(` • Maximum Drawdown: ${(riskMetrics.maxDrawdown * 100).toFixed(2)}%`); console.log("\n 3. Monte Carlo Insights:");console.log(` • Expected 12M Return: ${(mcResult.meanReturn * 100).toFixed(2)}%`);console.log(` • Worst Case (5%): ${(mcResult.percentile5 * 100).toFixed(2)}%`);console.log(` • Best Case (95%): ${(mcResult.percentile95 * 100).toFixed(2)}%`); console.log("\n 4. Stress Test Worst Cases:");const worstStress = stressResults.reduce((worst, r) => r.portfolioImpact < worst.portfolioImpact ? r : worst);console.log(` • ${worstStress.scenario}: ${(worstStress.portfolioImpact * 100).toFixed(2)}%`); console.log("\nOutput Files:");console.log(` • ${OUTPUT_DIR}/efficient-frontier.svg`);console.log(` • ${OUTPUT_DIR}/monte-carlo-distribution.svg`);console.log(` • ${OUTPUT_DIR}/correlation-heatmap.svg`); console.log(`\n${"═".repeat(70)}`);console.log(" Financial Risk Analysis Complete!");console.log("═".repeat(70));Output
Risk report on the console
`output/efficient-frontier.svg`
`output/monte-carlo-distribution.svg`
`output/correlation-heatmap.svg`