03 · Stochastic Volatility · Calibration

Calibration of the
Heston Model.

A self-contained framework for calibrating the Heston (1993) stochastic volatility model to 142 CAC40 option quotes, from 4 days to 4.84 years. The page derives the characteristic function and the Fourier pricing formula, rebuilds the forward and dividend curve from put-call parity, and analyses the inverse problem. The main lesson: the dominant error came from the forward, not from the model. Adding a dividend yield alone cuts the price RMSE from €135 to €32.

Context Personal study — September 2026
Data CAC40 option chain, 142 OTM quotes, 13 expiries · EURIBOR 6M zero curve (PCHIP)
Model Heston (1993), Gatheral formulation, bootstrapped dividend curve
Method Gil–Pelaez inversion · vega-weighted loss · multi-start L-BFGS-B

Beyond constant volatility.

The Black-Scholes framework assumes a constant volatility, and therefore predicts a flat implied volatility surface across all strikes and maturities. Since 1987, equity options show the opposite: implied volatility decreases with the strike (the skew), with a term structure that depends on maturity. Stochastic volatility models let the variance of the underlying follow its own diffusion to capture these effects.

The Heston (1993) model remains one of the most widely used. Its main advantage is a semi-closed-form price for European options through Fourier transforms, which makes calibration to market quotes computationally efficient.

A calibration on real data also exposes a second, often underestimated, layer of complexity: the market does not quote the risk-neutral forward of the underlying. Rate and, above all, dividend assumptions must be inferred from the option prices themselves before any stochastic volatility parameter can be fitted. In practice, this issue dominated the calibration error on long-dated options far more than any deficiency of the Heston dynamics.

The study derives the characteristic function from the Riccati system, together with the Gil–Pelaez, Carr–Madan and COS pricing formulas and the Greeks. It shows that a price-space loss is, to first order, a vega-weighted implied volatility loss, and that the short-maturity smile identifies the product \(\rho\xi\) rather than \(\rho\) and \(\xi\) separately.

The Heston model equations.

Under the risk-neutral measure \(\mathbb{Q}\), the asset price \(S_t\) and its variance \(v_t\) follow the coupled stochastic differential equations:

Underlying dynamics
\[ dS_t = (r-q)\,S_t\,dt + \sqrt{v_t}\,S_t\,dW_t^{S} \]
Variance dynamics
\[ dv_t = \kappa(\theta-v_t)\,dt + \xi\sqrt{v_t}\,dW_t^{v} \]
Correlation
\[ d\langle W^S, W^v \rangle_t = \rho\,dt, \quad \rho\in[-1,1] \]

Equivalently, \(W^v=\rho W^S+\sqrt{1-\rho^2}\,W^{\perp}\) with \(W^{\perp}\) independent of \(W^S\). This Cholesky form is the one used in simulation (section 10). Here \(r\) is the zero-coupon rate and \(q\) the cost of carry, which includes both the dividend yield and the repo/borrow spread. Both are term structures in practice, written \(r(t)\) and \(q(t)\). Applying Itô's formula to \(x_t=\ln S_t\) gives the log-price dynamics used to derive the characteristic function:

Log-price
\[ dx_t = \left(r-q-\tfrac12 v_t\right)dt + \sqrt{v_t}\,dW_t^{S} \]

By Feynman–Kac, the value \(V(t,S,v)\) of a European derivative solves a two-dimensional backward PDE, with terminal condition \(V(T,S,v)=(S-K)^+\) for a call:

Pricing PDE
\[ \begin{aligned} &\frac{\partial V}{\partial t} +\tfrac12 vS^2\frac{\partial^2 V}{\partial S^2} +\rho\xi vS\frac{\partial^2 V}{\partial S\,\partial v} +\tfrac12\xi^2 v\frac{\partial^2 V}{\partial v^2} \\ &\quad+(r-q)S\frac{\partial V}{\partial S} +\kappa(\theta-v)\frac{\partial V}{\partial v} -rV=0 \end{aligned} \]

The mixed-derivative term \(\rho\xi vS\,\partial_S\partial_v V\) is what lets the model generate a skew. Unlike Black-Scholes, no closed form in elementary functions exists: the solution is obtained by Fourier methods (section 05).

Five parameters, five effects on the surface.

The model is governed by \(\Theta=(v_0,\kappa,\theta,\xi,\rho)\). The goal of the calibration is the vector \(\Theta\) that minimizes the error between market and model at every point of the volatility surface. Each parameter has a distinct effect:

\(v_0 > 0\) Instantaneous variance at \(t=0\). Sets the level of the short-dated smile: the short-maturity ATM volatility is about \(\sqrt{v_0}\).
\(\theta > 0\) Long-term mean variance. Sets the level of the long-dated smile: the long-maturity ATM volatility is about \(\sqrt{\theta}\).
\(\kappa > 0\) Mean-reversion speed. Controls how fast the smile flattens with maturity, and the time scale \(1/\kappa\) of the transition from \(v_0\) to \(\theta\).
\(\xi > 0\) Volatility of volatility. Drives the curvature of the smile and lifts the wings; amplifies skew and curvature at short and mid maturities.
\(\rho \in [-1,1]\) Spot-variance correlation. A negative \(\rho\) gives the downward-sloping equity skew (leverage effect), which decays with maturity at a rate governed by \(\kappa\).

For small maturities, the ATM skew of Heston has a finite limit, with \(k=\ln(K/F)\):

Short-maturity ATM skew
\[ \lim_{T\to0} \left.\frac{\partial\sigma_{\mathrm{BS}}}{\partial k}\right|_{k=0} = \frac{\rho\,\xi}{4\sqrt{v_0}} \]

Two consequences matter for calibration. The skew depends on the product \(\rho\xi\), so the first-order slope cannot separate \(\rho\) from \(\xi\): only the curvature can. And for equity indices, where \(\rho\lt0\), the skew is negative, as observed.

The CIR variance process and the Feller condition.

The variance follows a Cox–Ingersoll–Ross process. Conditionally on \(v_s\), with \(\tau=t-s\), \(v_t\) is a scaled non-central chi-squared variable. This exact law is the basis of exact simulation and of the QE scheme (section 10):

Transition law
\[ v_t \overset{d}{=} c(\tau)\,\chi'^2_d\big(\lambda(\tau)\big), \quad c(\tau)=\frac{\xi^2(1-e^{-\kappa\tau})}{4\kappa}, \quad d=\frac{4\kappa\theta}{\xi^2}, \quad \lambda(\tau)=\frac{4\kappa e^{-\kappa\tau}}{\xi^2(1-e^{-\kappa\tau})}\,v_s \]
Conditional mean and variance
\[ \mathbb{E}[v_t]=\theta+(v_0-\theta)e^{-\kappa t}, \qquad \mathrm{Var}(v_t)=\frac{v_0\xi^2}{\kappa}\left(e^{-\kappa t}-e^{-2\kappa t}\right) +\frac{\theta\xi^2}{2\kappa}\left(1-e^{-\kappa t}\right)^2 \]

As \(t\to\infty\), \(v_t\) converges to a Gamma law with shape \(\alpha=2\kappa\theta/\xi^2\) and rate \(\beta=2\kappa/\xi^2\). Its density behaves like \(v^{\alpha-1}\) near zero and blows up when \(\alpha\lt1\). This is the intuition behind the Feller condition, which keeps the variance strictly positive:

Feller condition
\[ 2\kappa\theta > \xi^2 \]

The Feller ratio \(2\kappa\theta/\xi^2\) is exactly the shape parameter of the stationary Gamma law. Market calibrations often violate it. This is not an issue for Fourier pricing, since the characteristic function remains valid, but Monte Carlo schemes then need an appropriate boundary treatment.

A useful diagnostic. An optimizer that drives the Feller ratio far below 1 while pinning \(\rho\) against its bound is often not describing smile dynamics, but compensating for a mis-specified forward (section 06). This exact symptom appeared here: before the forward was corrected, the fit converged to \(\rho=-0.939\) with a Feller ratio of about 0.17.

Integrating the mean gives the expected integrated variance, which drives the ATM term structure:

Integrated variance
\[ \mathbb{E}\!\left[\int_0^T v_t\,dt\right] = \theta T+(v_0-\theta)\,\frac{1-e^{-\kappa T}}{\kappa} \]

Dividing by \(T\) gives the approximate ATM implied variance: it moves from \(v_0\) at short maturity to \(\theta\) at long maturity, on a time scale \(1/\kappa\).

Characteristic function and Fourier inversion.

The European call price is written with two exercise probabilities: \(P_2=\mathbb{Q}(S_T>K)\), and \(P_1\), the same probability under the share measure. Both are obtained by Fourier inversion of the characteristic function.

Call price
\[ C(S_0,K,T) = S_0e^{-qT}P_1-Ke^{-rT}P_2 \]

Since the coefficients of the log-price and variance equations are affine in \((x,v)\), the characteristic function has an exponential-affine form \(\exp\big(iux+A(\tau)+\Delta(\tau)v\big)\). Substituting into the backward Kolmogorov equation and collecting the terms in \(v^0\) and \(v^1\) gives a Riccati system with constant coefficients:

Riccati system
\[ \Delta'(\tau)=\tfrac12\xi^2\Delta^2-(\kappa-i\rho\xi u)\Delta-\tfrac12(u^2+iu), \qquad A'(\tau)=iu(r-q)+\kappa\theta\,\Delta(\tau) \]

Solving it with \(A(0)=\Delta(0)=0\) gives the characteristic function of \(\ln S_T\), written in Gatheral's numerically stable form:

Characteristic function
\[ \phi(u;T) = \exp\Big(C(u,T)\,\theta+D(u,T)\,v_0+iu\big(\ln S_0+(r-q)T\big)\Big) \]
Terms \(C\), \(D\), \(d\), \(g\)
\[ \begin{aligned} D(u,T) &= \frac{\kappa-i\rho\xi u-d}{\xi^2}\left(\frac{1-e^{-dT}}{1-ge^{-dT}}\right), & C(u,T) &= \frac{\kappa}{\xi^2}\left[(\kappa-i\rho\xi u-d)\,T-2\ln\frac{1-ge^{-dT}}{1-g}\right], \\[4pt] d &= \sqrt{(\kappa-i\rho\xi u)^2+\xi^2(u^2+iu)}, & g &= \frac{\kappa-i\rho\xi u-d}{\kappa-i\rho\xi u+d} \end{aligned} \]
The "little Heston trap". The original 1993 formulation uses \(1/g\) together with \(e^{+dT}\). Its complex logarithm can cross the branch cut of the principal logarithm as \(u\) varies, which creates discontinuities in prices during optimization. The form above (Gatheral 2006, Albrecher et al. 2007) keeps the logarithm on the principal branch along the whole integration path.

The two probabilities follow from the Gil–Pelaez inversion theorem. Under the share measure, the characteristic function becomes \(\phi(u-i)/\phi(-i)\), with \(\phi(-i)=\mathbb{E}^{\mathbb{Q}}[S_T]=F(T)\):

Gil–Pelaez inversion
\[ P_2=\frac12+\frac1\pi\int_0^{\infty}\mathrm{Re}\!\left[\frac{e^{-iu\ln K}\,\phi(u;T)}{iu}\right]du, \qquad P_1=\frac12+\frac1\pi\int_0^{\infty}\mathrm{Re}\!\left[\frac{e^{-iu\ln K}\,\phi(u-i;T)}{iu\,F(T)}\right]du \]

Both integrands stay bounded near \(u=0\). Puts follow from put-call parity. The integrals are computed by adaptive quadrature (scipy.integrate.quad), truncated at \(u_{\max}=100\) for every maturity: simple to implement correctly and fast enough for a few hundred quotes.

Two faster alternatives were studied. Carr–Madan prices a whole grid of \(N\) strikes with a single FFT in \(O(N\log N)\), by damping the call price with a factor \(e^{\alpha k}\):

Carr–Madan
\[ C(K,T)=\frac{e^{-\alpha k-rT}}{\pi}\,\mathrm{Re}\int_0^{\infty} e^{-ivk}\,\frac{\phi\big(v-(\alpha+1)i;T\big)}{\alpha^2+\alpha-v^2+i(2\alpha+1)v}\,dv, \qquad k=\ln K \]

The COS method (Fang and Oosterlee) expands the density of \(\ln(S_T/K)\) in a cosine series on a truncated interval sized from the cumulants. The series converges exponentially for smooth densities, so a few dozen terms often suffice, which makes it the natural candidate for the inner loop of a calibration.

The call is homogeneous of degree one in \((S_0,K)\), so Euler's relation gives the Greeks in closed form, the Gamma coming from the risk-neutral density:

Delta and Gamma
\[ \Delta=\frac{\partial C}{\partial S_0}=e^{-qT}P_1, \qquad \Gamma=\frac{\partial^2 C}{\partial S_0^2}=\frac{K^2}{S_0^2}\,\frac{\partial^2 C}{\partial K^2} \]

Parameter sensitivities are obtained by differentiating under the integral sign. For instance \(\partial_{v_0}\phi=D(u,T)\,\phi\), which gives analytical gradients for the calibration.

The forward isn't quoted — it has to be rebuilt.

Index options are quoted against a forward \(F(t)=S_0e^{(r(t)-q(t))t}\) that is not directly observable. The rate \(r(t)\) is bootstrapped from EURIBOR 6M zero-coupon rates with a PCHIP interpolation. The cost of carry \(q(t)\), which combines dividends and repo, is neither flat nor quoted, and for the CAC40 it is strongly seasonal, concentrated between April and June.

The effect of a wrong forward can be quantified. Let \(\hat q\) be the carry used instead of the true \(q\), and \(\hat F\) the resulting forward. Holding the volatility dynamics fixed:

Price bias from a mis-specified carry
\[ \hat C-C\approx D(T)\,P_1\,(\hat F-F), \qquad \hat P-P\approx -D(T)\,(1-P_1)\,(\hat F-F), \qquad \hat F-F=F\left(e^{(q-\hat q)T}-1\right) \]

If \(\hat q=0\lt q\), the forward is too high: every call is overpriced and every put underpriced. At the longest maturity, \(T=4.84\), omitting a constant \(q=3\%\) overstates the forward by \(e^{0.1452}-1\approx15.6\%\). The bias is not only vertical: in log-moneyness, the whole smile is shifted horizontally.

Horizontal shift of the smile
\[ \hat k=\ln\frac{K}{\hat F}=k-(q-\hat q)\,T \]

Here the shift is about 0.145, a third of the total standard deviation \(\sigma\sqrt T\approx0.44\) for an illustrative 20% volatility. No Heston parameter set can reproduce a horizontal translation, so the optimizer tries to mimic it with extreme values of \(\rho\) and \(\xi\).

Empirical diagnosis. With \(q\equiv0\), the fit is excellent below one year, but at \(T=4.84\) calls are overpriced and puts underpriced, with errors up to €650 on individual quotes and a global price RMSE of €135. A single constant \(q=3\%\) brings the RMSE down to €32, a 76% reduction. The residual error comes from a flat \(q\) that cannot follow the seasonal dividends.

For each expiry, put-call parity implies that the call and put price curves cross exactly at the forward. Call and put quotes are interpolated in strike with a shape-preserving PCHIP interpolator (cubic splines would create spurious oscillations), and the crossing is found by bisection on \(K\mapsto C-P=D(T)(F(T)-K)\), which is strictly decreasing:

Parity at the forward
\[ C\big(F(T),T\big)=P\big(F(T),T\big) \]

Parity is even linear in \(K\): \(C_i-P_i=\alpha+\beta K_i\). A least-squares fit on near-the-money strikes averages out quote noise and also returns an implied discount factor, a consistency check of the rate curve: \(\hat F(T)=-\hat\alpha/\hat\beta\) and \(\hat D(T)=-\hat\beta\approx e^{-r(T)T}\).

Assuming \(q(t)\) piecewise constant between listed expiries \(T_1\lt\cdots\lt T_n\), each segment is solved recursively:

Recursive dividend bootstrap
\[ q_k = \frac{1}{T_k-T_{k-1}} \ln \left( \frac{ S_0 \exp\!\left( r(T_k)T_k - \sum_{j=1}^{k-1} q_j(T_j-T_{j-1}) \right) }{F(T_k)} \right) \]

The curve is extrapolated flat before \(T_1\) and after \(T_n\), and the forward at any maturity is \(F(T)=S_0\exp\big(r(T)T-\int_0^T q(t)\,dt\big)\). The bootstrap is a discrete derivative, so it amplifies noise: an error \(\delta_k\) on \(\ln F(T_k)\) becomes

Noise amplification
\[ \delta q_k\approx\frac{\delta_{k-1}-\delta_k}{T_k-T_{k-1}} \]

With two expiries 4 days apart, a 1 basis-point error on \(\ln F\) turns into about 0.9% on \(q_k\). The bootstrapped segments at short maturities must therefore be checked, or the cumulative carry smoothed, before use.

The machinery is split into two single-responsibility classes, usable by any pricing engine:

ForwardCurve Takes listed strikes and call/put prices per expiry, solves the parity bisection and exposes calc_forward(T) for any \(T\).
DividendCurve Takes the implied forwards and the rate curve, runs the bootstrap and exposes a callable q(T), a drop-in replacement for the scalar \(q\) of HestonModel. It needs no option data, so it can also be built from futures prices.

The pipeline: (1) solve parity for each expiry to get \(F(T_k)\); (2) bootstrap \(q_k\); (3) evaluate \(F(T)\), \(q(T)\) and \(D(T)\) at each quote's maturity; (4) invert prices into implied volatilities with these inputs; (5) minimize the loss.

Why calibrate on volatility, not price.

The first implementation minimized the mean squared error on option prices:

Mean squared error on price
\[ \mathcal{L}(\Theta) = \frac{1}{N} \sum_{i=1}^{N} \big(C_{\mathrm{market},i}-C_{\mathrm{model},i}(\Theta)\big)^2 \]

It did not give good results. A 5-year CAC40 option costs about €1,000, a 9-day option a dozen euros: minimizing the price error makes the calibration concentrate on long maturities, which weigh far more. The loss retained is vega-weighted on implied volatilities, so that every maturity carries the same weight:

Vega-weighted loss
\[ \mathcal{L}(\Theta) = \sum_{i=1}^{N} \mathcal{V}_i^2 \big(\sigma_{\mathrm{market},i}-\sigma_{\mathrm{Heston},i}(\Theta)\big)^2 \]

It has two limits: options with a small vega (deep out of the money) barely count, and the vega used comes from market data, not from the model.

The initial point is \(v_0=0.04\), \(\theta=0.04\), \(\kappa=2\), \(\xi=0.3\), \(\rho=-0.7\): a 20% volatility, usual for major indices, and a return to equilibrium in about 4 months (\(\ln2/\kappa=0.35\) years). L-BFGS-B, however, is a local optimizer, and several runs showed that it does not reliably reach the global minimum.

Starting points \(\Theta\) are therefore drawn in a Latin hypercube within the bounds below, each start is refined by L-BFGS-B, and a Tikhonov penalty is added:

Bounds
\[ v_0,\theta\in[10^{-3},1],\quad \kappa\in[10^{-3},20],\quad \xi\in[10^{-2},5],\quad \rho\in[-0.999,0.999] \]
Start \(v_0\) \(\theta\) \(\kappa\) \(\xi\) \(\rho\) Evals Time (s) IV RMSE
LHS 8 0.01970.04381.3080 0.4819−0.6051 51013.660.4211
LHS 3 0.01970.04381.3076 0.4818−0.6051 3429.020.4211
LHS 1 0.01970.04381.3076 0.4819−0.6051 47412.850.4211
LHS 5 0.01980.04381.3084 0.4822−0.6051 86422.750.4211
LHS 7 0.01980.04381.3100 0.4824−0.6051 3188.410.4211
LHS 6 0.01970.04381.3057 0.4817−0.6051 41410.990.4211
LHS 4 0.01970.04381.3074 0.4818−0.6049 45011.900.4211
LHS 2 0.01960.04071.9751 0.6055−0.6223 37810.150.4441
LHS 10 0.02870.036311.9019 3.2605−0.5064 1985.280.6781
LHS 9 0.03140.036114.8552 4.0447−0.5025 2045.460.6957

Multi-start calibration. IV RMSE in vol points.

Seven starts out of ten fall into the same minimum, with the same RMSE of 0.4211 vol points. This point is used to rebuild the implied volatility surface. The other runs stop in worse local minima, with \(\kappa\) up to 14.9 and \(\xi\) up to 4.0: a sign of the ill-posedness discussed in section 11.

\(v_0 = 0.0197\) Spot volatility \(\sqrt{v_0}\approx14.0\%\).
\(\theta = 0.0438\) Long-run volatility \(\sqrt{\theta}\approx20.9\%\): an upward-sloping ATM term structure.
\(\kappa = 1.308\) Half-life of a variance shock \(\ln 2/\kappa\approx0.53\) years.
\(\xi = 0.482\) Feller ratio \(2\kappa\theta/\xi^2\approx0.49\): the condition is violated.
\(\rho = -0.605\) Strongly negative, as expected for an equity index.

Interpolating the surface without arbitrage.

The market implied volatility surface is obtained by inverting Black-Scholes at every quoted point, then interpolated linearly across strikes and in total variance (V2T) across expiries. A quoted surface is free of static arbitrage if and only if:

Static no-arbitrage
\[ -e^{-r(t)t}\le\frac{\partial C}{\partial K}\le0, \qquad \frac{\partial^2 C}{\partial K^2}\ge0, \qquad t\mapsto\frac{C\big(F(t)e^{k},t\big)}{D(t)F(t)}\ \text{non-decreasing} \]

The second condition says that the risk-neutral density is non-negative (Breeden–Litzenberger). The calendar condition is written in forward-normalized form, one more reason to get \(F(t)\) right. Heston prices are arbitrage-free by construction, but noisy quotes are not, and must be filtered before calibration.

01

Linear

The first interpolation that comes to mind. It gives a good overview of the surface, but guarantees none of the conditions above.

Overview
02

Variance-to-Time (V2T)

Interpolating implied volatility directly in time often creates calendar arbitrage. V2T interpolates the total variance \(w(k,T)=\sigma^2_{\mathrm{impl}}(k,T)\,T\) with a monotone scheme, which guarantees \(\partial w/\partial T\ge0\) for every \(k\): positive forward volatilities and well-behaved Dupire local volatility inputs.

Calendar arbitrage
03

SVI

Gatheral's parametric smile, fitted slice by slice on the total variance:

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

\(a\) sets the level, \(b\) the slope of the wings, \(\rho\) the skew, \(m\) the horizontal shift and \(\sigma\) the smoothness at the money. It reproduces the asymptotic wings of stochastic volatility models such as Heston.

Parametric
04

SSVI

Gatheral and Jacquier's surface extension, conditioned on the ATM total variance \(\theta_t=w(0,t)\):

\[ w(k,\theta_t)=\frac{\theta_t}{2}\left(1+\rho\varphi(\theta_t)k+\sqrt{\big(\varphi(\theta_t)k+\rho\big)^2+1-\rho^2}\right) \]

It gives explicit conditions for a fully arbitrage-free surface: no calendar arbitrage as long as \(\theta_t\) is strictly increasing and \(\frac{d}{d\theta}\big(\theta\varphi(\theta)\big)\ge0\), for instance with the Heston-like \(\varphi(\theta)=\frac{1}{\gamma\theta}\left(1-\frac{1-e^{-\gamma\theta}}{\gamma\theta}\right)\).

Arbitrage-free surface

Impact of correcting the forward.

Price RMSE on the 142-quote CAC40 set across the successive stages of debugging:

Configuration \(q\) Price RMSE (€) Feller ratio
Baseline (buggy objective) constant, 0 135.0 ≈ 0.17
Objective bug fixed constant, 0 135.0 —
Constant dividend proxy constant, 3% 32.0 —
Bootstrapped dividend curve \(q(T)\), piecewise constant pending pending

The objective bug mainly caused NaN propagation and optimizer instability, not a different converged minimum.

The drop from €135 to €32 with a crude constant dividend confirms that the forward, not the stochastic volatility dynamics, was the dominant source of error at long maturity. The fully bootstrapped, seasonal \(q(T)\) is expected to close most of the remaining gap and to bring \(\rho\) and \(\xi\) back from their bounds toward economically plausible values.

On the smiles, the model reproduces the market well from the second expiry onward. The shortest expiry (\(T=0.02\) years) is completely mispriced: the structural limit of a pure diffusion (section 11). As a sanity check, semi-analytical prices are compared with Monte Carlo prices from HestonModel.generate_paths.

The exercise suggests a checklist linking what the calibration shows to its likely cause, in the order in which to investigate:

01

Calls overpriced, puts underpriced, growing with \(T\)

Overstated forward (missing or too small \(q\)). Compare the parity forward \(\hat F(T)\) with \(S_0e^{(r-q)T}\).

Forward
02

\(\rho\) pinned at a bound, Feller ratio \(\ll 1\)

A forward level error compensated by skew and vol-of-vol. Fix the forward first, then recalibrate.

Forward
03

NaN in the objective, line-search failures

Implied volatility inversion failures propagating. Penalize the residual and check the arbitrage bounds.

Numerics
04

Erratic parameters across consecutive days

An ill-posed inverse problem. Compute \(\mathrm{cond}(J^{\top}J)\) and regularize toward the previous day.

Identifiability
05

Bad fit only below one month

The structural limit of a diffusion. Consider a jump extension (Bates).

Model structure

Simulating the variance process.

Calibration relies on Fourier pricing, but path-dependent products need Monte Carlo under the calibrated parameters. A naive Euler scheme can produce negative variances when the Feller condition is violated. Full truncation replaces \(v\) with \(v^+=\max(v,0)\) in the drift and diffusion, with time step \(\Delta\):

Euler with full truncation
\[ \begin{aligned} \tilde v_{t+\Delta} &= \tilde v_t+\kappa\big(\theta-\tilde v_t^+\big)\Delta+\xi\sqrt{\tilde v_t^+}\sqrt{\Delta}\,Z_v,\\ \ln\tilde S_{t+\Delta} &= \ln\tilde S_t+\left(r-q-\tfrac12\tilde v_t^+\right)\Delta+\sqrt{\tilde v_t^+}\sqrt{\Delta}\,Z_S \end{aligned} \]

with \(Z_v=\rho Z_S+\sqrt{1-\rho^2}\,Z_{\perp}\). The scheme is simple and robust, but biased for large \(\Delta\) or when \(2\kappa\theta/\xi^2\ll1\).

Andersen's Quadratic-Exponential (QE) scheme uses the exact transition law by moment matching. Given \(v_t\), it computes the exact conditional mean and variance, then \(\psi=s^2/m^2\):

Conditional moments
\[ m=\theta+(v_t-\theta)e^{-\kappa\Delta}, \qquad s^2=\frac{v_t\xi^2e^{-\kappa\Delta}}{\kappa}\left(1-e^{-\kappa\Delta}\right)+\frac{\theta\xi^2}{2\kappa}\left(1-e^{-\kappa\Delta}\right)^2 \]

Below a switching level \(\psi_c\) (typically 1.5), the next variance is a squared shifted Gaussian, \(a(b+Z)^2\). Above it, it is drawn from a mixture of a point mass at zero and an exponential law. The scheme matches the first two conditional moments exactly and behaves much better than Euler when Feller is violated, which is precisely the regime met in practice.

What the model still gets wrong.

01

Short-maturity skew

Being a diffusion, Heston cannot produce steep skews below one month: its ATM skew tends to the finite constant \(\rho\xi/(4\sqrt{v_0})\), whereas empirical equity skews diverge roughly like \(T^{-\gamma}\), with \(\gamma\) between 0.3 and 0.5. The Bates model adds jumps, and its characteristic function is the Heston one times a jump factor, so the whole Fourier machinery applies unchanged:

\[ \phi_{\mathrm{Bates}}(u;T)=\phi_{\mathrm{Heston}}(u;T)\, \exp\Big\{\lambda T\big[e^{iu\mu_J-u^2\delta^2/2}-1-iu\bar k\big]\Big\} \]
Pure diffusion
02

Branch cuts

The complex logarithm of the original formulation is multi-valued and causes price discontinuities during optimization. The Gatheral / Albrecher form used here removes the problem.

Numerics
03

Parameter instability and model risk

Several distinct \(\Theta\) give almost identical vanilla prices. Calibrated parameters then jump from one day to the next, and so do hedge ratios. Two exotics with identical vanilla prices can have very different Heston prices, a genuine source of model risk.

Ill-posed problem
04

Forward mis-specification as a confound

For the optimizer, a wrong forward is indistinguishable from a deficiency of the dynamics: both show up as a poor fit corrected through extreme \(\rho\) and \(\xi\). Parameters at their bounds or a Feller ratio far below one should first be investigated as a market-data problem (rates, dividends, forward).

Market inputs
05

Convergence of the Fourier integral

The integrals are truncated at \(u_{\max}=100\) whatever the maturity. The integrand decays more slowly at short maturities, so convergence is not guaranteed at the extremes. The quadrature error estimate should be monitored, with a maturity-adaptive bound where needed.

Numerics
06

Static versus dynamic consistency

Each daily calibration fits well, but the model assumes constant parameters while successive calibrations are not stable. This time-inconsistency further motivates regularization toward the previous day's parameters.

Model structure

From dynamics to robust calibration.

Pricing Characteristic function from the Riccati system in Gatheral's stable form, prices by Gil–Pelaez inversion, closed-form Delta and Gamma.
Forward The most important correction: a dividend yield alone takes the price RMSE from €135 to €32. The forward is rebuilt from put-call parity with a bootstrapped dividend curve.
Calibration Vega-weighted loss so that every maturity counts; Latin hypercube multi-start with L-BFGS-B, converging to 0.42 vol points.
Next steps Full run with the bootstrapped dividend curve and IV RMSE per maturity; analytical Greeks; Carr–Madan and COS pricing; Differential Evolution pre-search with Tikhonov regularization; QE scheme; 3D surface module; hybrid IV/APE loss; Bates jumps and stochastic local volatility.
Before attributing a poor fit to the stochastic volatility dynamics, first rule out a mis-specified market input. Here, the missing maturity-dependent dividend yield accounted for most of the calibration error: it overprices calls, underprices puts and forces the optimizer to distort \(\rho\) and \(\xi\). For exotic barrier options, coupling Heston with local volatility (SLV) remains the industry standard.

Paper and scripts.

Full paper

Calibration of the Heston Stochastic Volatility Model: theoretical framework, numerical methods and limitations.

heston_calibration.pdf
Download ↓

Heston calibration scripts

The full Python implementation: HestonModel, ForwardCurve, DividendCurve, the minimizers and the notebook.

heston_scripts.zip
Download ↓