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.