Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shortrate-lab

A stochastic short-rate term-structure toolkit from first principles in pure Python + NumPy/SciPy. The Vasicek and CIR models (closed-form affine bond prices, moments, yields), the unifying affine framework that reproduces both by integrating the Riccati ODE, the Vasicek bond-option closed form, and curve/calibration utilities — every formula derived from its definition, no rates library underneath.

54/54 tests verifying algebraic identities — the affine ODE reproducing each closed form, the four limits, put-call parity, the credit-style Feller condition — never a comparison to another library.

$ pytest tests/
=========================== 54 passed in 2.54s ============================

Every identity in this repo was adversarially verified by a multi-agent workflow before a line of code was written — 48 claims were independently checked and 4 had subtle errors corrected (the Monte Carlo bond-price test must be an honest 4σ band — tightening it by adding paths only surfaces the trapezoidal bias floor; the CIR σ→0 limit converges at O(σ²) but hits a ~1e-9 roundoff floor below σ≈1e-3, so the bound is scale-aware with a floor; the calibration "fitted yields match" identity holds only at the global minimum on on-model data, not "regardless of where the optimizer lands"; and the bond-call is not monotone in the bond maturity T_B — that clause was dropped for the sign-definite vega). See "How this was built".

Why this exists

Interest-rate models are where most people reach for QuantLib and never see the machinery. This repo is the opposite: the bond price is A(τ)·exp(−B(τ)·r) with B, A from the closed form, and the same price is recovered by numerically integrating the affine Riccati ODE — so the closed form is proven, not asserted. Every test pins an algebraic identity (a limit, a round-trip, a cross-model consistency), not a number copied from a textbook.

It completes the rates pillar of the portfolio: the discounting is tvm-lab, the deterministic term structure is fixedalt-lab, and the CIR process here is exactly the stochastic-intensity engine that credit-lab left on its roadmap.

The 5 modules

Module Topic Headline functions
vasicek.py Gaussian OU model bond_price, B, A, mean, variance, zero_yield, long_yield, mc_discount_factor
cir.py Square-root model bond_price, gamma, feller_satisfied, expected_rate, variance_rate, sample_exact, mc_bond_price
affine.py Affine framework AffineParams, vasicek_to_affine, cir_to_affine, solve_ab, affine_bond_price
curve.py Curve / calibration zero_yield, instantaneous_forward, vasicek_forward, par_yield, calibrate_vasicek
bondopt.py Vasicek ZCB options option_sigma_p, zcb_call, zcb_put

API in 30 seconds

from shortrate import vasicek, cir, affine, bondopt

vp = vasicek.VasicekParams(kappa=0.3, theta=0.05, sigma=0.02)
vasicek.bond_price(vp, r0=0.03, tau=4)        # 0.859410  (= A·exp(-B·r))
vasicek.long_yield(vp)                         # 0.047778  (= theta - sigma^2/2kappa^2)

cp = cir.CIRParams(kappa=0.5, theta=0.06, sigma=0.10)
cir.feller_satisfied(cp)                       # True  (2 kappa theta >= sigma^2 -> r > 0)
cir.bond_price(cp, r=0.04, tau=5)              # 0.770281

# the central identity: ONE ODE solver reproduces BOTH closed forms
affine.affine_bond_price(affine.cir_to_affine(cp), r=0.04, tau=5)   # 0.770281 (rel err ~5e-12)

# a Vasicek bond option, with put-call parity holding to ~1e-17
bondopt.zcb_call(vp, r0=0.03, T_O=1, T_B=4, K=0.85)   # 0.038119

The algebraic identities we actually test

54 tests, every one an identity the formula must satisfy. Highlights:

The affine spine (cross-module)

  • THE central identity: affine_bond_price(vasicek_to_affine(p), …) and …(cir_to_affine(p), …) reproduce the Vasicek/CIR closed forms by integrating b' = 1 − βb − (ν/2)b², a' = αb − (δ/2)b² — separately b == B and exp(−a) == A (honest solver tolerance ~1e-8, never 1e-12)
  • Vasicek/CIR duality: (κθ, κ, σ², 0) vs (κθ, κ, 0, σ²) — δ and ν swap (constant-vol vs square-root)
  • b(τ) saturates (a β-sign slip makes it explode); P(·,0) == 1 exactly

Vasicek

  • Affine price A·exp(−B·r); zero_yield reuses the same B (price/yield consistent to ulps)
  • The four limits: κ→0 → driftless integrated-BM price; σ→0 → deterministic OU discount; τ→0 → r0; τ→∞ → θ − σ²/2κ²
  • Conditional moments (state-independent variance); yield increasing in r0, long yield r0-independent
  • Monte Carlo: E_Q[exp(−∫r ds)] == P(0,T) (exact Gaussian transition, honest 4σ band)

CIR

  • Affine price with γ = √(κ²+2σ²) and the (κ+γ) grouping (the long yield 2κθ/(κ+γ) is the cleanest place that grouping is checked)
  • State-dependent variance (the r0 term — not Vasicek's), cross-checked against the exact noncentral-χ² sampler's empirical moments
  • Feller 2κθ ≥ σ² ⟺ strictly positive paths, transition df ≥ 2
  • σ→0 → the deterministic OU discount (O(σ²), scale-aware with a roundoff floor)

Curve & options

  • y = −ln P/τ round-trip; analytic forward vs central-difference (O(h²)); par-yield convention
  • Calibration: a loose-tolerance parameter round-trip (the Jacobian is ill-conditioned, cond ~1.5e3) and a tight-tolerance fitted-yields identity at the global minimum
  • Put-call parity for bond options (call & put computed independently, so it's a real test); no-arbitrage sandwich; vega-monotonicity

Worked examples

PYTHONPATH=. python examples/yield_curves.py        # Vasicek vs CIR term structures
PYTHONPATH=. python examples/affine_unification.py  # one ODE reproduces both closed forms
PYTHONPATH=. python examples/bond_option_and_mc.py  # bond option + parity + MC check

affine_unification.py output (the closed form IS the ODE solution):

CIR:   tau      closed form       affine ODE    rel err
         0.5   0.979077103015   0.979077103015    3.8e-14
           1   0.956751217294   0.956751217294    1.7e-13
           5   0.770281316614   0.770281316610    5.1e-12

bond_option_and_mc.py (parity to a hair, MC inside the band):

  put-call parity:  call - put = 0.03672898
                    P(0,T_B) - K P(0,T_O) = 0.03672898   (residual 1.4e-17)
  T=5:  MC = 0.822766 +/- 0.000146   closed = 0.822763   z = +0.02

How this was built

This repo was built with an adversarial multi-agent workflow before implementation:

  1. Design panel — three independent agents proposed the modules and identities from different angles (Vasicek-first, CIR-first, affine-&-testing-first).
  2. Synthesis — one canonical spec: 48 identities, precise signatures, 18 analytically-derived golden vectors.
  3. Adversarial verification — one agent per identity tried to refute it. 4 of 48 were corrected before any code: the MC honest-band trap, the CIR σ→0 roundoff floor, the calibration global-minimum qualifier, and the non-sign-definite dCall/dT_B (replaced by vega).
  4. Implementation against the verified spec — overflow-safe e^(−γτ) forms, _ln_A in log space, expm1/log1p throughout.
  5. Adversarial code review — reviewers per dimension (math, numerical, API, tests), findings verified before applying.

What's intentionally NOT here yet

  • v0.2.0 — Hull-White (time-dependent θ(t)) exact fit to an initial curve
  • v0.2.0 alt — CIR bond options (noncentral-χ² / Jamshidian on the CIR tree)
  • v0.3.0 — two-factor affine (G2++ / CIR2) and the affine ODE system
  • v0.3.0 alt — market-price-of-risk: the P-measure drift and term premia

Related repos

  • credit-lab — the CIR process here is its stochastic-intensity engine; same affine machinery, hazard instead of rate
  • fixedalt-lab — deterministic term-structure bootstrapping; this lab adds the stochastic dynamics
  • tvm-lab — the discounting spine
  • monte-carlo-lab, pde-lab — the simulation/PDE siblings the MC check and affine PDE connect to

References

  • Vasicek, O. (1977). An Equilibrium Characterization of the Term Structure. JFE.
  • Cox, J., Ingersoll, J., & Ross, S. (1985). A Theory of the Term Structure of Interest Rates. Econometrica.
  • Duffie, D. & Kan, R. (1996). A Yield-Factor Model of Interest Rates. Math. Finance. (Affine framework.)
  • Brigo, D. & Mercurio, F. (2006). Interest Rate Models — Theory and Practice. Springer.

License

MIT.

About

Stochastic short-rate term-structure models from first principles: Vasicek + CIR closed-form affine bond prices, the unifying affine ODE framework that reproduces both, Vasicek bond options, and calibration. Every formula identity-tested — the affine ODE IS the closed form. No rates library underneath.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages