svi-py
Stochastic volatility inspired (SVI) parametrizations of the implied volatility surface in Python — plus the SABR stochastic volatility model.
Given a panel of contemporaneous European call and put option prices across strikes and maturities, svi-py calibrates smooth, arbitrage-aware total variance surfaces using the SVI family of models and SABR. It handles the full pipeline: implied vol extraction, forward estimation via put-call parity, OTM leg selection, and per-slice calibration with configurable no-arbitrage constraints.
Installation
pip install svi-py
Requires Python >= 3.13.
Quick start
You need a DataFrame with columns for strike prices, implied volatilities (or raw option prices from which to compute them), time to maturity, and an implied forward price. A typical workflow:
import numpy as np
import pandas as pd
from pysvi import SVI, get_model, calibrate_slice, apply_slice, ArbitrageFreedom
# Suppose df_slice is a single-maturity cross-section with columns:
# strike, iv, maturity, implied_forward
model = get_model("svi")
params = calibrate_slice(df_slice, model)
# Generate fitted IVs and residuals
fitted = apply_slice(df_slice, params, model)
print(fitted[["strike", "iv", "fitted_iv", "residual_iv"]])
For SSVI/eSSVI, pass the ATM total variance as an extra argument:
model = get_model("ssvi")
theta = float(np.nanmin(df_slice["iv"] ** 2 * df_slice["maturity"]))
params = calibrate_slice(df_slice, model, theta=theta)
For jump-wings, pass the time to expiry:
model = get_model("jw")
T = float(df_slice["maturity"].iloc[0])
params = calibrate_slice(df_slice, model, T=T)
DirectSVI requires no extra arguments — it fits directly from log-moneyness and total variance:
model = get_model("dsvi")
params = calibrate_slice(df_slice, model)
For SABR, pass the time to expiry and the forward, and optionally fix the CEV exponent β (default 0.5):
model = get_model("sabr")
T = float(df_slice["maturity"].iloc[0])
F = float(df_slice["implied_forward"].iloc[0])
params = calibrate_slice(df_slice, model, T=T, F=F, beta=1.0) # beta=1 for FX/equity
Where do the inputs come from?
svi-py expects you to already have implied volatilities and forward prices. If you're starting from raw option prices, the library provides helpers:
compute_ivs_vectorizedcomputes Black-Scholes-Merton implied vols from option mid-prices viapy_vollib.calculate_implied_forwardestimates the forward price from put-call parity: $F = K + e^{rT}(C - P)$.choose_legselects the OTM leg (calls for $K \geq F$, puts for $K < F$) for cleaner vol quotes.
You need a panel of contemporaneous call and put option prices across multiple strikes for at least one maturity. The richer the strike grid, the better the calibration.
Parametrizations
All parametrizations work in total variance space: $w(k) = \sigma^2(k) \cdot T$, where $k = \log(K/F)$ is log-moneyness.
Raw SVI
The original Gatheral (2004) parametrization with 5 free parameters:
$$w(k) = a + b\left[\rho(k - m) + \sqrt{(k - m)^2 + \sigma^2}\right]$$
| Parameter | Meaning | Constraint |
|---|---|---|
| $a$ | overall variance level | $a \geq 0$ |
| $b$ | slope / curvature scale | $b > 0$ |
| $\rho$ | skew (correlation) | $|\rho| < 1$ |
| $m$ | log-moneyness shift | unconstrained |
| $\sigma$ | vol-of-vol (smile width) | $\sigma > 0$ |
Maximum flexibility (5 degrees of freedom per slice). No automatic arbitrage guarantees beyond soft parameter bounds.
SSVI (Surface SVI)
Gatheral & Jacquier (2014). Reduces to 2 free parameters per slice by fixing the ATM total variance $\theta = \sigma_{\text{ATM}}^2 T$:
$$w(k;\theta) = \frac{\theta}{2}\left[1 + \rho,\varphi(\theta),k + \sqrt{\left(\varphi(\theta),k + \rho\right)^2 + 1 - \rho^2}\right]$$
where $\varphi(\theta) = \eta / \sqrt{\theta}$ is the curvature function.
| Parameter | Meaning | Constraint |
|---|---|---|
| $\theta$ | ATM total variance (fixed input) | $\theta > 0$ |
| $\rho$ | skew | $|\rho| < 1$ |
| $\eta$ | curvature scale | $\eta > 0$ |
Guarantees no butterfly arbitrage by construction for each fixed $\theta$.
eSSVI (Extended SSVI)
Extends SSVI with maturity-dependent skew via a $\rho(\theta)$ term structure:
$$\rho(\theta) = \text{clip}!\left(\rho_0 + \rho_1 \left(\frac{\theta}{\theta_{\text{ref}}}\right)^\alpha,; -1,; 1\right)$$
The total variance formula is the same as SSVI but with $\rho \to \rho(\theta)$. This adds 4 parameters globally ($\rho_0, \rho_1, \alpha, \eta$) and enables realistic calendar skew evolution across maturities. $\theta_{\text{ref}}$ is a reference ATM total variance (typically the median across slices) that normalises the power law.
DirectSVI
A closed-form SVI calibration method (Schadner, forthcoming) that linearises the SVI equation by rewriting it as a conic section (hyperbola) in $(k, w)$ space:
$$z_0 k^2 + z_1 w^2 + z_2 kw + z_3 k + z_4 w + z_5 = 0$$
The 6 conic coefficients $z$ are found by solving a quadratically constrained eigenvalue problem (hyperbola constraint $z_2^2 - 4z_0 z_1 > 0$):
- Build design matrices $D_2 = [k^2,; w^2]$ and $D_1 = [kw,; k,; w,; 1]$
- Compute scatter matrices $S_{22}, S_{21}, S_{11}$
- Solve $M\mathbf{a}2 = \lambda, C_1\mathbf{a}2$ where $M = S{22} - S{21}S_{11}^{-1}S_{21}^\top$ and $C_1 = \begin{pmatrix}0 & -2\-2 & 0\end{pmatrix}$
- Select eigenvector for smallest positive eigenvalue; recover remaining coefficients via $\mathbf{a}1 = -S{11}^{-1}S_{21}^\top\mathbf{a}_2$
Evaluation solves the conic for $w$ given $k$ via the quadratic formula. No iterative optimisation is needed, making this the fastest calibration method in the library.
| Parameter | Meaning |
|---|---|
| $z_0$ – $z_5$ | Conic section coefficients (normalised so $z_1 = 1$) |
Note: DirectSVI does not support penalty-based arbitrage enforcement (
NO_BUTTERFLY/NO_CALENDAR). OnlyArbitrageFreedom.QUASIis meaningful.
Reference: Schadner, W. "Direct Fit for SVI Implied Volatilities", Journal of Derivatives (forthcoming). See also wol-fi/directSVI.
Jump-Wings
The jump-wings parametrization (Gatheral 2004) re-expresses SVI in terms of financially interpretable quantities:
| Parameter | Meaning |
|---|---|
| $v_t$ | ATM variance $\sigma_{\text{ATM}}^2$ |
| $\psi_t$ | ATM skew |
| $p_t$ | left (put) wing slope, $p_t \geq 0$ |
| $c_t$ | right (call) wing slope, $c_t \geq 0$ |
| $\tilde{v}_t$ | minimum implied variance, $\tilde{v}_t > 0$ |
These map to raw SVI $(a, b, \rho, m, \sigma)$ via a bijection:
$$b = \frac{p_t + c_t}{2}, \quad \rho = 1 - \frac{p_t}{b}, \quad \beta = \rho - \frac{2\psi_t\sqrt{T}}{b}$$
$$\alpha = \text{sgn}(\beta)\sqrt{\frac{1}{\beta^2} - 1}, \quad m = \frac{(v_t - \tilde{v}_t),T}{b\left[-\rho + \text{sgn}(\alpha)\sqrt{1 + \alpha^2} - \alpha\sqrt{1 - \rho^2}\right]}$$
$$\sigma = |\alpha \cdot m|, \quad a = \tilde{v}_t \cdot T - b,\sigma\sqrt{1 - \rho^2}$$
Same 5 degrees of freedom as raw SVI but with parameters that have direct market interpretation (wing slopes, ATM level, minimum variance).
SABR
The SABR stochastic volatility model (Hagan, Kumar, Lesniewski & Woodward 2002) — the market standard for interest-rate (swaptions, caps/floors) and FX volatility smiles. Unlike the SVI family, SABR is a dynamic model: it specifies stochastic differential equations for the forward and its volatility,
$$dF = \alpha F^\beta, dW_1, \qquad d\alpha = \nu, \alpha, dW_2, \qquad d\langle W_1, W_2\rangle = \rho, dt$$
Implied volatilities come from the Hagan et al. asymptotic expansion (the "HKLW formula"):
$$\sigma_B(K, F) = \frac{\alpha}{(FK)^{(1-\beta)/2}, D(L)} \cdot \frac{z}{x(z)} \cdot \left{1 + \left[\frac{(1-\beta)^2\alpha^2}{24,(FK)^{1-\beta}} + \frac{\rho\beta\nu\alpha}{4,(FK)^{(1-\beta)/2}} + \frac{2-3\rho^2}{24}\nu^2\right] T\right}$$
where $L = \log(F/K)$, $D(L) = 1 + \frac{(1-\beta)^2}{24}L^2 + \frac{(1-\beta)^4}{1920}L^4$, $z = \frac{\nu}{\alpha}(FK)^{(1-\beta)/2} L$, and $x(z) = \log!\left[\frac{\sqrt{1-2\rho z+z^2}+z-\rho}{1-\rho}\right]$. At the money $z/x(z) \to 1$ (handled via Taylor expansion). Total variance is then $w(k) = \sigma_B^2(k), T$.
| Parameter | Meaning | Constraint |
|---|---|---|
| $\alpha$ | initial volatility level | $\alpha > 0$ |
| $\beta$ | CEV exponent (fixed, not fitted) | $0 \leq \beta \leq 1$ |
| $\rho$ | spot/vol correlation | $|\rho| < 1$ |
| $\nu$ | vol-of-vol | $\nu \geq 0$ |
Following market practice, $\beta$ is fixed by convention rather than calibrated: $\beta = 1$ (lognormal) for FX and equity, $\beta \approx 0.5$ for interest rates, $\beta = 0$ (normal) for spread-like underlyings. Calibration fits $(\alpha, \rho, \nu)$ per slice given $\beta$, $F$, $T$:
model = get_model("sabr")
params = calibrate_slice(df_slice, model, T=T, F=F, beta=0.5)
Note: the HKLW expansion is accurate near the money and for moderate maturities, but is known to lose accuracy (and can even imply negative densities) for deep wings or very long maturities.
NO_BUTTERFLYperforms a numerical density check via finite differences — it is a guard, not a structural guarantee.
Reference: Hagan, P., Kumar, D., Lesniewski, A., Woodward, D. (2002). "Managing Smile Risk", Wilmott Magazine.
Arbitrage freeness
Every parametrization accepts an arbitrage_condition argument controlling how strictly no-arbitrage is enforced during calibration. The options are flags that can be combined with |:
from pysvi import ArbitrageFreedom
# Default: soft parameter bounds only
model = get_model("svi") # ArbitrageFreedom.QUASI
# Enforce no butterfly arbitrage (non-negative density)
model = get_model("svi", ArbitrageFreedom.NO_BUTTERFLY)
# Enforce no calendar spread arbitrage (non-decreasing total variance in T)
model = get_model("ssvi", ArbitrageFreedom.NO_CALENDAR)
# Enforce both
model = get_model("svi", ArbitrageFreedom.NO_BUTTERFLY | ArbitrageFreedom.NO_CALENDAR)
QUASI (default)
Soft parameter-bound constraints only: $b > 0$, $|\rho| < 1$, $\sigma > 0$. Enforced via bounded optimisation and penalty terms. Fast, and usually sufficient for liquid underlyings.
NO_BUTTERFLY
Enforces non-negative call price density $g(k) \geq 0$ across strikes, where:
$$g(k) = \left(1 - \frac{k,w'(k)}{2,w(k)}\right)^2 - \frac{w'(k)^2}{4}\left(\frac{1}{w(k)} + \frac{1}{4}\right) + \frac{w''(k)}{2}$$
Butterfly arbitrage exists whenever $g(k) < 0$ for some $k$. The calibrator evaluates $g$ on a fine grid and penalises violations. Note that SSVI and eSSVI already guarantee $g(k) \geq 0$ by their functional form; this flag adds an explicit numerical check.
NO_CALENDAR
Enforces non-decreasing total variance in maturity: $w(k, T_2) \geq w(k, T_1)$ for $T_2 > T_1$ at every $k$. This is a cross-slice condition. Pass the prior (shorter-maturity) slice's total variance via the w_prev keyword argument to calibrate:
# After calibrating the first slice:
w_prev = model.total_variance(k_grid, params_first_slice)
# Calibrate the next slice with calendar constraint:
params_next = model.calibrate(k, w_target, w_prev=w_prev)
Calibration details
All models calibrate via L-BFGS-B (bounded quasi-Newton) with automatic Nelder-Mead fallback. The pipeline for a single maturity slice is:
prepare_slice: extracts $T$, $F$, computes $k = \log(K/F)$ and $w = \sigma_{\text{mkt}}^2 T$, filters invalid data, clips extreme moneyness.model.calibrate: minimises MSE$(w_{\text{model}}, w_{\text{target}})$ plus penalty terms.apply_slice: evaluates the fitted surface, computes $\sigma_{\text{fit}} = \sqrt{w/T}$ and residuals.
The factory function get_model(name) accepts "svi", "ssvi", "essvi", "jumpwings" (or "jw"), "directsvi" (or "dsvi"), and "sabr".
Contributing
Contributions, bug reports, and feature requests are welcome. Open an issue or submit a PR on GitHub.
Wanted: the original Gamma-Vanna-Volga paper. The Gamma-Vanna-Volga parametrization is something of a holy grail in the quant vol surface literature and would be a great addition to this library. If you have a copy of the original paper, please send it to marwin.steiner@gmail.com.
License
MIT
Release files for svi-py 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| svi_py-0.4.0.tar.gz | 73.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| svi_py-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.8 kB
Release files / svi_py-0.4.0.tar.gz
| Download URL | svi_py-0.4.0.tar.gz |
|---|---|
| Size | 73.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fadb5855fad4bece3e75733dba5f8eb42bf47b35dccbfa5bffbcd2b14a5034c5
|
|
BLAKE2b-256 checksum How to use checksums |
98159c46b84fd15bc805a41fd71d6280bd0696cf4e647ac7207f2f64d6974234
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.
Transparency logRelease files / svi_py-0.4.0-py3-none-any.whl
| Download URL | svi_py-0.4.0-py3-none-any.whl |
|---|---|
| Size | 22.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
df0a12030b6d6eb65b45368156a5083ccf7b9e0f28df486d4b5493b59007c79d
|
|
BLAKE2b-256 checksum How to use checksums |
6398f76bbb555ac9a1782c3c373e5a8e735a29e885df68448a6ee9a9ac8f98f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.
Transparency log