Table of Contents

A volatility surface and Heston's calibration

The DAX options market of 5 July 2002, thirteen strikes by eight expiries, as QuantLib's tests calibrate Heston's model to it (from A. Sepp's "Pricing European-Style Options under Jump Diffusion Processes with Stochastic Volatility"). The calibrated parameters match QuantLib's on the same data. The sample, VolatilityAndHeston.cs, carries QuantLib's notice.

The surface

The quoted Black volatilities as a surface by expiry and strike, BlackVarianceSurface, which interpolates in variance:

Date today = Context.EvaluationDate;
Date[] expiries = [.. Days.Select(d => today + d)];
var vols = new Matrix(Strikes.Count, Days.Count);
for (int s = 0; s < Strikes.Count; ++s)
{
    for (int m = 0; m < Days.Count; ++m)
    {
        vols[s, m] = Vols[s, m];
    }
}

var surface = new BlackVarianceSurface(today, new TARGET(), expiries, Strikes, vols, new Actual365Fixed());

Read back at three of its strikes, it gives the quotes:

Expiry, days Vol at 4,000 Vol at 4,400 Vol at 5,000
13 45.41% 37.26% 33.43%
41 38.69% 33.96% 29.59%
75 34.92% 31.08% 27.05%
165 31.49% 27.81% 25.40%
256 29.63% 27.88% 25.04%
345 29.26% 27.22% 24.64%
524 28.19% 26.61% 24.48%
703 28.00% 26.86% 24.62%

Between the nodes it interpolates in variance, bilinearly by default.

Heston's calibration

A HestonModelHelper per quote, each priced by the semi-analytic engine, and Levenberg-Marquardt minimizing the vols' errors:

Date today = Context.EvaluationDate;
DayCounter dayCounter = new Actual365Fixed();
var dates = new List<Date> { today };
var rates = new List<double> { 0.0357 };
double[] zeroRates = [0.0357, 0.0349, 0.0341, 0.0355, 0.0359, 0.0368, 0.0386, 0.0401];
for (int i = 0; i < Days.Count; ++i)
{
    dates.Add(today + Days[i]);
    rates.Add(zeroRates[i]);
}

var riskFree = new ZeroCurve(dates, rates, dayCounter);
var dividend = new FlatForward(today, 0.0, dayCounter);

var helpers = new List<HestonModelHelper>();
for (int s = 0; s < Strikes.Count; ++s)
{
    for (int m = 0; m < Days.Count; ++m)
    {
        var maturity = new Period((int)((Days[m] + 3) / 7.0), TimeUnit.Weeks); // to the nearest week
        helpers.Add(new HestonModelHelper(maturity, new TARGET(), Spot, Strikes[s], Vols[s, m], riskFree, dividend,
            Context, BlackCalibrationHelperCalibrationErrorType.ImpliedVolError)
            .SetPricingEngine<HestonModel>((option, context, model) =>
                AnalyticHestonEngine.Calculate(option, context, model, 64)));
    }
}

var start = new HestonModel(new HestonProcess(riskFree, dividend, Spot, 0.1, 1.0, 0.1, 0.5, -0.5));
HestonModel calibrated = start.Calibrate(helpers, new LevenbergMarquardt(1e-8, 1e-8, 1e-8),
    new EndCriteria(400, 40, 1.0e-8, 1.0e-8, 1.0e-8), Context).Model;

The calibrated model:

Parameter Calibrated
θ, the long-run variance 0.0745912
κ, the mean reversion 15.6627
σ, the volatility of variance 3.36192
ρ, the correlation -0.511492
v₀, the initial variance 0.195662
Sum of squared vol errors, in vol points² 177.233

Calibrate returns a new model with its record (the criterion that ended the optimization, the problem's values, the function evaluations), and leaves the starting model as it was.