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/plotProject 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));479Console Output
$ npx tsx 01-financial-risk-analysis/index.ts
Risk metrics report
Correlation heatmap (SVG)
Efficient frontier plot (SVG)
Portfolio allocation recommendationsKey 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.