import React, { useState, useMemo } from 'react'; // Standard Normal Cumulative Distribution Function (CDF) Approximation function normalCDF(x) { const sign = x < 0 ? -1 : 1; const xAbs = Math.abs(x) / Math.sqrt(2); const t = 1.0 / (1.0 + 0.3275911 * xAbs); const a1 = 0.254829592; const a2 = -0.284496736; const a3 = 1.421413741; const a4 = -1.453152027; const a5 = 1.061405429; const erf = 1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-xAbs * xAbs); return 0.5 * (1 + sign * erf); } // Lognormal Probability Density Function (PDF) function lognormalPDF(x, mean, variance) { if (x <= 0) return 0; const sd = Math.sqrt(variance); return (1 / (x * sd * Math.sqrt(2 * Math.PI))) * Math.exp(-Math.pow(Math.log(x) - mean, 2) / (2 * variance)); } export default function MertonSimulator() { // Core parameters (defaulted to the June 22 market drift baseline) const [assetValue, setAssetValue] = useState(100); const [debtPoint, setDebtPoint] = useState(70); const [volatility, setVolatility] = useState(0.20); const [riskFreeRate, setRiskFreeRate] = useState(0.045); // 4.5% baseline const [timeHorizon, setTimeHorizon] = useState(1.0); // Calculate Distance to Default (DD) and Probability of Default (PD) const { DD, PD, chartData, chartMeta } = useMemo(() => { // Merton Model Calculations const expectedReturn = riskFreeRate; const drift = expectedReturn - (volatility * volatility) / 2; const ddNumerator = Math.log(assetValue / debtPoint) + (drift * timeHorizon); const ddDenominator = volatility * Math.sqrt(timeHorizon); const calculatedDD = ddNumerator / ddDenominator; const calculatedPD = normalCDF(-calculatedDD); // Chart Distribution Parameters const meanLog = Math.log(assetValue) + (drift * timeHorizon); const varianceLog = (volatility * volatility) * timeHorizon; // Dynamic Chart Boundaries const sdLog = Math.sqrt(varianceLog); const xMin = Math.max(1, Math.exp(meanLog - 3.5 * sdLog)); const xMax = Math.max(200, Math.exp(meanLog + 3 * sdLog), assetValue * 1.5, debtPoint * 1.5); // Generate Curve Points const points = 250; const data = []; let maxY = 0; for (let i = 0; i <= points; i++) { const x = xMin + (i / points) * (xMax - xMin); const y = lognormalPDF(x, meanLog, varianceLog); if (y > maxY) maxY = y; data.push({ x, y }); } return { DD: calculatedDD, PD: calculatedPD, chartData: data, chartMeta: { xMin, xMax, maxY: Math.max(maxY, 0.0001) } }; }, [assetValue, debtPoint, volatility, riskFreeRate, timeHorizon]); // SVG Rendering Constants const SVG_WIDTH = 800; const SVG_HEIGHT = 350; const PADDING = 50; const getX = (val) => PADDING + ((val - chartMeta.xMin) / (chartMeta.xMax - chartMeta.xMin)) * (SVG_WIDTH - 2 * PADDING); const getY = (val) => SVG_HEIGHT - PADDING - (val / chartMeta.maxY) * (SVG_HEIGHT - 2 * PADDING); // Path Generation const fullCurvePath = chartData.map((d, i) => `${i === 0 ? 'M' : 'L'} ${getX(d.x)} ${getY(d.y)}`).join(' '); const defaultData = chartData.filter(d => d.x <= debtPoint); let defaultAreaPath = ''; if (defaultData.length > 0) { defaultAreaPath = defaultData.map((d, i) => `${i === 0 ? 'M' : 'L'} ${getX(d.x)} ${getY(d.y)}`).join(' '); const lastPoint = defaultData[defaultData.length - 1]; const firstPoint = defaultData[0]; defaultAreaPath += ` L ${getX(lastPoint.x)} ${getY(0)} L ${getX(firstPoint.x)} ${getY(0)} Z`; } // Dynamic Risk Status Color const getStatusColor = (pd) => { if (pd < 0.03) return 'text-emerald-400 border-emerald-400/30'; if (pd < 0.08) return 'text-amber-400 border-amber-400/30'; return 'text-rose-500 border-rose-500/30'; }; return (
{/* Header Area */}

Adam OS v30.1: Merton Distance-to-Default

Real-time lognormal asset simulation and structural default probability

Execution Env
FEDERATED_WEIGHTS_SYNCED
{/* Control Panel */}

Macro & Asset Parameters

{/* Control: Asset Volatility */}
{(volatility * 100).toFixed(1)}%
setVolatility(parseFloat(e.target.value))} className="w-full accent-cyan-500 cursor-pointer" />

Simulates market uncertainty / rate shock.

{/* Control: Expected Return / Risk Free Rate */}
{(riskFreeRate * 100).toFixed(2)}%
setRiskFreeRate(parseFloat(e.target.value))} className="w-full accent-cyan-500 cursor-pointer" />

Drift parameter representing current base rates.

{/* Control: Debt Threshold */}
${debtPoint}
setDebtPoint(parseFloat(e.target.value))} className="w-full accent-rose-500 cursor-pointer" />

Total short-term debt + 50% long-term debt.

{/* Control: Initial Asset Value */}
${assetValue}
setAssetValue(parseFloat(e.target.value))} className="w-full accent-blue-500 cursor-pointer" />
{/* Control: Time Horizon */}
{timeHorizon.toFixed(1)} Years
setTimeHorizon(parseFloat(e.target.value))} className="w-full accent-slate-500 cursor-pointer" />
{/* Visualization & Metrics Panel */}
{/* Live Output Metrics */}
1-Year Probability of Default (PD) {(PD * 100).toFixed(2)}%
Distance to Default (DD) {DD.toFixed(3)} Standard Deviations (σ)
{/* SVG Chart */}
Asset Distribution
Default Region
{/* Grid Lines */} {[0, 0.25, 0.5, 0.75, 1].map(tick => ( ))} {/* Base Axis line */} {/* Default Region Area Fill */} {/* Lognormal Curve */} {/* Debt Point Vertical Marker */} Threshold (${debtPoint}) {/* Initial Asset Vertical Marker */} V₀ (${assetValue}) {/* X-Axis Labels */} {chartMeta.xMin.toFixed(0)} {chartMeta.xMax.toFixed(0)} FUTURE ASSET VALUE DISTRIBUTION (V_T)
); }