Skip to content

reeveskeefe/mertonlab

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mertonlab

Calibrate the Black–Scholes–Merton (BSM) structural model for equity/credit, and numerically validate the core identities (pricing, delta–leverage, martingale discipline, PD vs simulation, and Breeden–Litzenberger density mass). Dependency-light, deterministic, and production-friendly.

Table of Contents


Why

Analytical derivations are necessary but not sufficient. Operational trust requires:

  • Parameter recovery that actually meets both price and leverage constraints,
  • Verified martingale behavior under risk-neutral discretization,
  • Default probability matches simulation under $\mathbb{Q}$,
  • Risk-neutral density integrates to 1 on a sufficiently broad strike grid,
  • Transparent diagnostics: residuals, conditioning, and Jacobian sign.

This project provides all of that in a compact, auditable package.


Features

  • Structural calibration of $(V,\sigma_A)$ from observed $(E,\sigma_E)$ with robust bracketed solvers.
  • Credit metrics: $\mathrm{DD}=d_2$, $\mathrm{PD}=\Phi(-d_2)$, risky debt $D_0$, and spread.
  • RN martingale check with unbiased step discretization.
  • PD simulation vs analytic (Merton default $V_T<K$).
  • Breeden–Litzenberger density recovery with 5-point stencil FD.
  • Jacobian determinant diagnostic for local uniqueness/conditioning.
  • Zero dynamic code exec, no I/O side effects; prints JSON.

Dependency: numpy only.


Math at a Glance

  • Equity as call on assets with carry $\delta$:

    $$ E = V e^{-\delta T}\Phi(d_1)-K e^{-rT}\Phi(d_2),\quad d_1=\frac{\ln(V/K)+(r-\delta+\tfrac12\sigma_A^2)T}{\sigma_A\sqrt{T}},\ d_2=d_1-\sigma_A\sqrt{T}. $$

  • Delta–leverage (equity vol mapping):

    $$ \sigma_E=\frac{V}{E}e^{-\delta T}\Phi(d_1),\sigma_A. $$

  • Merton PD: $\mathrm{PD}=\Phi(-d_2)$; DD $=d_2$.

  • Risky debt: $D_0=V e^{-\delta T}\Phi(-d_1)+K e^{-rT}\Phi(d_2)$.

  • RN martingale (unbiased step):

    $$ S_{n+1}=S_n\exp!\big((r-q)\Delta-\tfrac12\bar h\Delta+\sqrt{\bar h\Delta},Z\big),\quad Z\sim\mathcal{N}(0,1). $$

  • BL RND: $f^{\mathbb{Q}}(K)=e^{rT},\partial^2 C/\partial K^2$ (finite-difference on a wide strike grid).


Install

python -m pip install numpy

Run from source (no install):

# Calibrate
python -m mertonlab.cli calibrate --E 5e9 --sigmaE 0.35 --K 3e9 --r 0.04 --T 1.0 --delta 0.00

# Validate numerically
python -m mertonlab.cli validate  --E 5e9 --sigmaE 0.35 --K 3e9 --r 0.04 --T 1.0 --delta 0.00 --paths 200000

Quick Start (CLI)

usage: python -m mertonlab.cli {calibrate,validate} [options]

calibrate:
  --E <float>         Equity market cap (E_obs)
  --sigmaE <float>    Equity volatility (annualized)
  --K <float>         Debt face at horizon
  --r <float>         Risk-free rate (continuous)
  --T <float>         Horizon in years
  --delta <float>     Asset payout/carry

validate:
  (same params) + --paths <int>  # Monte Carlo paths (default 200000)

All outputs are JSON to stdout (easy to pipe into logs or dashboards).


Example Outputs

Calibrate:

{
  "V_hat": 7882367691.02606,
  "sigma_A_hat": 0.2220148923936331,
  "d1": 4.642307250584444,
  "d2": 4.42029235819081,
  "DD": 4.42029235819081,
  "PD": 4.928372857315733e-06,
  "D0": 2882367691.02606,
  "spread": 2.1733203695367667e-07,
  "pricing_residual": 0.0,
  "vol_residual": -7.216449660063518e-16,
  "detJ": 1.576407070515039
}

Notes: residuals are numerically zero (model hits both price and leverage), and $\det J&gt;0$ indicates locally unique, well-conditioned solution.

Risk-neutral density mass (wide grid) For BL density, use a sufficiently broad strike grid. With $K \in [10^{-3}V,,8V]$ and an $h$ proportional to $V$, the numerical mass is ~1:

∫ f^Q(K) dK  ≈ 1.000000000179

(See RND guidance for details.)


Python API

from mertonlab.structural import MertonInputs
from mertonlab.calibration import calibrate
from mertonlab.validate import numerical_validation

inputs = MertonInputs(
    E_obs=5e9, sigma_E_obs=0.35,
    K=3e9, r=0.04, T=1.0, delta=0.0
)

res = calibrate(inputs)
print(res.state.V, res.state.sigma_A, res.DD, res.PD, res.D0, res.spread)

rep = numerical_validation(inputs, n_paths=200000)
print(rep)

Key objects:

  • CalibrationResult: state(V, sigma_A, d1, d2, E_model, sigma_E_model), DD, PD, D0, spread, residuals, detJ.
  • ValidationReport: absolute pricing/vol errors, RN martingale error, PD sim gap, BL density mass error, jacobian_det.

Numerical Validations

  1. Pricing & leverage residuals Solve for $(V,\sigma_A)$ so that

    $$ E_{\text{model}}=E_{\text{obs}},\qquad \sigma_{E,\text{model}}=\sigma_{E,\text{obs}}. $$

    Report $|E_{\text{model}}-E_{\text{obs}}|$ and $|\sigma_{E,\text{model}}-\sigma_{E,\text{obs}}|$.

  2. RN martingale discipline With the unbiased step,

    $$ \mathbb{E}^{\mathbb{Q}}!\big[e^{-rT}S_T\big]=S_0 e^{-qT}. $$

    Report absolute difference of the Monte Carlo estimate and RHS.

  3. PD simulation vs analytic Under $\mathbb{Q}$ for assets, simulate $V_T$ and estimate $\mathbb{P}(V_T&lt;K)$. Compare to $\Phi(-d_2)$.

  4. BL density mass Recover $f^\mathbb{Q}(K)=e^{rT},\partial^2 C/\partial K^2$ via a 5-point stencil and integrate over a wide strike range. Tip: For production-grade mass≈1, set:

    • $K_{\min}\approx 10^{-3},V$ (or lower), $K_{\max}\in[6V,10V]$,
    • stencil step $h\approx 10^{-3}V$,
    • grid size ≥ 3000 points.

    The CLI’s default RND check is conservative and uses a compact grid as a guardrail; for a formal mass-=1 verification, use the API with a wide grid as above.

  5. Jacobian determinant Evaluate the analytic $\det J$ at the solution; positivity implies local uniqueness and good conditioning.


Units, Assumptions, and Scaling

  • Rates $r,\delta$ are continuously compounded (annualized). Vols are annualized.
  • Horizon $T$ is in years.
  • Input levels are in currency units; the calibration internally scales and clamps to avoid log/div-by-zero.
  • Small-PD regimes require wider strike ranges for stable BL density recovery.

Performance & Stability

  • Root-finding uses bracketed bisection for the inner price solve and secant for the outer volatility solve (with bracket expansion), ensuring robustness.
  • Monte Carlo is fully vectorized. For most workloads, $10^5$ paths per check is <1s on a modern CPU; scale as needed.
  • When calibrating very levered names or ultra-short maturities, consider increasing solver tolerances and strike ranges for RND.

Security Posture

  • No network calls, no dynamic code execution, no implicit file I/O.
  • CLI reads args and writes JSON to stdout only.
  • Inputs are sanitized; numerical guards prevent overflow/underflow and division by zero.

Tests

A smoke test ensures calibration residuals are ~0 and numerical validations are within reasonable tolerances:

python -m pip install pytest numpy
pytest -q

Project Layout

mertonlab/
  mertonlab/
    structural.py      # closed-form Merton/BSM building blocks
    calibration.py     # robust parameter recovery + detJ
    black_cox.py       # Black–Cox survival (exponential barrier)
    bl_density.py      # Breeden–Litzenberger finite-diff RND
    montecarlo.py      # RN unbiased step + martingale check
    validate.py        # one-shot validation suite (JSON)
    cli.py             # CLI for calibrate/validate
  tests/
    test_calibrate.py  # smoke test
  README.md

Roadmap

  • User-configurable strike grids and stencils in CLI for BL density.
  • Optional Black–Cox PD integration in the validation suite.
  • Confidence intervals for PD via binomial/CLT and bootstrap.
  • Multiperiod RN checks and time-gridded variance schedules.
  • Hooks for real-world $\mathbb{P}$ volatility models (e.g., GARCH/DCC) feeding into scenario analysis.

Tip: For audit logs, run both commands and store the JSON alongside the exact input set. This gives you a reproducible, model-complete snapshot (params, conditioning, and validation) for every calibration.

About

Calibrate the Black–Scholes–Merton (BSM) structural model for equity/credit, and numerically validate the core identities (pricing, delta–leverage, martingale discipline, PD vs simulation, and Breeden–Litzenberger density mass). Dependency-light, deterministic, and production-friendly.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages