Project 01
Tensors
Linear Algebra
Statistics
DataFrame
Random

Financial Portfolio Risk Analysis System

A production-grade financial risk analysis system built with Deepbox, demonstrating comprehensive use of statistics, linear algebra, and DataFrame operations. It combines deepbox/ndarray, deepbox/linalg, deepbox/stats, deepbox/dataframe, deepbox/random, deepbox/plot to deliver a larger production-style Deepbox workflow with reproducible outputs and documented architecture.

Features

  • Portfolio Construction: Build diversified portfolios from asset data
  • Risk Metrics: Calculate VaR (Value at Risk), CVaR, Sharpe Ratio, Sortino Ratio
  • Correlation Analysis: Asset correlation matrices and heatmaps
  • Optimization: Mean–variance optimization using matrix inverse (`inv`)–based Markowitz machinery
  • Statistical Tests: Normality tests, stationarity analysis
  • Monte Carlo Simulation: Portfolio return simulations

Deepbox Modules Used

deepbox/ndarraydeepbox/linalgdeepbox/statsdeepbox/dataframedeepbox/randomdeepbox/plot

Project Architecture

  • 01-financial-risk-analysis/
  • ├── index.ts # Main entry point
  • ├── README.md # This file
  • └── src/
  • ├── portfolio.ts # Portfolio class and operations
  • ├── risk-metrics.ts # VaR, CVaR, Sharpe calculations
  • ├── optimization.ts # Mean-variance optimization
  • └── monte-carlo.ts # Monte Carlo simulations

Source Files

index.ts
1/**2 * Financial Portfolio Risk Analysis System3 *4 * A comprehensive financial risk analysis application demonstrating:5 * - Portfolio construction and management6 * - Risk metrics (VaR, CVaR, Sharpe, Sortino)7 * - Mean-variance optimization8 * - Monte Carlo simulation9 * - Correlation analysis10 * - Stress testing11 *12 * Deepbox Modules Used:13 * - deepbox/ndarray: Tensor operations14 * - deepbox/linalg: Matrix inverse, Cholesky, determinant/trace15 * - deepbox/stats: Correlation, covariance, statistical measures16 * - deepbox/dataframe: Data manipulation17 * - deepbox/plot: Visualization18 */1920import { existsSync, mkdirSync, writeFileSync } from "node:fs";21import { isNumericTypedArray, isTypedArray } from "deepbox/core";22import { DataFrame } from "deepbox/dataframe";23import { det, trace } from "deepbox/linalg";24import { tensor } from "deepbox/ndarray";25import { Figure } from "deepbox/plot";26import { pearsonr, shapiro, spearmanr } from "deepbox/stats";27import {28  bootstrapConfidenceInterval,29  bootstrapReturns,30  getHistoricalStressScenarios,31  runStressTests,32  simulatePortfolioReturns,33} from "./src/monte-carlo";34import {35  generateEfficientFrontier,36  maxSharpePortfolio,37  minimumVariancePortfolio,38  riskParityPortfolio,39} from "./src/optimization";40import { generateSyntheticAssets, Portfolio } from "./src/portfolio";41import {42  backtestVaR,43  calculateRiskMetrics,44  calculateRollingVaR,45  calculateVaR,46} from "./src/risk-metrics";4748// ============================================================================49// Configuration50// ============================================================================5152const OUTPUT_DIR = "docs/projects/01-financial-risk-analysis/output";53const NUM_PERIODS = 60; // 5 years of monthly data54const RISK_FREE_RATE = 0.02; // 2% annual risk-free rate5556const expectNumericTypedArray = (57  value: unknown58): Float32Array | Float64Array | Int32Array | Uint8Array => {59  if (!isTypedArray(value) || !isNumericTypedArray(value)) {60    throw new Error("Expected numeric typed array");61  }62  return value;63};6465// ============================================================================66// Main Execution67// ============================================================================6869console.log("═".repeat(70));70console.log("  FINANCIAL PORTFOLIO RISK ANALYSIS SYSTEM");71console.log("  Built with Deepbox — TypeScript toolkit for AI & numerical computing");72console.log("═".repeat(70));7374// Create output directory75if (!existsSync(OUTPUT_DIR)) {76  mkdirSync(OUTPUT_DIR, { recursive: true });77}7879// ============================================================================80// Step 1: Generate Synthetic Asset Data81// ============================================================================8283console.log("\n📊 STEP 1: Generating Asset Data");84console.log("─".repeat(70));8586const assets = generateSyntheticAssets(NUM_PERIODS);8788console.log(`Generated ${assets.length} assets with ${NUM_PERIODS} periods of data:\n`);89const assetSummary = new DataFrame({90  Symbol: assets.map((a) => a.symbol),91  Name: assets.map((a) => a.name),92  Sector: assets.map((a) => a.sector),93  "Avg Monthly Return (%)": assets.map((a) => {94    const avg = a.returns.reduce((s, r) => s + r, 0) / a.returns.length;95    return (avg * 100).toFixed(3);96  }),97  "Volatility (%)": assets.map((a) => {98    const avg = a.returns.reduce((s, r) => s + r, 0) / a.returns.length;99    const variance = a.returns.reduce((s, r) => s + (r - avg) ** 2, 0) / a.returns.length;100    return (Math.sqrt(variance) * 100).toFixed(3);101  }),102});103104console.log(assetSummary.toString());105106// ============================================================================107// Step 2: Build Initial Portfolio108// ============================================================================109110console.log("\n📈 STEP 2: Building Initial Portfolio");111console.log("─".repeat(70));112113// Start with equal weights114const equalWeights = Array(assets.length).fill(1 / assets.length);115const portfolio = new Portfolio(assets, equalWeights, RISK_FREE_RATE);116117console.log("\nInitial Equal-Weight Portfolio Allocation:");118const portfolioDF = portfolio.toDataFrame();119console.log(portfolioDF.toString());120121const initialMetrics = portfolio.getMetrics();122console.log("\nInitial Portfolio Metrics:");123console.log(`  Expected Annual Return: ${(initialMetrics.expectedReturn * 100).toFixed(2)}%`);124console.log(`  Annual Volatility:      ${(initialMetrics.volatility * 100).toFixed(2)}%`);125console.log(`  Sharpe Ratio:           ${initialMetrics.sharpeRatio.toFixed(3)}`);126console.log(`  Sortino Ratio:          ${initialMetrics.sortinoRatio.toFixed(3)}`);127console.log(`  Maximum Drawdown:       ${(initialMetrics.maxDrawdown * 100).toFixed(2)}%`);128129// ============================================================================130// Step 3: Risk Analysis131// ============================================================================132133console.log("\n⚠️  STEP 3: Risk Analysis");134console.log("─".repeat(70));135136const portfolioReturns = portfolio.getPortfolioReturns();137const returnsArray = Array.from(expectNumericTypedArray(portfolioReturns.data));138139const riskMetrics = calculateRiskMetrics(returnsArray);140141console.log("\nValue at Risk (VaR):");142console.log(`  95% VaR (Historical): ${(riskMetrics.var95 * 100).toFixed(2)}%`);143console.log(`  99% VaR (Historical): ${(riskMetrics.var99 * 100).toFixed(2)}%`);144145console.log("\nConditional Value at Risk (CVaR / Expected Shortfall):");146console.log(`  95% CVaR: ${(riskMetrics.cvar95 * 100).toFixed(2)}%`);147console.log(`  99% CVaR: ${(riskMetrics.cvar99 * 100).toFixed(2)}%`);148149console.log("\nOther Risk Metrics:");150console.log(`  Annualized Volatility:    ${(riskMetrics.volatility * 100).toFixed(2)}%`);151console.log(`  Downside Deviation:       ${(riskMetrics.downsideDeviation * 100).toFixed(2)}%`);152console.log(`  Maximum Drawdown:         ${(riskMetrics.maxDrawdown * 100).toFixed(2)}%`);153console.log(`  Calmar Ratio:             ${riskMetrics.calmarRatio.toFixed(3)}`);154155// VaR comparison across methods156console.log("\nVaR Method Comparison (95% Confidence):");157console.log(158  `  Historical:     ${(calculateVaR(returnsArray, 0.95, "historical") * 100).toFixed(2)}%`159);160console.log(161  `  Parametric:     ${(calculateVaR(returnsArray, 0.95, "parametric") * 100).toFixed(2)}%`162);163console.log(164  `  Cornish-Fisher: ${(calculateVaR(returnsArray, 0.95, "cornish-fisher") * 100).toFixed(2)}%`165);166167// ============================================================================168// Step 4: Correlation Analysis169// ============================================================================170171console.log("\n🔗 STEP 4: Correlation Analysis");172console.log("─".repeat(70));173174const corrMatrix = portfolio.getCorrelationMatrix();175const covMatrix = portfolio.getCovarianceMatrix();176177console.log("\nCorrelation Matrix:");178const symbols = portfolio.getSymbols();179const corrData: { [key: string]: number[] } = {};180for (let i = 0; i < symbols.length; i++) {181  const symbol = symbols[i];182  corrData[symbol] = [];183  for (let j = 0; j < symbols.length; j++) {184    corrData[symbol].push(Number(Number(corrMatrix.data[i * symbols.length + j]).toFixed(3)));185  }186}187const corrDF = new DataFrame(corrData);188console.log(corrDF.toString());189190// Covariance matrix properties191console.log("\nCovariance Matrix Properties:");192console.log(`  Determinant: ${det(covMatrix).toExponential(4)}`);193console.log(`  Trace:       ${Number(trace(covMatrix).data[0]).toFixed(6)}`);194195// Statistical tests on returns196console.log("\nStatistical Tests (First Asset vs Last Asset):");197const asset1Returns = tensor(assets[0]?.returns);198const asset8Returns = tensor(assets[7]?.returns);199200const [pearsonCorr, pearsonP] = pearsonr(asset1Returns, asset8Returns);201const [spearmanCorr, spearmanP] = spearmanr(asset1Returns, asset8Returns);202203console.log(`  Pearson Correlation:  ${pearsonCorr.toFixed(4)} (p-value: ${pearsonP.toFixed(4)})`);204console.log(205  `  Spearman Correlation: ${spearmanCorr.toFixed(4)} (p-value: ${spearmanP.toFixed(4)})`206);207208// Normality test209const shapiroResult = shapiro(asset1Returns);210console.log(211  `  Shapiro-Wilk Test (TECH): W=${shapiroResult.statistic.toFixed(212    4213  )}, p=${shapiroResult.pvalue.toFixed(4)}`214);215216// ============================================================================217// Step 5: Portfolio Optimization218// ============================================================================219220console.log("\n🎯 STEP 5: Portfolio Optimization");221console.log("─".repeat(70));222223const expectedReturns = assets.map((a) => a.returns.reduce((s, r) => s + r, 0) / a.returns.length);224225// Minimum Variance Portfolio226console.log("\n1. Minimum Variance Portfolio:");227const minVarWeights = minimumVariancePortfolio(covMatrix);228const minVarPortfolio = new Portfolio(assets, minVarWeights, RISK_FREE_RATE);229const minVarMetrics = minVarPortfolio.getMetrics();230console.log(`   Weights: [${minVarWeights.map((w) => `${(w * 100).toFixed(1)}%`).join(", ")}]`);231console.log(`   Expected Return: ${(minVarMetrics.expectedReturn * 100).toFixed(2)}%`);232console.log(`   Volatility:      ${(minVarMetrics.volatility * 100).toFixed(2)}%`);233console.log(`   Sharpe Ratio:    ${minVarMetrics.sharpeRatio.toFixed(3)}`);234235// Maximum Sharpe Portfolio236console.log("\n2. Maximum Sharpe Ratio Portfolio:");237const maxSharpeResult = maxSharpePortfolio(expectedReturns, covMatrix, RISK_FREE_RATE);238console.log(239  `   Weights: [${maxSharpeResult.weights.map((w) => `${(w * 100).toFixed(1)}%`).join(", ")}]`240);241console.log(`   Expected Return: ${(maxSharpeResult.expectedReturn * 100).toFixed(2)}%`);242console.log(`   Volatility:      ${(maxSharpeResult.volatility * 100).toFixed(2)}%`);243console.log(`   Sharpe Ratio:    ${maxSharpeResult.sharpeRatio.toFixed(3)}`);244245// Risk Parity Portfolio246console.log("\n3. Risk Parity Portfolio:");247const riskParityWeights = riskParityPortfolio(covMatrix);248const riskParityPortfolioObj = new Portfolio(assets, riskParityWeights, RISK_FREE_RATE);249const riskParityMetrics = riskParityPortfolioObj.getMetrics();250console.log(`   Weights: [${riskParityWeights.map((w) => `${(w * 100).toFixed(1)}%`).join(", ")}]`);251console.log(`   Expected Return: ${(riskParityMetrics.expectedReturn * 100).toFixed(2)}%`);252console.log(`   Volatility:      ${(riskParityMetrics.volatility * 100).toFixed(2)}%`);253console.log(`   Sharpe Ratio:    ${riskParityMetrics.sharpeRatio.toFixed(3)}`);254255// Generate Efficient Frontier256console.log("\n4. Efficient Frontier (sample points):");257const frontier = generateEfficientFrontier(expectedReturns, covMatrix, 10);258const frontierDF = new DataFrame({259  "Return (%)": frontier.map((p) => (p.return * 100).toFixed(2)),260  "Volatility (%)": frontier.map((p) => (p.volatility * 100).toFixed(2)),261});262console.log(frontierDF.toString());263264// ============================================================================265// Step 6: Monte Carlo Simulation266// ============================================================================267268console.log("\n🎲 STEP 6: Monte Carlo Simulation");269console.log("─".repeat(70));270271const optimalWeights = maxSharpeResult.weights;272const mcResult = simulatePortfolioReturns(273  expectedReturns,274  covMatrix,275  optimalWeights,276  10000, // 10,000 simulations277  12, // 12 month horizon278  42 // seed279);280281console.log("\nMonte Carlo Simulation Results (10,000 scenarios, 12-month horizon):");282console.log(`  Mean Return:     ${(mcResult.meanReturn * 100).toFixed(2)}%`);283console.log(`  Median Return:   ${(mcResult.medianReturn * 100).toFixed(2)}%`);284console.log(`  Volatility:      ${(mcResult.volatility * 100).toFixed(2)}%`);285console.log(`  95% VaR:         ${(mcResult.var95 * 100).toFixed(2)}%`);286console.log(`  99% VaR:         ${(mcResult.var99 * 100).toFixed(2)}%`);287288console.log("\nReturn Distribution Percentiles:");289console.log(`  5th percentile:  ${(mcResult.percentile5 * 100).toFixed(2)}%`);290console.log(`  25th percentile: ${(mcResult.percentile25 * 100).toFixed(2)}%`);291console.log(`  75th percentile: ${(mcResult.percentile75 * 100).toFixed(2)}%`);292console.log(`  95th percentile: ${(mcResult.percentile95 * 100).toFixed(2)}%`);293294// Bootstrap confidence interval for Sharpe ratio295console.log("\nBootstrap Confidence Interval for Mean Return:");296const bootstrapMeans = bootstrapReturns(297  returnsArray,298  1000,299  (data) => data.reduce((a, b) => a + b, 0) / data.length,300  42301);302const ci = bootstrapConfidenceInterval(bootstrapMeans, 0.95);303console.log(`  95% CI: [${(ci.lower * 100).toFixed(3)}%, ${(ci.upper * 100).toFixed(3)}%]`);304console.log(`  Bootstrap Mean: ${(ci.mean * 100).toFixed(3)}%`);305306// ============================================================================307// Step 7: Stress Testing308// ============================================================================309310console.log("\n💥 STEP 7: Stress Testing");311console.log("─".repeat(70));312313const stressScenarios = getHistoricalStressScenarios();314const stressResults = runStressTests(optimalWeights, expectedReturns, stressScenarios);315316console.log("\nStress Test Results (Optimal Portfolio):\n");317const stressDF = new DataFrame({318  Scenario: stressResults.map((r) => r.scenario),319  "Portfolio Impact (%)": stressResults.map((r) => (r.portfolioImpact * 100).toFixed(2)),320});321console.log(stressDF.toString());322323// ============================================================================324// Step 8: VaR Backtesting325// ============================================================================326327console.log("\n📋 STEP 8: VaR Backtesting");328console.log("─".repeat(70));329330// Calculate rolling VaR331const rollingVaR = calculateRollingVaR(returnsArray, 20, 0.95);332333// Backtest against actual returns (offset by window size)334const backtestReturns = returnsArray.slice(20);335const backtestResult = backtestVaR(backtestReturns, rollingVaR.slice(0, backtestReturns.length));336337console.log("\nVaR Backtesting Results:");338console.log(`  Number of Periods:    ${backtestReturns.length}`);339console.log(`  VaR Exceedances:      ${backtestResult.exceedances}`);340console.log(`  Exceedance Rate:      ${(backtestResult.rate * 100).toFixed(2)}%`);341console.log(`  Expected Rate (95%):  ${(backtestResult.expected * 100).toFixed(2)}%`);342console.log(343  `  Model ${backtestResult.rate <= 0.07 ? "PASSES" : "FAILS"} validation (tolerance: 7%)`344);345346// ============================================================================347// Step 9: Generate Visualizations348// ============================================================================349350console.log("\n📊 STEP 9: Generating Visualizations");351console.log("─".repeat(70));352353// 1. Efficient Frontier Plot354try {355  const frontierFig = new Figure({ width: 800, height: 600 });356  const frontierAx = frontierFig.addAxes();357358  const frontierReturns = frontier.map((p) => p.return * 100);359  const frontierVols = frontier.map((p) => p.volatility * 100);360361  frontierAx.plot(tensor(frontierVols), tensor(frontierReturns), {362    color: "#2196F3",363    linewidth: 2,364  });365366  // Mark special portfolios367  frontierAx.scatter(368    tensor([minVarMetrics.volatility * 100]),369    tensor([minVarMetrics.expectedReturn * 100]),370    { color: "#4CAF50", size: 12 }371  );372373  frontierAx.scatter(374    tensor([maxSharpeResult.volatility * 100]),375    tensor([maxSharpeResult.expectedReturn * 100]),376    { color: "#FF5722", size: 12 }377  );378379  frontierAx.setTitle("Efficient Frontier");380  frontierAx.setXLabel("Volatility (%)");381  frontierAx.setYLabel("Expected Return (%)");382383  const frontierSvg = frontierFig.renderSVG();384  writeFileSync(`${OUTPUT_DIR}/efficient-frontier.svg`, frontierSvg.svg);385  console.log(`  ✓ Saved: ${OUTPUT_DIR}/efficient-frontier.svg`);386} catch (e) {387  console.log(`  ⚠ Could not generate efficient frontier plot: ${e}`);388}389390// 2. Monte Carlo Distribution Plot391try {392  const mcFig = new Figure({ width: 800, height: 600 });393  const mcAx = mcFig.addAxes();394395  // Create histogram data396  const numBins = 50;397  const minReturn = Math.min(...mcResult.scenarios);398  const maxReturn = Math.max(...mcResult.scenarios);399  const binWidth = (maxReturn - minReturn) / numBins;400401  const bins: number[] = [];402  const counts: number[] = [];403404  for (let i = 0; i < numBins; i++) {405    const binStart = minReturn + i * binWidth;406    const binEnd = binStart + binWidth;407    const binCenter = (binStart + binEnd) / 2;408    const count = mcResult.scenarios.filter((s) => s >= binStart && s < binEnd).length;409    bins.push(binCenter * 100);410    counts.push(count);411  }412413  mcAx.bar(tensor(bins), tensor(counts), { color: "#9C27B0" });414  mcAx.setTitle("Monte Carlo Return Distribution");415  mcAx.setXLabel("Return (%)");416  mcAx.setYLabel("Frequency");417418  const mcSvg = mcFig.renderSVG();419  writeFileSync(`${OUTPUT_DIR}/monte-carlo-distribution.svg`, mcSvg.svg);420  console.log(`  ✓ Saved: ${OUTPUT_DIR}/monte-carlo-distribution.svg`);421} catch (e) {422  console.log(`  ⚠ Could not generate Monte Carlo plot: ${e}`);423}424425// 3. Correlation Heatmap426try {427  const heatFig = new Figure({ width: 800, height: 700 });428  const heatAx = heatFig.addAxes();429430  heatAx.heatmap(corrMatrix);431  heatAx.setTitle("Asset Correlation Matrix");432433  const heatSvg = heatFig.renderSVG();434  writeFileSync(`${OUTPUT_DIR}/correlation-heatmap.svg`, heatSvg.svg);435  console.log(`  ✓ Saved: ${OUTPUT_DIR}/correlation-heatmap.svg`);436} catch (e) {437  console.log(`  ⚠ Could not generate correlation heatmap: ${e}`);438}439440// ============================================================================441// Step 10: Final Summary442// ============================================================================443444console.log(`\n${"═".repeat(70)}`);445console.log("  ANALYSIS COMPLETE - SUMMARY");446console.log("═".repeat(70));447448console.log("\n📌 Key Findings:\n");449console.log("  1. Portfolio Comparison:");450console.log(`     • Equal-Weight Sharpe:   ${initialMetrics.sharpeRatio.toFixed(3)}`);451console.log(`     • Min-Variance Sharpe:   ${minVarMetrics.sharpeRatio.toFixed(3)}`);452console.log(`     • Max-Sharpe Sharpe:     ${maxSharpeResult.sharpeRatio.toFixed(3)}`);453console.log(`     • Risk-Parity Sharpe:    ${riskParityMetrics.sharpeRatio.toFixed(3)}`);454455console.log("\n  2. Risk Assessment:");456console.log(`     • 95% VaR:               ${(riskMetrics.var95 * 100).toFixed(2)}%`);457console.log(`     • 95% CVaR:              ${(riskMetrics.cvar95 * 100).toFixed(2)}%`);458console.log(`     • Maximum Drawdown:      ${(riskMetrics.maxDrawdown * 100).toFixed(2)}%`);459460console.log("\n  3. Monte Carlo Insights:");461console.log(`     • Expected 12M Return:   ${(mcResult.meanReturn * 100).toFixed(2)}%`);462console.log(`     • Worst Case (5%):       ${(mcResult.percentile5 * 100).toFixed(2)}%`);463console.log(`     • Best Case (95%):       ${(mcResult.percentile95 * 100).toFixed(2)}%`);464465console.log("\n  4. Stress Test Worst Cases:");466const worstStress = stressResults.reduce((worst, r) =>467  r.portfolioImpact < worst.portfolioImpact ? r : worst468);469console.log(`     • ${worstStress.scenario}: ${(worstStress.portfolioImpact * 100).toFixed(2)}%`);470471console.log("\n📁 Output Files:");472console.log(`   • ${OUTPUT_DIR}/efficient-frontier.svg`);473console.log(`   • ${OUTPUT_DIR}/monte-carlo-distribution.svg`);474console.log(`   • ${OUTPUT_DIR}/correlation-heatmap.svg`);475476console.log(`\n${"═".repeat(70)}`);477console.log("  ✅ Financial Risk Analysis Complete!");478console.log("═".repeat(70));479

Console Output

$ npx tsx 01-financial-risk-analysis/index.ts
Risk metrics report
Correlation heatmap (SVG)
Efficient frontier plot (SVG)
Portfolio allocation recommendations

Key Takeaways

  • Portfolio Construction: Build diversified portfolios from asset data
  • Risk Metrics: Calculate VaR (Value at Risk), CVaR, Sharpe Ratio, Sortino Ratio
  • Correlation Analysis: Asset correlation matrices and heatmaps
  • Optimization: Mean–variance optimization using matrix inverse (`inv`)–based Markowitz machinery
  • Use deepbox/ndarray for Tensor operations, matrix math.
  • Use deepbox/linalg for Matrix inverse (`inv`), Cholesky (`cholesky`), determinant/trace helpers.