Setup
Market Context
The implied vol surface is the complete language of the options market. Every option price, hedge ratio, and P&L scenario is rooted in this surface. Calibrating it accurately and consistently is a core task on any derivatives desk.
A vol surface must satisfy two properties to be financially meaningful:
- Arbitrage-free: no calendar spread, butterfly, or call spread arbitrage is implied.
- Smooth: the surface must be differentiable enough to produce well-defined local vols via the Dupire formula, and to compute stable vega ladders.
Fitting a surface point-by-point (interpolating market quotes directly) satisfies neither: it typically produces arbitrage-violating kinks and is unstable off-grid. A parametric smile model is used instead.
Conventions
Throughout this module:
- Total implied variance: , where is the log-moneyness and is the forward.
- Working with instead of simplifies arbitrage conditions (they become inequalities on and ).
- A vol slice at fixed is an implied vol smile. A collection of smiles across maturities is the vol surface.
Theory: The Raw SVI Parametrisation
Motivation: Derman-Kani and the Wings
At extreme log-moneyness , the implied vol smile must grow roughly linearly in . This follows from Lee's moment formula: if the -th moment of the stock price is finite under , the right tail of grows at most as times a function of . More precisely:
where is the critical moment. Any smile that grows faster than in total variance violates moment bounds and implies arbitrage. The SVI form is designed to reproduce this linear-in- wing behaviour exactly.
Raw SVI
Gatheral (2004) proposed the Stochastic Volatility Inspired (SVI) parametrisation. For a fixed maturity , the total implied variance as a function of log-moneyness is:
where the five raw SVI parameters satisfy:
- : overall level of total variance (vertical shift).
- : slope of the wings; controls how fast variance grows with .
- : correlation-like parameter; controls the asymmetry between left and right wings (skew).
- : location of the ATM vertex (horizontal shift); usually .
- : smoothness of the vertex; larger gives a wider, rounder bottom.
Geometric Interpretation
The graph of is a rotated hyperbola with:
- Asymptotes: as , (right wing slope ); as , (left wing slope , so the wing rises).
- Vertex: at , . This is the minimum of the smile (for the call side) when .
- ATM vol: . Since in typical calibrations, .
Note: the left wing slope is always negative (wings rise on both sides) since and . The right wing slope is always non-negative. The asymmetry between wings is controlled by : negative (typical for equities) steepens the left wing and flattens the right, consistent with the negative equity skew.
Arbitrage-Free Conditions
A slice is free of static arbitrage if and only if:
Condition 1: No Butterfly Arbitrage
Butterfly arbitrage is absent if and only if the function
where and . This condition (Gatheral 2004, following Dupire) ensures the local variance implied by the Dupire formula is non-negative:
If for some , the local variance is negative — an immediate arbitrage.
For the raw SVI form, is not automatic: it must be checked or enforced by constraining the parameters.
Necessary condition (Lee's bound): A weaker, necessary condition is:
A sufficient condition for no butterfly arbitrage that is easier to check analytically: for the raw SVI form, (Roper 2010).
Condition 2: No Calendar Spread Arbitrage
The total implied variance must be non-decreasing in maturity for each fixed log-moneyness:
If this fails at any point, a calendar spread (long far-dated call, short near-dated call at the same log-moneyness) is riskless arbitrage.
Condition 3: No Call Spread Arbitrage
Total variance must satisfy:
(Equivalent to the call price being non-increasing in strike.)
Jump-Wings Parametrisation
The raw SVI parameters are poorly conditioned for optimisation: small changes in and can produce large changes in the smile shape, and the parameters are correlated. The jump-wings (JW) parametrisation (Gatheral and Jacquier 2014) reparametrises in terms of quantities with direct financial meaning:
The exact mapping is:
The JW parameters have the advantage that , , and are directly related to observable quantities (ATM vol, put skew, call skew), making initial guesses intuitive and the parameter space more orthogonal.
SSVI: Extending to a Surface
Fitting each maturity slice independently with raw SVI can produce calendar spread arbitrage across slices. Surface SVI (SSVI) (Gatheral and Jacquier 2014) extends the parametrisation to the full surface:
where:
- : the ATM total variance term structure.
- : a smooth function (e.g., power law ).
- : a single surface-level correlation parameter.
Sufficient conditions for SSVI to be arbitrage-free (Gatheral-Jacquier):
- (ATM variance term structure is increasing in ).
- .
- .
These conditions are sufficient but not necessary. For the power-law : conditions reduce to simple bounds on , , and , making it easy to enforce during calibration.
Calibration Procedure
Single-Slice Calibration
For each maturity :
- Collect market quotes: (usually 5–10 strikes per slice).
- Initialise parameters using JW: set , estimate from the 25Δ skew, estimate wing slopes , from risk-reversals and butterfly spreads.
- Minimise subject to , , .
- Check arbitrage conditions post-calibration; re-optimise with constraint if violated.
import numpy as np
from scipy.optimize import minimize, Bounds
def svi_total_var(k: np.ndarray, a: float, b: float, rho: float, m: float, sigma: float) -> np.ndarray:
"""Raw SVI total implied variance w(k) = a + b*(rho*(k-m) + sqrt((k-m)^2 + sigma^2))."""
return a + b * (rho * (k - m) + np.sqrt((k - m)**2 + sigma**2))
def svi_butterfly_condition(k: np.ndarray, a, b, rho, m, sigma) -> np.ndarray:
"""
Computes g(k) = (1 - k*w'/(2w))^2 - (w')^2/4*(1/w + 1/4) + w''/2.
g(k) >= 0 for all k is the no-butterfly-arbitrage condition.
"""
eps = 1e-12
dkm = k - m
sq = np.sqrt(dkm**2 + sigma**2)
w = a + b * (rho * dkm + sq)
wp = b * (rho + dkm / sq) # dw/dk
wpp = b * sigma**2 / (sq**3) # d^2w/dk^2
g = (1 - k * wp / (2 * w + eps))**2 \
- (wp**2) / 4 * (1 / (w + eps) + 0.25) \
+ wpp / 2
return g
def calibrate_svi_slice(
log_moneyness: np.ndarray,
market_total_var: np.ndarray,
weights: np.ndarray | None = None
) -> dict:
"""
Calibrate raw SVI parameters (a, b, rho, m, sigma) to a single maturity slice.
Inputs:
log_moneyness: k = log(K/F), array shape (N,)
market_total_var: w_mkt = sigma_imp^2 * T, array shape (N,)
weights: per-instrument weights (default: uniform)
Returns:
dict with keys: a, b, rho, m, sigma, rms_error, converged
"""
if weights is None:
weights = np.ones_like(log_moneyness)
def objective(x: np.ndarray) -> float:
a, b, rho, m, sigma = x
w_model = svi_total_var(log_moneyness, a, b, rho, m, sigma)
return float(np.sum(weights * (w_model - market_total_var)**2))
# Initialise from market observables
w_atm = float(np.interp(0.0, log_moneyness, market_total_var))
skew = float(np.gradient(market_total_var, log_moneyness)[len(log_moneyness)//2])
x0 = [w_atm * 0.9, 0.1, -0.3, 0.0, 0.1]
bounds = Bounds(
lb=[-1.0, 1e-6, -0.999, -1.0, 1e-6],
ub=[ 1.0, 2.0, 0.999, 1.0, 5.0 ],
)
result = minimize(
objective, x0,
method='L-BFGS-B', bounds=bounds,
options=dict(ftol=1e-14, gtol=1e-10, maxiter=2000),
)
a, b, rho, m, sigma = result.x
rms = np.sqrt(result.fun / len(log_moneyness))
# Post-calibration butterfly check
k_dense = np.linspace(log_moneyness.min() - 1.0, log_moneyness.max() + 1.0, 500)
g_min = float(np.min(svi_butterfly_condition(k_dense, a, b, rho, m, sigma)))
return dict(a=a, b=b, rho=rho, m=m, sigma=sigma,
rms_error=rms, converged=result.success,
butterfly_g_min=g_min)
Detecting Calendar Spread Arbitrage
Check that for each grid point across adjacent maturities:
def check_calendar_arbitrage(
k_grid: np.ndarray,
svi_params_by_maturity: list[dict]
) -> list[tuple[int, float]]:
"""
Returns list of (maturity_index, log_moneyness) pairs where calendar arbitrage occurs.
"""
violations = []
for j in range(len(svi_params_by_maturity) - 1):
p_near = svi_params_by_maturity[j]
p_far = svi_params_by_maturity[j + 1]
w_near = svi_total_var(k_grid, **{k: p_near[k] for k in ('a','b','rho','m','sigma')})
w_far = svi_total_var(k_grid, **{k: p_far[k] for k in ('a','b','rho','m','sigma')})
bad_k = k_grid[w_far < w_near]
for k_val in bad_k:
violations.append((j, float(k_val)))
return violations
Limitations
Parameter unidentifiability at sparse strikes. With only 5 market quotes per slice (a typical single-broker strip), the raw SVI has 5 parameters and no degrees of freedom. The fit will be exact but potentially arbitrage-ridden or non-unique. Use regularisation or a lower-dimensional parametrisation (e.g., fixing ).
SSVI loses slice flexibility. SSVI imposes a global correlation across all maturities, which may not fit markets where the skew term structure is complex. In practice, SSVI parameters are time-dependent, breaking the simple global surface structure.
Extrapolation beyond quoted strikes. SVI is well-specified by construction in the wings (linear growth in ). But the wing slope parameters must be consistent with moment constraints — large combined with large can violate Lee's bounds for short maturities.
Short-maturity smile. SVI was designed for maturities month. For very short maturities ( week), the smile can be very steep and SVI's smooth wing structure may not capture the convexity correctly. Normalised Bachelier vols are sometimes used instead.
Calibration to bid-ask midpoints. Market implied vols are quoted with a bid-ask spread. Fitting the midpoint produces a parametric smile that may lie inside the bid-ask cone even when the fit is "good". A more robust approach is to minimise the sum of violations outside the bid-ask cone, not the midpoint residuals.