Volatility Surface Construction — SVI / SABR Calibration Family Calibrate parametric volatility surfaces (SVI for equity, SABR for interest-rate) consumed by option and swaption pricers.
Family-level model covering statistical calibration of vol surfaces: Stochastic Volatility Inspired (SVI) parametric form for equity vol; SABR (Stochastic Alpha Beta Rho) for interest-rate vol. Output surfaces consumed by M-131 Equity Options and M-132 IR/FX Derivatives. Implementations: 2 FinView L0 calibration engines.
Inputs, processing, outputs
Methodology
M-136 is the implied-volatility surface family. It is FinView-resident:
the engine code lives entirely in finlib/vol_surface.py ([redacted])
and is exposed to the governance ecosystem through two bound engines —
finview.L0.svi_vol_surface (SVIVolSurface, assumption A-282) and
finview.L0.sabr_surface (SABRSurface, assumption A-281). InsModel and the
swaption/CVA stack consume this surface; they do not own it. The two
parameterizations cover the two asset classes the platform prices options on:
1. SVI (Stochastic Volatility Inspired) — equity smile. Each expiry slice is
parameterized in total variance w(k) = σ²·T as a function of log-moneyness
k = log(K/F):
w(k) = a + b·[ ρ·(k − m) + √((k − m)² + σ²) ]
with five parameters (SVISlice.__init__): a (overall variance level), b
(slope / wing steepness, b ≥ 0), ρ (rotation, the skew, |ρ| < 1), m
(horizontal translation of the smile minimum), and σ (ATM curvature, σ > 0).
The implied vol at any (k, T) is √(w(k)/T) (SVISlice.implied_vol), guarded
to return 0.0 for non-positive variance or maturity.
Calibration (calibrate_svi_slice) maps market strikes/IVs to model total
variance and minimizes a (optionally vega-weighted) sum of squared total-variance
residuals via SciPy L-BFGS-B, with box bounds enforcing the parameter
validity domain (a ≥ 0, b ∈ [0.001, 5], ρ ∈ (−0.999, 0.999),
m ∈ [−2, 2], σ ∈ [0.001, 5]). Initial guess seeds a from the median market
total variance and a mild negative skew (ρ₀ = −0.3). The multi-expiry surface
(SVIVolSurface) stores one SVISlice per maturity and answers off-grid (K, T)
queries by linear interpolation of total variance between bracketing expiries
(get_vol), which is the standard calendar-arbitrage-free interpolation scheme,
clamping at the first/last calibrated expiry. A calibrate_from_chain path
ingests a yfinance-style options chain, OTM-filters calls (above forward) and
puts (below forward), open-interest-weights, and calibrates one slice per expiry
with ≥5 surviving quotes.
2. SABR (Hagan's approximation) — rates / swaption smile. Black implied vol is
computed in closed form (sabr_implied_vol) from four parameters — α (vol
level), β (CEV exponent, fixed not fitted), ρ (forward/vol correlation),
ν (vol-of-vol) — using the Hagan-Kumar-Lesniewski-Woodward 2002 singular
expansion, with a separate exact ATM branch (|F − K| < 1e-12) and the
z / χ(z) log-moneyness term for the wings. Calibration (calibrate_sabr) fits
(α, ρ, ν) at fixed β (default 0.5) by L-BFGS-B on squared Black-vol residuals.
The surface (SABRSurface) holds calibrated parameter dicts on an
(expiry, tenor) grid and serves a swaption vol by nearest-cell lookup plus a
fresh Hagan evaluation at the queried strike (get_swaption_vol).
Arbitrage discipline. Two structural protections are built in: (a) SVI's
bound set keeps each slice in the valid no-negative-variance region, and the
left-wing floor a + b·σ·√(1−ρ²) ≥ 0 is a necessary condition for butterfly
non-arbitrage on a slice; (b) total-variance linear interpolation across expiries
is calendar-arbitrage-free by construction. A full Durrleman butterfly
no-arbitrage check on each calibrated slice is not implemented — see
Limitation 1.
Key Assumptions and Their Justification
| ID | Name | Value | Derivation | Justification |
|---|---|---|---|---|
| A-282 | SVI fit residual tolerance | ε = 1e-4 (squared-error on log-strike grid) |
expert_judgment (numerical hyperparameter) | Gatheral-Jacquier (2014) literature default. Convergence tolerance on the parametric fit; controls how tightly SVI tracks the observed smile, not the smile shape. Sensitivity LOW. |
| A-281 | SABR β (moneyness elasticity) | β = 0.5 (lognormal-CEV midpoint), fixed |
expert_judgment (calibration hyperparameter) | Hagan et al. (2002) literature default; β=0 normal/Bachelier, β=1 lognormal/Black-Scholes, β=0.5 CEV midpoint common for IR vols. Within [0.3, 0.7], ATM vol shifts <2%. Sensitivity LOW. |
| (struct.) | SVI parameter bounds | a≥0, b∈[.001,5], ρ∈(−.999,.999), m∈[−2,2], σ∈[.001,5] |
code-enforced (L-BFGS-B box) | Keeps each slice inside the no-negative-variance validity domain; prevents pathological fits on sparse quotes. |
| (struct.) | Calendar interpolation | linear in total variance w between expiries |
code-enforced (get_vol) |
Standard calendar-arbitrage-free interpolation; w is monotone in T for a no-arb surface. |
| (struct.) | SABR surface interpolation | nearest (expiry, tenor) cell |
code-enforced (get_swaption_vol) |
Simple, deterministic grid lookup; acceptable on a dense calibrated grid but not smooth (Limitation 2). |
Registry-bound but NOT consumed by this engine (paper-binding disclosure).
The registry binds six assumptions to M-136 (A-060, A-061, A-062, A-063, A-281,
A-282), but the vol-surface engine consumes only A-281 and A-282 — verified
in code: finlib/vol_surface.py declares exactly @uses_assumptions(['A-282'])
on the SVI path and @uses_assumptions(['A-281']) on the SABR path, and a grep
of the module finds no equity-risk-premium / jump-intensity / jump-size /
vol-of-vol reference. The remaining four are SVJ / jump-diffusion parameters that
belong to the equity scenario generator, not the surface calibration:
| ID | Name (registry) | Value (registry) | Status on M-136 |
|---|---|---|---|
| A-060 | Equity risk premium | 6.0% (geometric, long-run) | Paper binding — not consumed. Scenario-generator input; the surface calibration uses only observed quotes. |
| A-061 | SVJ vol-of-vol | 0.30 | Paper binding — not consumed. Stochastic-vol/jump (SVJ) scenario parameter, not the SVI σ curvature. |
| A-062 | Jump intensity | 0.08 (8% annual) | Paper binding — not consumed. Jump-diffusion scenario parameter. |
| A-063 | Jump size (equity) | mean −12%, sd 5% | Paper binding — not consumed. Jump-diffusion scenario parameter. |
This disclosure makes the existing paper binding visible (mirroring how INV-011 paper bindings are surfaced elsewhere). Rebinding the registry so the components list matches what the engine actually consumes (keep A-281/A-282, unbind A-060..A-063) is an output-/registry-changing item left for ratification — see the note under Limitations.
Parameterization choice. SVI is used for equities because it natively
encodes the equity skew (ρ < 0) and recovers a smooth, near-arbitrage-free
smile from as few as five OTM quotes; SABR is used for rates because its
parameters map onto swaption-market intuition (level/skew/convexity) and Hagan's
closed form makes per-strike evaluation effectively free inside a calibration
loop. Calibration inputs are purely market option quotes (strikes, IVs,
forward, T) — no fundamental/firm data enters, so the engine is BV-032 immune.
Arbitrage-free constraints are enforced structurally (bounds + total-variance
interpolation) rather than as hard post-fit rejections; the load-bearing gap is
the absent per-slice butterfly check (Limitation 1). β is held fixed, never
calibrated (A-281) — a deliberate identifiability choice, since (α, β) are
jointly under-determined from a single smile.
Output Snapshot
Deterministic dual-calibration run of the FinView vol-surface engines —
reproducible, requires no live market data (python scripts/model_snapshots.py
M-136; backed by tests/test_cva_vol_surface.py). Both calibrations recover a
known parameter set from a closed-form synthetic smile, so the snapshot doubles
as a round-trip recovery check. No yfinance options chain, FRED, or live quote
feed is touched.
SVI input: synthetic 5-strike equity smile (K = 90/95/100/105/110, F = 100,
T = 1.0y) generated from a known slice a=0.04, b=0.10, ρ=−0.30, m=0.0, σ=0.15.
SABR input: synthetic 5-strike Black smile (K = 2/3/4/5/6%, F = 4.00%,
T = 1.0y) from known params α=0.02, β=0.5, ρ=−0.30, ν=0.40.
| output | value | meaning |
|---|---|---|
| SVI a / b | 0.045554 / 0.081562 | calibrated variance level / wing slope |
| SVI ρ / m / σ | −0.301260 / 0.009710 / 0.111917 | skew / translation / curvature (ρ recovered to 3dp) |
| iv ATM (K=100) | 0.234424 | calibrated implied vol at the money |
| iv downside wing (K=90) | 0.247938 | OTM-put vol > ATM → negative skew, as expected |
| SVI calibration RMSE | 8.88e-05 | fitted-vs-market IV residual (well inside ε=1e-4) |
| left-wing var floor | 0.054258 | a + b·σ·√(1−ρ²) > 0 → butterfly-floor satisfied |
| skew check iv₉₀ > iv₁₀₀ | True | smile orientation correct |
| SABR α / β / ρ / ν | 0.020000 / 0.50 / −0.299999 / 0.400001 | params recovered near-exactly (β fixed) |
| iv ATM (K=F=4.00%) | 0.101014 | Black ATM swaption vol |
| iv +100bp payer (K=5.00%) | 0.095724 | OTM payer vol (downward skew, ρ<0) |
| SABR calibration RMSE | 4.56e-08 | fitted-vs-market vol residual (near machine-exact) |
The SVI fit recovers the seed parameters to ~3 decimals and reproduces the input
smile to an RMSE of ~9e-5 IV points — comfortably below the A-282 tolerance —
while the necessary butterfly-floor quantity stays strictly positive. The SABR
fit is essentially exact (RMSE ~5e-8) because, with β fixed at its
generating value, the remaining (α, ρ, ν) are well-identified from a 5-strike
smile. Identical output was produced on both the FinView venv and the InsModel
(ecosystem/InsModel/Models/.venv) venv.
Captured 2026-06-04 · deterministic, no live data.
Limitations and Known Gaps
-
No per-slice butterfly no-arbitrage check. Only the SVI parameter bounds and the left-wing variance floor are enforced; the engine does not run a full Durrleman /
g(k) ≥ 0butterfly density check across the strike range. A calibrated slice can in principle imply a locally negative risk-neutral density in the wings without being rejected. The class docstring states this explicitly ("no butterfly arbitrage check on calibrated slices"). -
SABR surface uses nearest-cell lookup, not smooth interpolation.
get_swaption_volsnaps to the closest(expiry, tenor)grid cell and evaluates Hagan there — there is no bilinear or parameter interpolation between cells, so the surface is piecewise-constant in parameter space across the grid (vol is still continuous in strike within a cell). -
β is fixed, never calibrated (A-281). SABR
βis held at its default (0.5). The(α, β)pair is jointly under-identified from a single smile, so this is deliberate, but it means the surface cannot adapt the moneyness elasticity to a market that is genuinely closer to normal (β→0) or lognormal (β→1) without a manual override. -
Hagan approximation degrades in known regimes. The closed form can break down for very low or negative rates and for high vol-of-vol; the docstring flags this. There is no fallback to a more accurate (e.g. arbitrage-free PDE) SABR in those regimes.
-
Calibration is unweighted by default and uses a fixed start point. Without a supplied weight vector SVI fits all strikes equally (vega weighting is opt-in via
calibrate_from_chainopen-interest weights); SABR is always unweighted. The single fixed L-BFGS-B start (ρ₀=−0.3) is robust on the smiles tested but has no multi-start protection against local minima on awkward real-market smiles, and convergence failures onlylogger.warning— they do not raise. -
Calibration-tolerance / β assumptions are literature defaults, not market-calibrated (BV-032 context honesty). A-281 (β) and A-282 (ε) are both
expert_judgmentwithevidence: null; the planned DS-057 (equity IV) and DS-058 (swaption smile) fetchers that would replace these defaults with observed strike-grid IVs are not yet landed, so the surface is currently validated only on synthetic and live-chain-shaped inputs, never against a curated historical smile dataset. This is an honestly under-built part of the model. -
FinView-resident, cross-repo. The engine source is not in InsModel; the snapshot adds
[redacted]tosys.pathbefore importing. Any InsModel-side consumer (swaption pricer, CVA) depends on FinView remaining importable and version-aligned. No 10-K / firm-fundamental data is involved at any point.
Tracked for ratification (not applied in this documentation pass). The following are output-changing / modeling-code items left for ratification, noted here for transparency: a real Tier-2 §10.2 annual back-test of calibrated SVI/SABR surfaces against curated historical market smiles (gated on the DS-057 equity-IV / DS-058 swaption-smile fetchers landing); unbinding A-060/A-061/A-062/A-063 from M-136 in
model_registry.yamlso registry components match what the engine consumes (INV-023 / P2 paper-binding pattern); adding a per-slice Durrleman butterfly no-arbitrage check, smooth SABR-surface interpolation, multi-start calibration and raise-on-non-convergence (INV-023); a packaged/versioned FinViewfinlibinterface + cross-repo drift gate so avol_surface.pysignature change fails loudly for InsModel consumers (INV-032). (The Tier-2 peer-level effective-challenge sign-off is now recorded — RAT-136-v1.0.0, registrypeer_review.status: complete,ratification_ref: RAT-136-v1.0.0— and is no longer a tracked-open item; the historical-smile back-test remains gated as above.)
Validation Packet
| Check | Where | Result |
|---|---|---|
| SVI round-trip recovery | tests/test_cva_vol_surface.py::TestCalibrateSVI::test_calibrate_recovers_vols |
fitted IVs match input to atol=0.01 |
| SVI smile orientation (ρ<0 → put-side richer) | ::TestSVISlice::test_skew |
passes |
| SVI total-variance positivity / zero-T guard | ::TestSVISlice::test_total_variance_atm, test_zero_maturity |
passes |
| SVI surface interpolation / extrapolation | ::TestSVIVolSurface*, ::TestSVIVolSurfaceExtrapolation |
passes (incl. before-first / beyond-last expiry) |
| SVI chain calibration (OTM filter, ≥5 quotes) | ::TestCalibrateFromChain |
calibrates valid expiry, skips past/invalid/too-few |
| SABR round-trip recovery | ::TestSABR::test_calibrate |
params recovered (snapshot RMSE ~5e-8) |
| SABR ATM / OTM / invalid-input guards | ::TestSABR::test_atm_vol, test_otm_vol, test_invalid_inputs |
passes (F/K/T/α ≤ 0 → 0.0) |
| SABR surface nearest-cell selection | ::TestSABRSurfaceNearestCell |
distinct cells → distinct vols |
| Snapshot determinism (dual-venv) | snap_M_136 run on FinView + InsModel venvs |
byte-identical output |
| Engine module coverage | pytest tests/test_cva_vol_surface.py |
52 passed; finlib/vol_surface.py at 97% |
Suite result: 52 passed in 2.20s. Uncovered vol_surface.py lines are
non-convergence logger.warning branches (94, 294) and a few interpolation
guard branches — i.e. defensive paths, not core math.
References
Literature:
- J. Gatheral, A parsimonious arbitrage-free implied volatility parameterization
(Global Derivatives 2004) — the SVI total-variance form w(k)=a+b[ρ(k−m)+√((k−m)²+σ²)].
- J. Gatheral & A. Jacquier, Arbitrage-free SVI volatility surfaces,
Quantitative Finance 14(1), 2014 — SVI no-arbitrage conditions and the
butterfly/calendar constraints (basis for the residual-tolerance default A-282).
- P. Hagan, D. Kumar, A. Lesniewski, D. Woodward, Managing Smile Risk,
Wilmott Magazine, 2002 — the SABR model and the singular-expansion implied-vol
approximation implemented in sabr_implied_vol (basis for the β default A-281).
Engine / code:
- finlib/vol_surface.py ([redacted]) — SVISlice,
calibrate_svi_slice, SVIVolSurface, sabr_implied_vol, calibrate_sabr,
SABRSurface.
- Engine registry: finview.L0.svi_vol_surface and finview.L0.sabr_surface
(insightalm/modelling/engine_registry.yaml, tier-2-gate, owner
engine-owner-foundation, both model_membership: [M-136]).
- Assumptions: A-281 (SABR β) and A-282 (SVI ε) in
insightalm/modelling/assumption_domains.yaml (PL-07 closure 2026-05-11).
Tests:
- tests/test_cva_vol_surface.py — TestSVISlice, TestCalibrateSVI,
TestSVIVolSurface, TestSABR, TestSABRSurface, TestCalibrateFromChain
and edge-case coverage classes.
Change Log
Card change history. Code-side change history lives in git log of the component files.
The full governance change log (with the remediation diff and lineage) is at
M-136-changes.md.
- 2026-06-06 — RAT-136-v1.0.0 remediation (COND-001 + COND-002). Reconciled the
registry
documentation_packflags to disk (model_card/validation_evidence/change_log→present), stampedlifecycle.last_validated_on: 2026-06-06, setpeer_review.status: complete/signed_off_by/signed_off_onand confirmedratification_ref: RAT-136-v1.0.0. Authored the validation-evidence pack atinsightalm/modelling/validation_evidence/M-136/v1.0.0/README.md(52-test suite + deterministic round-trip snapshot = accepted Tier-2 form) and the governance change logM-136-changes.md. Snapshot re-run 2026-06-06 reproduces byte-identical (SVI RMSE 8.88e-05, SABR RMSE 4.56e-08); no output-changing fix applied, so no gold re-freeze. No validation results or model outputs were fabricated (Decision 053 §4). - 2026-05-08 — stub created from registry data per Decision 023 Phase 5 / B-07.
- 2026-06-06 — code-grounded documentation-completeness pass against
finlib/vol_surface.py+ the M-136 registry entry + assumption_domains.yaml. Added two missing gold-standard sections: Standards Coverage (ASOP 56 cross-cutting; SR 26-2 MRM regime layer; no NAIC/GAAP accounting standard prescribes the calibration — basis is the cited Gatheral 2004 / Gatheral-Jacquier 2014 / Hagan 2002 literature) and Dependencies (no upstream models — market-quote inputs only, BV-032 immune; downstream consumers M-131 + M-132, both registeringupstream_models:[M-136,M-137]). Disclosed the paper binding in Key Assumptions: A-060/A-061/A-062/A-063 are registry-bound to M-136 but NOT consumed by the engine (verified:@uses_assumptions(['A-282'])/['A-281']only; grep finds no ERP/jump/vol-of-vol reference) — they are SVJ/jump-diffusion scenario-generator parameters. Added DS-057 (planned) to Components data sources to match the registry. Noted gated/ratification items (real back-test, registry rebind, butterfly/interpolation/multi-start, cross-repo drift gate, Tier-2 sign-off) under Limitations. No model outputs, back-test numbers, or validation results were fabricated or changed.
Open findings (3)
Independent 2nd-line review (INV-2026-06) — implemented capability vs registered scope. Each carries a recommended fix and is tracked in insightalm-mrm until closed.
Validation evidence + change logs missing across most of the inventory
Only M-001/M-020/M-050 carried full documentation packs before this pass. Most models record validation_evidence: missing and change_log: missing with peer_review: pending. Gold tests freeze behaviour but many assert only structural invariants (e.g. reserve>0), not correctness against external truth. The flagship T0-vs-10-K match is circular (BV-032).
Recommendation: For each Tier-1 model: produce a validation-evidence pack (back-test vs disclosed results once BV-032 re-calibration lands, sensitivity suite, challenger comparison), a change log, and a 2L ratification. Sequence behind BV-032 (firm-data) for anything needing 10-K reconciliation.
Vol surface: no butterfly arb-check; SABR beta fixed; nearest-cell interpolation
No per-slice Durrleman butterfly no-arbitrage check (only bounds + a left-wing floor); SABR beta is fixed (A-281), never calibrated; the SABR surface is nearest-cell lookup with no smooth interpolation; calibration failures only warn, never raise; single fixed optimizer start. The DS-057/058 market-smile fetchers are not landed, so the surface has never been validated against a curated historical smile set.
Recommendation: Add a butterfly/calendar arb-free check, smooth SABR-surface interpolation, multi-start calibration, and validate against the DS-057/058 historical smiles once landed.
FinView-resident pricing/CECL engines reached cross-repo with no drift gate
M-040 (CECL) and M-130-137 (asset pricing) are FinView-resident, consumed by InsModel only via sys.path insertion. No packaged interface or drift gate — a finlib signature change silently breaks the InsModel snapshot harness / consumers.
Recommendation: Define a packaged interface (or a versioned contract) for the FinView pricing library and a CI drift gate, so cross-repo consumers fail loudly on signature change. Tie to D041 ownership.
Per-tier expectations
Per MRM Framework §10.2 + §10.3, this model's regulatory_frameworks tag list activates the following overlays:
| component | tier-2 expectation | status |
|---|---|---|
| Registry entry | required | present |
| Model card (§10.5 doc pack) | required | present |
| Validation evidence | required | present |
| Change log | required | present |
| Independent effective challenge (2L) | required | attested |
Ratified — RAT-136-v1.0.1
Latest ratification on file: RAT-136-v1.0.1. Authored by 2L (mrm-peer-reviewer) per Decision 028 charter §5 Pattern A.