Tier 2 · Internal Risk Management

Volatility Surface Construction — SVI / SABR Calibration Family

M-136 · lifecycle: monitoring · RAT-136-v1.0.1

Intended Use

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.


Components

Inputs, processing, outputs

data sources
DS-004 · DS-006 · DS-031 · DS-057
assumptions
A-060, A-061, A-062, A-063, A-281, A-282
engines
finview.L0.svi_vol_surface
finview.L0.sabr_surface
contracts
vol_surface_v1
dimensions
D2

Methodology & Mechanics

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

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

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

Limitations and Known Gaps

  1. 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) ≥ 0 butterfly 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").

  2. SABR surface uses nearest-cell lookup, not smooth interpolation. get_swaption_vol snaps 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).

  3. β 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.

  4. 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.

  5. 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_chain open-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 only logger.warning — they do not raise.

  6. Calibration-tolerance / β assumptions are literature defaults, not market-calibrated (BV-032 context honesty). A-281 (β) and A-282 (ε) are both expert_judgment with evidence: 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.

  7. FinView-resident, cross-repo. The engine source is not in InsModel; the snapshot adds [redacted] to sys.path before 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.yaml so 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 FinView finlib interface + cross-repo drift gate so a vol_surface.py signature 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, registry peer_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 Evidence

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

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.pyTestSVISlice, TestCalibrateSVI, TestSVIVolSurface, TestSABR, TestSABRSurface, TestCalibrateFromChain and edge-case coverage classes.


Change Log

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-06RAT-136-v1.0.0 remediation (COND-001 + COND-002). Reconciled the registry documentation_pack flags to disk (model_card / validation_evidence / change_logpresent), stamped lifecycle.last_validated_on: 2026-06-06, set peer_review.status: complete / signed_off_by / signed_off_on and confirmed ratification_ref: RAT-136-v1.0.0. Authored the validation-evidence pack at insightalm/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 log M-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 registering upstream_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.

2L Inventory Review

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.

HIGH INV-029 · P5 · validation-gap

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.

LOW INV-023 · P3 · proxy

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.

LOW INV-032 · cross-repo · cross-repo

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.


Validation Coverage

Per-tier expectations

Per MRM Framework §10.2 + §10.3, this model's regulatory_frameworks tag list activates the following overlays:

asop_56 sr_26_2 internal
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

Ratification

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.