Derman–Kani (Dupire) Local Volatility: Theory and Practical Calibration
Derivation and practical guide to extracting a local volatility surface from market implied volatilities (Derman–Kani / Dupire), with implementation notes and a Python example.
Derivation and practical guide to extracting a local volatility surface from market implied volatilities (Derman–Kani / Dupire), with implementation notes and a Python example.
Local volatility models are a cornerstone of modern option pricing and risk management. They answer the question: what instantaneous volatility function σ_loc(S,t) applied to a diffusion process dS_t = μS_t dt + σ_loc(S_t,t) S_t dW_t produces the same European option prices (or implied volatilities) that we observe in the market today?
This article explains the theory behind the Derman–Kani / Dupire local volatility approach, the practical challenges of calibrating a stable surface from discrete, noisy market implied vols, and a concise Python example that walks through a minimal implementation.
Implied volatility (IV) is the volatility parameter that, when plugged into Black–Scholes, yields the observed market price for an option. The IV surface σ_imp(K,T) (strike K, maturity T) is a compact representation of market option prices.
A local volatility model assumes that the underlying follows a time- and state-dependent diffusion
1\frac{dS_t}{S_t} = r(t) dt + \sigma_{loc}(S_t, t) dW_t,
2and prices European claims by taking expectations under that diffusion. The Derman–Kani / Dupire result tells us there exists a σ_loc(S,t) that reproduces the marginal distributions (hence European option prices for all strikes) implied by the market (ignoring dividends / interest-rate complications). It's a model calibrated to the entire implied-vol surface — perfect for pricing path-dependent exotics when one trusts today's option smiles to encode forward-looking risk.
Use-cases:
Limitations:
Dupire derived a closed-form expression for the local variance function in terms of the call price surface C(K,T). In plain form:
sigma_loc^2(K, T) = 2 * ∂_T C(K,T) / (K^2 * ∂^2 C(K,T)/∂K^2).
Here C(K,T) is the price of a European call with strike K and maturity T (forward or undiscounted versions are commonly used to simplify the formula). The symbol ∂^2C/∂K^2 denotes the second partial derivative of the call price with respect to strike — by Breeden–Litzenberger, for forward (undiscounted) calls this equals the risk‑neutral density f_{S_T}(K). Written in terms of implied vol σ_imp(K,T) the same expression can be expanded (but is algebraically heavier) by differentiating Black–Scholes formulas w.r.t. K and T.
Important points:
In practice we evaluate Dupire on a grid of strikes and maturities and then transform to the local volatility function in (S,t) coordinates (mapping from strike K to spot/forward levels or log-moneyness). Many implementations work directly with moneyness (k = ln(K/F)) because it is numerically more stable.
Real market data is discrete, noisy, and sometimes inconsistent. To apply Dupire robustly you must address:
A pragmatic pipeline is to fit SVI for each maturity, smooth the time dimension with monotone cubic Hermite interpolating polynomials (PCHIP) on total variance, and compute analytic derivatives for strike using SVI formulas. This avoids finite-difference noise in strike derivatives.
Main derivatives needed:
Techniques:
Edge-cases to watch:
Contract: inputs and outputs
K_i and maturities T_j; spot price S0; interest rate curve r(t) and dividend yield q(t) or forward prices F(t).High-level steps:
Because markets quote implied vol, many implementations transform Dupire into an expression in terms of σ_imp(K,T). Let w = σ_imp^2(K,T) T denote total implied variance. With some algebra the Dupire formula becomes (in log-moneyness coordinates) a formula involving partial derivatives of w w.r.t. T and k. A commonly used form (for forward price frame) is:
1\sigma_{loc}^2(k,T) = \frac{\partial_T w(k,T)}{1 - \frac{k}{w} \partial_k w(k,T) + \frac{1}{4}\left(-\frac{1}{4} - \frac{1}{w} + \frac{k^2}{w^2} \right) (\partial_k w(k,T))^2 + \frac{1}{2} \partial_{kk} w(k,T)}.
2This expression shows why total variance parameterizations (w) are convenient: derivatives of w are smoother and better behaved than derivatives of implied vol directly. Many practitioners fit w(k,T) and differentiate it analytically.
Below is a compact Python example that sketches the main steps. This is intentionally minimal — production code must include robust calibration, constraints, logging, and tests.
Note: dependencies — numpy, scipy, and optionally matplotlib. For SVI fits a small routine is included.
1# minimal_local_vol.py
2import numpy as np
3from scipy.optimize import minimize
4from scipy.interpolate import PchipInterpolator
5from math import log, sqrt
6
7# Black-Scholes helpers (undiscounted forward prices assumed)
8from scipy.stats import norm
9
10def bs_call_price(F, K, T, sigma):
11 if T <= 0 or sigma <= 0:
12 return max(F - K, 0.0)
13 d1 = (np.log(F / K) + 0.5 * sigma * sigma * T) / (sigma * np.sqrt(T))
14 d2 = d1 - sigma * np.sqrt(T)
15 return F * norm.cdf(d1) - K * norm.cdf(d2)
16
17# SVI total variance function (raw parametrization)
18def svi_total_var(params, k):
19 a, b, rho, m, sigma = params
20 return a + b * (rho * (k - m) + np.sqrt((k - m)**2 + sigma**2))
21
22# Fit SVI to market total variance (per maturity)
23def fit_svi(k, w_market, initial=None):
24 if initial is None:
25 initial = np.array([0.1, 0.1, 0.0, 0.0, 0.1])
26 bounds = [(-1, 5), (1e-6, 5), (-0.999, 0.999), (-5, 5), (1e-6, 5)]
27 def obj(p):
28 w = svi_total_var(p, k)
29 return np.sum((w - w_market)**2)
30 res = minimize(obj, initial, bounds=bounds)
31 return res.x
32
33# Example usage (synthetic data)
34if __name__ == '__main__':
35 # Synthetic market: expiries and strikes
36 expiries = np.array([0.1, 0.25, 0.5, 1.0])
37 strikes = np.linspace(-0.6, 0.6, 31) # log-moneyness k = ln(K/F)
38 F = 100.0
39
40 # Create synthetic total variance surface (w = sigma_imp^2 * T)
41 # For demo we'll fabricate an implied vol surface
42 w_surface = {}
43 for T in expiries:
44 # simple smile: base variance grows with T, add smile curvature
45 base_var = 0.04 + 0.02 * T
46 w = base_var + 0.02 * np.exp(-strikes**2 / 0.1) # total variance
47 w_surface[T] = w
48
49 # Fit SVI per slice
50 svi_params = {}
51 for T in expiries:
52 p = fit_svi(strikes, w_surface[T])
53 svi_params[T] = p
54
55 # Time smoothing: for each strike (k) we have w(T); interpolate w in T with PCHIP
56 # Prepare grid for evaluation
57 T_grid = np.linspace(expiries.min(), expiries.max(), 40)
58 K_grid = np.exp(strikes) * F
59 w_grid = np.zeros((len(T_grid), len(strikes)))
60
61 for i, k in enumerate(strikes):
62 w_by_T = np.array([svi_total_var(svi_params[T], k) for T in expiries])
63 interp = PchipInterpolator(expiries, w_by_T) # monotone in T
64 w_grid[:, i] = interp(T_grid)
65
66 # Compute derivatives with finite-differences on smooth w_grid (careful in production)
67 dT = T_grid[1] - T_grid[0]
68 dk = strikes[1] - strikes[0]
69 dwdT = np.gradient(w_grid, dT, axis=0)
70 dwdK2 = np.gradient(np.gradient(w_grid, dk, axis=1), dk, axis=1)
71
72 # Compute local variance (simplified expression assuming forward frame)
73 local_var = np.zeros_like(w_grid)
74 for i in range(len(T_grid)):
75 for j in range(len(strikes)):
76 w = w_grid[i, j]
77 wT = dwdT[i, j]
78 wKK = dwdK2[i, j]
79 denom = 1 - (strikes[j] / w) * (np.gradient(w_grid[i,:], dk)[j]) + 0.25 * ((-0.25 - 1/w + (strikes[j]**2)/(w**2)) * (np.gradient(w_grid[i,:], dk)[j]**2)) + 0.5 * wKK
80 if denom <= 1e-12 or wT <= 0:
81 local_var[i,j] = np.nan
82 else:
83 local_var[i,j] = wT / denom
84
85 # local_sigma = sqrt(local_var)
86 local_sigma = np.sqrt(np.abs(local_var))
87
88 print('Computed local vol grid shape:', local_sigma.shape)
89
90Notes on the sample above:
After producing a candidate local volatility grid, validate it:
Typical fixes when things go wrong:
If you want to reproduce a working pipeline quickly:
Technical Writer
NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.
Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.