Skip to main content

Stochastic volatility inspired parametrizations of the implied volatility surface in Python!

Project description

svi-py

Stochastic volatility inspired (SVI) parametrizations of the implied volatility surface in Python.

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

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_vectorized computes Black-Scholes-Merton implied vols from option mid-prices via py_vollib.
  • calculate_implied_forward estimates the forward price from put-call parity: $F = K + e^{rT}(C - P)$.
  • choose_leg selects 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$):

  1. Build design matrices $D_2 = [k^2,; w^2]$ and $D_1 = [kw,; k,; w,; 1]$
  2. Compute scatter matrices $S_{22}, S_{21}, S_{11}$
  3. 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}$
  4. 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). Only ArbitrageFreedom.QUASI is 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).

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:

  1. prepare_slice: extracts $T$, $F$, computes $k = \log(K/F)$ and $w = \sigma_{\text{mkt}}^2 T$, filters invalid data, clips extreme moneyness.
  2. model.calibrate: minimises MSE$(w_{\text{model}}, w_{\text{target}})$ plus penalty terms.
  3. 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"), and "directsvi" (or "dsvi").

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

svi_py-0.3.0.tar.gz (68.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

svi_py-0.3.0-py3-none-any.whl (18.6 kB view details)

Uploaded Python 3

File details

Details for the file svi_py-0.3.0.tar.gz.

File metadata

  • Download URL: svi_py-0.3.0.tar.gz
  • Upload date:
  • Size: 68.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for svi_py-0.3.0.tar.gz
Algorithm Hash digest
SHA256 a149e904cd63de6caa5e24aaf7e76720bfb49c8b75c1ea419179674d2b73abca
MD5 46af5aeee2590e6bfc13af31255991d7
BLAKE2b-256 898fa6ff704d48a02622a578fe5e95a589ae93fe4fec3f5a7e0fd45a47f4a59f

See more details on using hashes here.

Provenance

The following attestation bundles were made for svi_py-0.3.0.tar.gz:

Publisher: python-publish.yml on marwinsteiner/pysvi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file svi_py-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: svi_py-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 18.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for svi_py-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0fc77f938fa8e12a52c0ee0f98c3ac14da49afac617ca654de073d28561ac1c5
MD5 33b30fe6797c01866b427c4ec5f55138
BLAKE2b-256 563dfc1ab3c1991fd8be7d7c4955bf664263101d03aac12032c2abc7c1aab98d

See more details on using hashes here.

Provenance

The following attestation bundles were made for svi_py-0.3.0-py3-none-any.whl:

Publisher: python-publish.yml on marwinsteiner/pysvi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page