Is the implied volatility surface arbitrage-free?#

Topics

SVI · SSVI · Durrleman condition · risk-neutral density · butterfly arbitrage · calendar spread

When we calibrate an implied volatility surface from market quotes, we get a smooth, good-looking surface. But does it make sense mathematically? Concretely: could a trader build a portfolio of vanilla options from this surface and lock in a guaranteed profit at zero cost? If yes, the surface is not arbitrage-free, it is internally inconsistent, regardless of how well it fits the data.

This note works through two classical notions of arbitrage on a vol surface, derives the conditions for each, and shows how to check them on a surface calibrated with hestonpy.


Setup: total implied variance#

Let \(F_T = S_0 e^{r T}\) denote the forward price at maturity \(T\) and spot \(S_0\), and let \(k = \ln(K / F_T)\) denote the log-moneyness. Instead of working with implied volatility \(\sigma_{imp}(k,T)\) directly, it is more convenient to work with the total implied variance:

\[ w(k, T) \;=\; T\sigma_{imp}(k, T)^2. \]

This removes the trivial \(\sqrt{T}\) scaling and makes the arbitrage conditions cleaner.


Two types of arbitrage#

Calendar spread arbitrage#

A calendar spread is a position long one call at maturity \(T_2\) and short one call at maturity \(T_1 < T_2\), at the same strike. Under any risk-neutral measure, holding an option to \(T_2\) is worth at least as much as holding it to \(T_1\) (early exercise aside), so the forward calendar spread price must be non-negative.

Translating this into the language of total variance, The total variance must be non-decreasing in maturity at every strike. Intuitively: more time means more uncertainty, so a wider distribution of terminal prices. Thus, one can show that a vol surface is free of calendar spread arbitrage if and only if, for every log-moneyness \(k\):

\[ \partial_T\, w(k, T) \;\geq\; 0. \]

Butterfly arbitrage and the Durrleman condition#

A butterfly centered at strike \(K_0\) is long calls at \(K_0 - \delta\) and \(K_0 + \delta\) and short two calls at \(K_0\). Its payoff is always non-negative (it’s a tent function), so its price must be non-negative too. A violation implies a negative risk-neutral density, the model assigns negative probability to some region of the spot, which is absurd.

For a single maturity slice \(w(\cdot) = w(\cdot, T)\), define Durrleman’s function:

\[ 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}, \]

where primes denote derivatives with respect to \(k\). One can show that the risk-neutral density of the log-moneyness \(k\) satisfies:

\[ p(k) \;=\; \frac{e^{-d_2(k)^2/2}}{\sqrt{2\pi\, w(k)}} \cdot g(k), \qquad d_2(k) = -\frac{k}{\sqrt{w(k)}} - \frac{\sqrt{w(k)}}{2}. \]

Since \(e^{-d_2^2/2} / \sqrt{2\pi w} > 0\) always, the density is non-negative if and only if \(g(k) \geq 0\). Therefore:

\[ \text{No butterfly arbitrage} \iff g(k) \geq 0 \quad \forall\, k. \]

Conditions for SVI#

The raw SVI parameterisation (Gatheral, 2004) reads:

\[ w^{SVI}(k) = a + b\!\left(\rho(k - m) + \sqrt{(k-m)^2 + \sigma^2}\right). \]

hestonpy calibrates this with the constraint \(a + b\sigma\sqrt{1-\rho^2} \geq 0\), which ensures the minimum total variance is non-negative. But is this sufficient for no butterfly arbitrage?

No, in general. The constraint is necessary (negative total variance immediately implies \(g < 0\)) but not sufficient. The full butterfly-free characterisation for SVI requires checking \(g(k) \geq 0\) everywhere. However, in practice a well-calibrated SVI slice almost never violates it. The dangerous regime is when the wings are too steep (\(b\) large, \(\sigma\) small).

Necessary conditions for SVI (no butterfly)

  • \(b \geq 0\)

  • \(|\rho| < 1\)

  • \(a + b\sigma\sqrt{1-\rho^2} \geq 0\)

These are enforced automatically by the StochasticVolatilityInspired.calibration() method.

Checking the Durrleman condition numerically#

For an SVI slice, \(g(k)\) can be evaluated analytically by computing \(w'\) and \(w''\):

\[ w'(k) = b\left(\rho + \frac{k - m}{\sqrt{(k-m)^2 + \sigma^2}}\right), \]
\[ w''(k) = b \cdot \frac{\sigma^2}{\left((k-m)^2 + \sigma^2\right)^{3/2}}. \]

Note that \(w'' > 0\) always (SVI is convex in \(k\)), which is a good sign. We plug these into \(g(k)\) and check the sign over a grid of log-moneyness values.

import numpy as np
from hestonpy.models.calibration.svi import StochasticVolatilityInspired

# --- Calibrate SVI on a single smile ---
T = 0.5
svi = StochasticVolatilityInspired(time_to_maturity=T)

# (assume strikes, market_ivs, forward are available)
params, model_ivs = svi.calibration(strikes, market_ivs, forward)
a, b, rho, m, sigma = params["a"], params["b"], params["rho"], params["m"], params["sigma"]

# --- Durrleman condition on a fine grid ---
k_grid = np.linspace(-0.5, 0.5, 500)

sqrt_term  = np.sqrt((k_grid - m)**2 + sigma**2)
w   =  a + b * (rho * (k_grid - m) + sqrt_term)
w_p =  b * (rho + (k_grid - m) / sqrt_term)
w_pp = b * sigma**2 / sqrt_term**3

g = (1 - k_grid * w_p / (2 * w))**2 \
    - (w_p**2 / 4) * (1/w + 0.25)   \
    + w_pp / 2

is_butterfly_free = np.all(g >= 0)
print(f"Butterfly-free: {is_butterfly_free}")
print(f"Min g(k) = {g.min():.6f}")

Conditions for SSVI#

The SSVI parameterisation (Gatheral & Jacquier, 2014) writes the total variance surface as:

\[ w^{SSVI}(k, T) = \frac{\theta_T}{2}\!\left( 1 + \rho_T\,\varphi_T\,k + \sqrt{(\varphi_T k + \rho_T)^2 + 1 - \rho_T^2} \right), \]

where \(\theta_T = \sigma_{ATM}(T)^2 \cdot T\) is the ATM total variance and \(\varphi_T \geq 0\) is the wing (curvature) parameter.

The strength of SSVI is that Gatheral & Jacquier derived analytical no-arbitrage conditions:

No-arbitrage theorem for SSVI (Gatheral & Jacquier, 2014)

No butterfly arbitrage iff, for every maturity \(T\):

\[\theta_T\,\varphi_T\,(1 + |\rho_T|) \leq 4.\]

No calendar spread arbitrage iff, for every pair of maturities \(T_1 < T_2\) and every \(k\):

\[w(k, T_1) \leq w(k, T_2).\]

In practice, since \(\theta_T = \sigma_{ATM}^2\cdot T\) is set directly from market ATM vols, calendar-spread arbitrage reduces to checking that the total ATM variance \(T \mapsto \theta_T\) is non-decreasing — which the market almost always satisfies.

In hestonpy, the calibration of each slice already enforces \(\varphi_T \leq 2/\theta_T\), which implies \(\theta_T \varphi_T \leq 2\). Combined with \((1 + |\rho|) \leq 2\), this gives \(\theta_T \varphi_T (1 + |\rho_T|) \leq 4\) — exactly the no-butterfly condition.

Key insight

The bound constraint phi <= 2/theta_T in the calibrate_single_maturity optimisation is not just a numerical trick — it is a direct encoding of the no-butterfly condition (up to the \(\rho\) factor, which is bounded by \(2\)).

Checking analytically with hestonpy#

import numpy as np
from hestonpy.models.calibration.ssvi import SurfaceStochasticVolatilityInspired

maturities = np.array([0.25, 0.5, 1.0, 2.0])
ssvi = SurfaceStochasticVolatilityInspired(maturities=maturities)

# (assume strikes, iv_surface, forwards are available)
params = ssvi.calibrate_surface(strikes, iv_surface, forwards)

print("Maturity | theta   | phi     | rho     | theta*phi*(1+|rho|) | OK?")
print("-" * 70)
for T in maturities:
    theta = ssvi.theta[T]
    rho   = params[T]["rho"]
    phi   = params[T]["phi"]
    cond  = theta * phi * (1 + abs(rho))
    ok    = "✓" if cond <= 4 else "✗ ARBITRAGE"
    print(f"  T={T:.2f}  | {theta:.4f} | {phi:.4f} | {rho:+.4f} | {cond:.4f}              | {ok}")

Calendar spread check#

# Check that total ATM variance is non-decreasing in T
thetas = np.array([ssvi.theta[T] for T in maturities])
diffs  = np.diff(thetas)

if np.all(diffs >= 0):
    print("✓ No calendar spread arbitrage (theta is non-decreasing)")
else:
    bad = np.where(diffs < 0)[0]
    for i in bad:
        print(f"✗ Calendar spread arbitrage between T={maturities[i]:.2f} and T={maturities[i+1]:.2f}")

Discussion#

SVI

SSVI

Butterfly-free

Check \(g(k) \geq 0\) numerically

Analytical: \(\theta\varphi(1+\vert\rho\vert)\leq 4\)

Calendar-spread-free

Must compare slices

\(\theta_T\) non-decreasing

Enforced in hestonpy

Necessary conditions only

✓ via bound on \(\varphi\)

The practical takeaway: SSVI is strictly safer than slice-by-slice SVI for building a full surface, because its no-arbitrage conditions can be encoded as simple box constraints in the optimiser. SVI is more flexible on a single smile but offers no cross-maturity guarantee.

For a production calibration pipeline, one should:

  1. Use SSVI (or parametric SSVI with \(\varphi(\theta) = \eta/\theta^\gamma\)) for the full surface, with the bound \(\varphi \leq 2/\theta\).

  2. After calibration, run a quick check: compute \(g(k)\) on a fine grid for each slice.

  3. Verify that \(T \mapsto \theta_T\) is non-decreasing.

If any check fails, tighten the optimisation constraints or review the input data for outlier quotes.


References#

  • Gatheral, J. (2004). A parsimonious arbitrage-free implied volatility parameterization with application to the valuation of volatility derivatives. Presentation at Global Derivatives & Risk Management, Madrid.

  • Gatheral, J. & Jacquier, A. (2014). Arbitrage-free SVI volatility surfaces. Quantitative Finance, 14(1), 59–71.

  • Durrleman, V. (2005). From implied to spot volatilities. PhD thesis, Princeton University.

  • Lee, R. (2004). The moment formula for implied volatility at extreme strikes. Mathematical Finance, 14(3), 469–480.