regit-svi 2.0.0

Arbitrage-free SVI volatility surfaces in pure Rust. Raw, Jump-Wings and SSVI parametrisations, calibration, and static-arbitrage checks. Zero dependencies.
Documentation

regit-svi

Auditable SVI volatility smiles and surfaces in safe, std-only Rust.

Crates.io docs.rs License Rust

regit-svi models total implied variance with Raw SVI, SVI Jump-Wings, and SSVI; calibrates slices and SSVI surfaces; evaluates the implied continuous risk-neutral density; and reports static-arbitrage evidence without turning a finite numerical scan into a global certificate.

Why this crate exists

Option markets quote a sparse set of implied volatilities, while pricing and risk systems need a continuous smile at each maturity and a coherent surface between maturities. Interpolation or independent slice fits can introduce two economically material defects:

  • negative call convexity, equivalently a negative continuous density at some strike; and
  • decreasing total variance with maturity at fixed forward log-moneyness.

SVI gives those questions a compact analytic structure. The implementation keeps the model, diagnostics, calibration termination, and strength of the supporting evidence visible to the caller. A theorem, a sufficient condition, a theorem-guided binary64 search, and a bounded grid are represented as different evidence kinds.

Quick start

[dependencies]
regit-svi = "2.0.0"

Upgrading from 1.x? Read the 2.0 migration guide before changing imports or interpreting arbitrage results.

use regit_svi::{
    ArbitrageStatus, Quote,
    calibration::calibrate_slice,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // One maturity: (forward log-moneyness, total variance, fit weight).
    let quotes = [
        Quote::new(-0.20, 0.0512, 1.0)?,
        Quote::new(-0.10, 0.0432, 1.0)?,
        Quote::new( 0.00, 0.0400, 1.0)?,
        Quote::new( 0.10, 0.0420, 1.0)?,
        Quote::new( 0.20, 0.0480, 1.0)?,
    ];

    let fit = calibrate_slice(&quotes)?;
    let slice = fit.slice();
    let report = fit.report();

    let total_variance = slice.total_variance(0.05);
    let implied_vol = slice.implied_vol(0.05, 1.0)?;

    println!("w={total_variance:.6}, vol={implied_vol:.4}");
    println!("RMSE={:.3e}", report.residuals().rmse());
    println!(
        "status={:?}, evidence={:?}",
        report.arbitrage_assessment().status(),
        report.arbitrage_assessment().evidence(),
    );
    assert_eq!(
        report.arbitrage_assessment().status(),
        ArbitrageStatus::NoViolationDetected,
    );
    Ok(())
}

NoViolationDetected must be read together with its evidence. For a regular Raw slice it records a theorem-guided numerical search, not a formal analytic certificate. See examples/quickstart.rs for the slice, conversion, density, SSVI-calibration, and surface workflow.

Coordinates and parametrisations

SVI uses forward log-moneyness k = ln(K/F) and total Black implied variance w = sigma_B^2 t. The helper log_moneyness evaluates ln(K) - ln(F) so a representable strike and forward do not overflow merely because K/F does.

Parametrisation Implemented form Intended use
Raw SVI w(k)=a+b[rho(k-m)+sqrt((k-m)^2+sigma^2)] Single-slice model, derivatives, calibration, and diagnostics
SVI Jump-Wings (v, psi, p, c, v_tilde) at maturity t ATM variance/skew and normalized wing observables
SSVI w(k,theta)=theta/2[1+rho phi k+sqrt((phi k+rho)^2+1-rho^2)] A shared smile family over an ATM-variance term structure

Constructors validate finiteness and the declared model domain. Invariant- bearing fields are private and exposed through accessors. Raw-to-Jump-Wings is defined for a positive ATM variance, including flat smiles, but the inverse is not globally identifiable: flat wings and an ATM minimum are rejected rather than assigned arbitrary Raw parameters. Every positive SSVI slice maps to Raw; a generic Raw slice has no SSVI inverse.

The low-level formula kernels intentionally remain infallible: Raw value and derivative methods, Phi::eval, Ssvi::total_variance, g, d_plus, d_minus, and the coordinate helpers accept raw f64. Callers must satisfy their documented finite/model-domain preconditions; otherwise IEEE-754 NaN or infinity may propagate. Constructors, conversions, diagnostics, density integration, and surface evaluation provide the validated/fallible workflow when an error value is required.

Evidence model

Every ArbitrageAssessment couples a three-way status to an evidence value:

Status Meaning
ViolationDetected A finite counterexample or failed analytic necessary condition was found.
NoViolationDetected The stated procedure completed without finding a violation in its stated scope.
Indeterminate Invalid support, non-finite evaluation, unresolved numerical boundary, or failure of a merely sufficient test prevents a conclusion.
Evidence Scope
AnalyticNecessaryAndSufficient A cited characterization on its declared domain; binary64 boundary tolerance is still reported.
AnalyticSufficient Passing proves the property; failing is inconclusive.
AnalyticNecessary Failing proves a violation; passing is not sufficient.
NumericalSearch Analytic reductions plus recorded root brackets and compactified numerical optimization.
NumericalScan A bounded, finite-resolution diagnostic only.

For regular non-flat Raw SVI, assess_raw follows the Martini–Mingone normalization, Fukasawa interval, two G2 roots, and compactified exterior search for the critical sigma. The last step is a global dyadic numerical search in IEEE-754 binary64. The cited paper does not prove the unimodality needed to promote a local optimizer to a proof, and the implementation does not use verified interval arithmetic. A clean result therefore carries NumericalSearch; a negative evaluated g outside the reported uncertainty is a conclusive witness.

The API treats other boundaries separately:

  • a positive flat Raw slice has the analytic Black constant-variance result;
  • a non-flat isolated zero is an analytic butterfly violation and a singular density input;
  • the strict right-wing slope condition makes calls vanish at infinite strike;
  • the separate strict left-wing condition controls unit continuous positive-strike mass; and
  • SSVI Theorem 4.2 evidence is sufficient, not necessary.

butterfly_scan and calendar_scan require ordered finite bounds and evaluate 401 points on the caller's range expanded by one log-moneyness unit on each side. A violation flag requires a sample below the scale-aware negative tolerance recorded in ScanEvidence; a minimum inside that boundary band is unresolved. Their minimum and optional nearby Brent crossing are useful diagnostics, but no result says anything about strikes outside that finite grid. ScanEvidence preserves the caller-requested domain, pre-expansion point count, expanded effective ScanConfig, successful sample count, whether refinement was attempted, and optional final root bracket/residual/evaluation evidence. RootEvidence also records the returned root and whether termination was an exact binary64 zero or bracket-width tolerance. Aggregated surface evidence adds represented-scan count, total sample count, and the selected scan whose per-scan configuration and refinement record are retained. The selected adjacent pair is ranked by semantic class—violation, then unresolved boundary, then clean—and then by its normalized minimum within that class.

Calibration

The crate exposes three calibration paths:

Path Numerical method Contract
quasi_explicit Convex inner weighted least squares in (a,d,c) over the Zeliade polytope; non-convex multi-start Nelder–Mead in (m,sigma) Deterministic heuristic; not a global optimizer
least_squares Levenberg–Marquardt with an analytic Raw Jacobian and positive-minimum transform Local refinement from a seed
calibration::surface Nelder–Mead over hard-feasible Heston-like or modified-power SSVI coordinates Final sufficient butterfly and necessary-and-sufficient calendar assessments

The default calibrate_slice runs quasi-explicit calibration, attempts an LM polish, and keeps the lower-RMSE successful result. Five positive-weight, sufficiently distinct strikes are required for a Raw fit. SSVI surface fitting requires strictly ordered maturities and non-decreasing positive ATM total variance. Heston-like fitting requires at least two positive-weight observations at distinct strikes; modified-power-law fitting requires at least three and at least two numerically distinct ATM-variance levels.

ConstraintMode::Constrained requires optimizer convergence plus the configured feasibility postconditions. ConstraintMode::BestEffort may return the best finite local result and records its actual termination and evidence. Reports include algorithm, attempted and selected starts, tolerance, iteration/evaluation counts, termination, weighted objective/RMSE/max-residual and quote diagnostics, parameterization/repair policy, recomputed domain/feasibility margins, and arbitrage assessments. Raw quasi-explicit reports also expose the selected normalized-design condition estimate. Iteration exhaustion and stagnation are not convergence.

Surfaces and density

Surface::from_slices accepts already ordered (maturity, RawSvi) pairs and rejects empty, non-positive, duplicate, or unordered maturities and decreasing ATM total variance. That ATM check is not a proof of global ordering between arbitrary Raw slices. Surface::calendar_assessment aggregates the bounded NumericalScan evidence for Raw backing and returns analytic Theorem 4.1 evidence for SSVI backing.

Inside the maturity grid, slice-backed surfaces interpolate linearly in total variance at fixed k. Outside the grid they hold implied variance w/t constant. SSVI-backed surfaces apply the same policy to the validated ATM total-variance term structure and then evaluate SSVI. These policies preserve calendar order when the relevant adjacent curves are globally ordered, but do not by themselves certify butterfly freedom.

Surface::total_variance and Surface::implied_vol return Result<_, ParamError> and reject non-finite log-moneyness or non-positive / non-finite maturity.

risk_neutral_density returns the continuous density in log-strike, not the g factor. integral and density_report use composite Simpson quadrature on caller-supplied finite bounds. They reject invalid panels and non-finite or singular evaluations. DensityError distinguishes exact non-positive variance from a tiny positive, scale-aware IllConditionedVariance. DensityReport::integration_error is a nested-Simpson Richardson estimate for quadrature on the declared finite window; it does not estimate truncated tail mass. Its violation flag requires a sampled g value below its scale-aware negative evaluation band; a tiny negative minimum density inside that band is unresolved and need not set the flag.

Architecture

src/
├── market/                  validated quotes, units, and conventions
├── smile/                   Raw, Jump-Wings, and conversions
├── surface/                 SSVI, term structures, and interpolation
├── calibration/             configs, reports, slice and surface fits
├── no_arb/                  butterfly/calendar diagnostics and evidence
├── density.rs               log-strike density and Simpson quadrature
├── error.rs                 typed domain/conversion/calibration failures
└── numerics/                crate-private Brent, Cholesky, LM, Nelder–Mead

Principal domain types are re-exported at the crate root; domain modules stay available for explicit imports. Private numerical routines cannot be used to bypass public model validation.

Testing and independent verification

cargo test --all-targets --locked
cargo test --doc --locked
cargo run --example quickstart
cargo bench

The Rust suite covers formula anchors, Raw/JW conversion degeneracies, SSVI slice identities and global envelopes, the Axel Vogt counterexample, strict tail and isolated-zero cases, bounded calendar failures, density mass and forward moment, published Zeliade curves, noisy and ill-conditioned fits, deterministic SplitMix64 suites, and proptest invariants. No hand-maintained test count is promised after release; use the test runner for the current inventory.

The 2.0.0 release baseline is:

Suite Passing tests
Inline unit tests 186
Main integration suite 32
High-precision oracle integration 1
Published-source integration 7
Rustdoc examples 81

tools/oracle independently generates decimal-string fixtures at 100 decimal digits with mpmath==1.4.1. The generator covers Raw/JW/SSVI, g, density, Heston small arguments, Martini–Mingone roots/search anchors, and extreme finite inputs. Rust checks each fixture with recorded absolute, relative, and ULP tolerances. Root records come from explicit sign-changing brackets and deterministic bisection; neither the oracle nor higher precision is presented as a global optimization certificate.

The published source archive includes the oracle so reviewers can reproduce the fixtures independently. It is development tooling only: Cargo never compiles or executes it as part of the library, tests, examples, or benchmarks.

Toolchain and dependencies

  • Library MSRV: Rust 1.85; edition 2024; safe, std-only Rust.
  • Normal dependencies: none.
  • Build dependencies: none.
  • Development dependencies only: approx ^0.5, proptest ^1, and criterion ^0.8 with html_reports.
  • Criterion 0.8 requires Rust 1.86 or newer, so all development targets run on the current stable toolchain while the shipped library is checked at 1.85.
  • The independent Python oracle pins mpmath==1.4.1; Ruff 0.15.22 is exact-pinned development tooling.

CI separately checks the empty normal/build Cargo graph, the full development graph with cargo-deny, actual dependency use and unlinked sources with cargo-shear 1.13.3 --deny-warnings --check-test-targets, strict rustdoc, current-stable all-target tests, the library MSRV, and a release WASM library build. See CONTRIBUTING.md for the local gate and BENCHMARKS.md for measurement policy.

Documentation

References

The primary mathematical sources are Gatheral–Jacquier (2014), Martini– Mingone (2022), Martini–De Marco / Zeliade (2012 revision), Lee (2004), and Breeden–Litzenberger (1978). Full citations and a formula-to-module-to-test matrix are in MATH.md.

License

Apache License 2.0. See LICENSE and NOTICE.

Copyright 2026 Regit.io — Nicolas Koenig

Part of Regit OS — the operating system for investment products. From Luxembourg.