Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

g2pp-lab

A two-factor additive Gaussian (G2++) interest-rate model from first principles in pure Python + NumPy/SciPy. The two correlated Ornstein–Uhlenbeck factors, the convexity functional and exact curve fit, zero-coupon bond options, caps/floors, and — the centrepiece — a European swaption priced three independent ways that agree. Every formula is derived from the SDEs; no rates library underneath.

67/67 tests verifying algebraic identities — the exact curve fit, the forward-bond martingale that pins the measure change, put-call and payer-receiver parities, the one-factor (Hull–White) collapse, and the flagship 1-D == 2-D == Monte-Carlo swaption agreement — never a comparison to QuantLib or any library.

$ pytest tests/
=========================== 67 passed in 21.0s ============================

Every identity in this repo was adversarially verified by a multi-agent workflow before a line of code was written — 60 claims were independently checked and 10 had errors corrected (the B(z,τ)→τ limit needs an exact z=0 branch, not a finite-z test pretending to hit 1e-12; the bond-option vega is sign-definite only in the total Σ, not in σ/η individually — with ρ<0 the cross term can flip dΣ²/dσ negative; cap/floor and swaption strike monotonicity is non-strict deep OTM where prices underflow; the V small-a series is mandatory — the naïve closed form returns exactly 0.0 at a=1e-6; and every Monte-Carlo identity is an honest 4σ band, never 1e-12). A second adversarial code review then surfaced 16 findings (3 medium, 4 low, 9 nit), all verified by running the code and applied — including a V cross-block that cancelled to a negative variance at tiny mean-reversion speeds, and a swaption quadrature box that silently truncated deep-OTM payoffs. See "How this was built".

Why this exists

Two-factor rate models are where the textbook closed forms stop being one-liners and most people reach for a library. This repo is the opposite. The short rate is r = x + y + φ(t) with x, y correlated Gaussian factors, and:

  • the bond price P(t,T) = P^M(0,T)/P^M(0,t) · exp(A) reprices today's curve exactly (P(0,T) == P^M(0,T) to the last bit) — the defining property of the model, tested directly;
  • the riskiest piece of the whole model — the change to the T-forward measure — is pinned by an identity, the forward-bond martingale E^{Q^T}[P(T,S)] = P^M(0,S)/P^M(0,T), so a sign slip in μ_x/μ_y fails a test;
  • and the swaption, which has no Jamshidian decomposition in two factors, is priced by the Brigo–Mercurio 1-D semi-analytic integral and proven by an independent 2-D quadrature and a full path Monte Carlo.

It extends the rates pillar of the portfolio one factor further: the v0.3.0 roadmap item that shortrate-lab left open ("two-factor affine / G2++") realised.

The 7 modules

Module Topic Headline functions
factors.py the Gaussian factor layer G2Params, B, cond_var_x/y, cond_cov_xy, chol2x2, V, phi, qt_moments
curve.py market curve P^M(0,·) ZeroCurve, FlatCurve, QuadraticForwardCurve, NelsonSiegelCurve
bond.py curve-consistent bond bond_A, bond_price, short_rate, ahat
options.py ZCB options option_Sigma, option_variance, zcb_call, zcb_put
capfloor.py caps/floors + swap caplet, floorlet, cap, floor, forward_swap_rate, swap_value
swaption.py the flagship swaption_bm, swaption_quad2d, swaption_mc, exercise_boundary
simulate.py exact-OU Monte Carlo simulate_factors, mc_discount_factor, mc_bond_price

API in 30 seconds

from g2pp import factors, curve, bond, options, swaption

p = factors.G2Params(a=0.5, b=0.1, sigma=0.01, eta=0.008, rho=-0.7)
crv = curve.QuadraticForwardCurve(c0=0.03, c1=0.005)      # f^M(0,T) = 3% + 0.5%·T

bond.bond_price(p, crv, 0.0, 5.0, 0.0, 0.0)               # 0.808560316  == P^M(0,5) exactly
options.zcb_call(p, crv, 0.0, 1.0, 3.0, K=0.9)            # 0.022391993   (put-call parity to 6e-17)

# the flagship: a 5y->2y payer swaption, priced two analytic ways + Monte Carlo
T, pay, acc, K = 5.0, [5.5, 6.0, 6.5, 7.0], [0.5]*4, 0.04
swaption.swaption_bm(p, crv, T, pay, acc, K)              # 0.0314287103  (Brigo-Mercurio 1-D)
swaption.swaption_quad2d(p, crv, T, pay, acc, K)          # 0.0314287103  (brute 2-D; rel 1e-13)

The algebraic identities we actually test

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

The curve fit & the measure change (the load-bearing ones)

  • P(0,T) == P^M(0,T) exactly (diff 0.0) on flat and non-flat curves — the model fits today's curve by construction (A(0,T)=0)
  • the forward-bond martingale E^{Q^T}[P(T,S)] == P^M(0,S)/P^M(0,T) (rel 1e-16) — the closed-form lognormal expectation that pins the sign of every Q^T drift; any flip in μ_x/μ_y breaks it
  • Σ² recomputed two independent ways (the variance-of-log-forward-bond decomposition and the verbatim 1/a³ Brigo–Mercurio form)

The swaption (flagship)

  • swaption_bm == swaption_quad2d to ~2e-13 — the 1-D semi-analytic reduction equals a brute 2-D adaptive quadrature with no shared price algorithm, so their agreement proves the reduction (there is no Jamshidian in two factors); holds even deep OTM (price ~1e-40) via a tail-aware box
  • swaption_mc within a 4σ band of the analytic price (full risk-neutral paths, exact OU steps)
  • payer − receiver == forward swap value (model-free, rel 1e-12)

Parities, limits & the one-factor collapse

  • put-call parity for ZCB options (call & put from independent N(·) forms, residual 6e-17); cap − floor == swap; par rate zeroes the swap
  • HW1F collapse: with η=ρ=0 the y- and cross-blocks of V and Σ² are literally 0.0, so the model reduces term-by-term to one-factor Hull–White
  • the four numerical limits: B(z,τ)→τ (exact z=0 branch), the V small-speed Taylor series (self- and cross-blocks), Σ→0 intrinsic, the χ-free chol2x2 at |ρ|=1
  • exact-OU Monte Carlo: E_Q[e^{-∫r}] == P^M(0,T) and the tower property, honest 4σ bands across multiple seeds

Worked examples

PYTHONPATH=. python examples/yield_and_curvefit.py      # P(0,T)=P^M(0,T), phi, the 2-factor correlation
PYTHONPATH=. python examples/bond_option_and_caps.py    # ZCB option parity + cap/floor = swap
PYTHONPATH=. python examples/swaption_three_routes.py   # 1-D vs 2-D vs Monte Carlo (FLAGSHIP)

swaption_three_routes.py (three methods, one number):

5y->2y payer swaption, semiannual, fixed 4%

  route                         price          vs BM
  Brigo-Mercurio 1-D       0.0314287103        --
  brute 2-D quadrature     0.0314287103   1.3e-14  (rel)
  path Monte Carlo         0.0314037683   z = -1.10  (+/-2.3e-05)

  payer - receiver = +0.03129165
  forward swap     = +0.03129165   (model-free; residual 1.3e-16)

How this was built

The portfolio's full adversarial multi-agent workflow, before and after implementation:

  1. Design panel — three independent agents proposed the modules and identities from different angles (factors-&-curve-first, bonds-options-caps, swaption-&-testing-first).
  2. Synthesis — one canonical spec: 60 identities, precise signatures, golden vectors derived from the math.
  3. Adversarial verification — one agent per identity tried to refute it, checking numerically against Monte Carlo / quadrature. 10 of 60 were corrected before any code (tolerance honesty, the vega sign qualification, the B and V limit branches, the deep-OTM monotonicity caveats).
  4. Implementation against the verified spec — expm1/log1p throughout, dedicated Taylor branches for the cancellation-prone V blocks, the bond price in log space, the swaption boundary by a bracketed root-find.
  5. Adversarial code review — reviewers per dimension (math / numerical / API / tests), 16 findings all verified by running the code and applied: the V cross-block needed its own Taylor branch (it cancelled to a negative variance at tiny speeds — the self-blocks were guarded, the cross-block was not); the swaption_quad2d ±8σ box silently truncated deep-OTM exercise regions (made tail-aware); dblquad's absolute floor replaced by pure relative control; plus API/test-honesty polish.

What's intentionally NOT here yet

  • v0.2.0 — calibration to a cap/swaption vol surface (the analytic prices are all here; what's missing is the optimiser and a market-data adapter)
  • v0.2.0 alt — the x/y ↔ principal-components reparametrisation and the P-measure drift / term premia
  • v0.3.0 — Bermudan swaptions (the 2-D exercise region is already the hard part; this adds backward induction on a lattice/regression)
  • v0.3.0 alt — CIR2 / multi-factor affine via the Riccati ODE system (the shortrate-lab affine spine, lifted to two factors)

Related repos

  • shortrate-lab — the one-factor parent (Vasicek/CIR + the affine framework); G2++ is its v0.3.0 roadmap item, and the η=ρ=0 collapse here reproduces its Hull–White limit
  • credit-lab — the same Gaussian machinery drives a hazard-rate intensity instead of a short rate
  • tvm-lab, fixedalt-lab — the discounting spine and the deterministic term structure this adds dynamics to
  • monte-carlo-lab — the simulation siblings the exact-OU MC cross-checks connect to

References

  • Brigo, D. & Mercurio, F. (2006). Interest Rate Models — Theory and Practice, ch. 4 (the G2++ model). Springer.
  • Hull, J. & White, A. (1994). Numerical Procedures for Implementing Term Structure Models II: Two-Factor Models. J. Derivatives.
  • Nelson, C. & Siegel, A. (1987). Parsimonious Modeling of Yield Curves. J. Business. (The smooth test curve.)

License

MIT.

About

The two-factor Gaussian (G2++) short-rate model from first principles: exact curve fit, ZCB options, caps/floors, and a European swaption priced three independent ways that agree (Brigo-Mercurio 1-D, 2-D quadrature, Monte Carlo). 67 identity tests, adversarially verified. No rates library underneath.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages