QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
hestonslvfdmmodel.cpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2015 Johannes Göttker-Schnetmann
5 Copyright (C) 2015 Klaus Spanderen
6
7 This file is part of QuantLib, a free-software/open-source library
8 for financial quantitative analysts and developers - http://quantlib.org/
9
10 QuantLib is free software: you can redistribute it and/or modify it
11 under the terms of the QuantLib license. You should have received a
12 copy of the license along with this program; if not, please email
13 <quantlib-dev@lists.sf.net>. The license is also available online at
14 <http://quantlib.org/license.shtml>.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21
22#include <ql/models/equity/hestonslvfdmmodel.hpp>
23#include <ql/math/distributions/normaldistribution.hpp>
24#include <ql/math/integrals/discreteintegrals.hpp>
25#include <ql/math/interpolations/bilinearinterpolation.hpp>
26#include <ql/methods/finitedifferences/meshers/concentrating1dmesher.hpp>
27#include <ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp>
28#include <ql/methods/finitedifferences/meshers/predefined1dmesher.hpp>
29#include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>
30#include <ql/methods/finitedifferences/operators/fdmhestonfwdop.hpp>
31#include <ql/methods/finitedifferences/schemes/craigsneydscheme.hpp>
32#include <ql/methods/finitedifferences/schemes/douglasscheme.hpp>
33#include <ql/methods/finitedifferences/schemes/expliciteulerscheme.hpp>
34#include <ql/methods/finitedifferences/schemes/hundsdorferscheme.hpp>
35#include <ql/methods/finitedifferences/schemes/impliciteulerscheme.hpp>
36#include <ql/methods/finitedifferences/schemes/modifiedcraigsneydscheme.hpp>
37#include <ql/methods/finitedifferences/solvers/fdmbackwardsolver.hpp>
38#include <ql/methods/finitedifferences/utilities/fdmmesherintegral.hpp>
39#include <ql/methods/finitedifferences/utilities/localvolrndcalculator.hpp>
40#include <ql/methods/finitedifferences/utilities/squarerootprocessrndcalculator.hpp>
41#include <ql/models/equity/hestonmodel.hpp>
42#include <ql/quotes/simplequote.hpp>
43#include <ql/termstructures/volatility/equityfx/fixedlocalvolsurface.hpp>
44#include <ql/termstructures/volatility/equityfx/localvoltermstructure.hpp>
45#include <ql/timegrid.hpp>
46#include <functional>
47#include <memory>
48#include <utility>
49
50namespace QuantLib {
51
52 namespace {
53 ext::shared_ptr<Fdm1dMesher> varianceMesher(
54 const SquareRootProcessRNDCalculator& rnd,
55 Time t0, Time t1, Size vGrid,
56 Real v0, const HestonSLVFokkerPlanckFdmParams& params) {
57
58 std::vector<ext::tuple<Real, Real, bool> > cPoints;
59
60 const Real v0Density = params.v0Density;
61 const Real upperBoundDensity = params.vUpperBoundDensity;
62 const Real lowerBoundDensity = params.vLowerBoundDensity;
63
64 Real lowerBound = Null<Real>(), upperBound = -Null<Real>();
65
66 for (Size i=0; i <= 10; ++i) {
67 const Time t = t0 + i/10.0*(t1-t0);
68 lowerBound = std::min(
69 lowerBound, rnd.invcdf(params.vLowerEps, t));
70 upperBound = std::max(
71 upperBound, rnd.invcdf(1.0-params.vUpperEps, t));
72 }
73
74 lowerBound = std::max(lowerBound, params.vMin);
75 switch (params.trafoType) {
77 {
78 lowerBound = std::log(lowerBound);
79 upperBound = std::log(upperBound);
80
81 const Real v0Center = std::log(v0);
82
83 cPoints = {
84 ext::make_tuple(lowerBound, lowerBoundDensity, false),
85 ext::make_tuple(v0Center, v0Density, true),
86 ext::make_tuple(upperBound, upperBoundDensity, false)
87 };
88
89 return ext::make_shared<Concentrating1dMesher>(
90 lowerBound, upperBound, vGrid, cPoints, 1e-8);
91 }
92 break;
94 {
95 const Real v0Center = v0;
96
97 cPoints = {
98 ext::make_tuple(lowerBound, lowerBoundDensity, false),
99 ext::make_tuple(v0Center, v0Density, true),
100 ext::make_tuple(upperBound, upperBoundDensity, false)
101 };
102
103 return ext::make_shared<Concentrating1dMesher>(
104 lowerBound, upperBound, vGrid, cPoints, 1e-8);
105 }
106 break;
108 {
109 const Real v0Center = v0;
110
111 cPoints = {
112 ext::make_tuple(lowerBound, lowerBoundDensity, false),
113 ext::make_tuple(v0Center, v0Density, true),
114 ext::make_tuple(upperBound, upperBoundDensity, false)
115 };
116
117 return ext::make_shared<Concentrating1dMesher>(
118 lowerBound, upperBound, vGrid, cPoints, 1e-8);
119 }
120 break;
121 default:
122 QL_FAIL("transformation type is not implemented");
123 }
124 }
125
126 Real integratePDF(const Array& p,
127 const ext::shared_ptr<FdmMesherComposite>& mesher,
129 Real alpha) {
130
131 if (trafoType != FdmSquareRootFwdOp::Power) {
132 return FdmMesherIntegral(
133 mesher, DiscreteSimpsonIntegral()).integrate(p);
134 }
135 else {
136 Array tp(p.size());
137 for (const auto& iter : *mesher->layout()) {
138 const Size idx = iter.index();
139 const Real nu = mesher->location(iter, 1);
140
141 tp[idx] = p[idx]*std::pow(nu, alpha-1);
142 }
143
144 return FdmMesherIntegral(
145 mesher, DiscreteSimpsonIntegral()).integrate(tp);
146 }
147 }
148
149
150 Array rescalePDF(
151 const Array& p,
152 const ext::shared_ptr<FdmMesherComposite>& mesher,
154
155 return p/integratePDF(p, mesher, trafoType, alpha);
156 }
157
158
159 template <class Interpolator>
160 Array reshapePDF(
161 const Array& p,
162 const ext::shared_ptr<FdmMesherComposite>& oldMesher,
163 const ext::shared_ptr<FdmMesherComposite>& newMesher,
164 const Interpolator& interp = Interpolator()) {
165
166 QL_REQUIRE( oldMesher->layout()->size() == newMesher->layout()->size()
167 && oldMesher->layout()->size() == p.size(),
168 "inconsistent mesher or vector size given");
169
170 Matrix m(oldMesher->layout()->dim()[1], oldMesher->layout()->dim()[0]);
171 for (Size i=0; i < m.rows(); ++i) {
172 std::copy(p.begin() + i*m.columns(),
173 p.begin() + (i+1)*m.columns(), m.row_begin(i));
174 }
175 const Interpolation2D interpol = interp.interpolate(
176 oldMesher->getFdm1dMeshers()[0]->locations().begin(),
177 oldMesher->getFdm1dMeshers()[0]->locations().end(),
178 oldMesher->getFdm1dMeshers()[1]->locations().begin(),
179 oldMesher->getFdm1dMeshers()[1]->locations().end(), m);
180
181 Array pNew(p.size());
182 for (const auto& iter : *newMesher->layout()) {
183 const Real x = newMesher->location(iter, 0);
184 const Real v = newMesher->location(iter, 1);
185
186 if ( x > interpol.xMax() || x < interpol.xMin()
187 || v > interpol.yMax() || v < interpol.yMin() ) {
188 pNew[iter.index()] = 0;
189 }
190 else {
191 pNew[iter.index()] = interpol(x, v);
192 }
193 }
194
195 return pNew;
196 }
197
198 class FdmScheme {
199 public:
200 virtual ~FdmScheme() = default;
201 virtual void step(Array& a, Time t) = 0;
202 virtual void setStep(Time dt) = 0;
203 };
204
205 template <class T>
206 class FdmSchemeWrapper : public FdmScheme {
207 public:
208 explicit FdmSchemeWrapper(T* scheme)
209 : scheme_(scheme) { }
210
211 void step(Array& a, Time t) override { scheme_->step(a, t); }
212 void setStep(Time dt) override { scheme_->setStep(dt); }
213
214 private:
215 const std::unique_ptr<T> scheme_;
216 };
217
218 ext::shared_ptr<FdmScheme> fdmSchemeFactory(
219 const FdmSchemeDesc desc,
220 const ext::shared_ptr<FdmLinearOpComposite>& op) {
221
222 switch (desc.type) {
224 return ext::shared_ptr<FdmScheme>(
225 new FdmSchemeWrapper<HundsdorferScheme>(
226 new HundsdorferScheme(desc.theta, desc.mu, op)));
228 return ext::shared_ptr<FdmScheme>(
229 new FdmSchemeWrapper<DouglasScheme>(
230 new DouglasScheme(desc.theta, op)));
232 return ext::shared_ptr<FdmScheme>(
233 new FdmSchemeWrapper<CraigSneydScheme>(
234 new CraigSneydScheme(desc.theta, desc.mu, op)));
236 return ext::shared_ptr<FdmScheme>(
237 new FdmSchemeWrapper<ModifiedCraigSneydScheme>(
238 new ModifiedCraigSneydScheme(
239 desc.theta, desc.mu, op)));
241 return ext::shared_ptr<FdmScheme>(
242 new FdmSchemeWrapper<ImplicitEulerScheme>(
243 new ImplicitEulerScheme(op)));
245 return ext::shared_ptr<FdmScheme>(
246 new FdmSchemeWrapper<ExplicitEulerScheme>(
247 new ExplicitEulerScheme(op)));
248 default:
249 QL_FAIL("Unknown scheme type");
250 }
251 }
252 }
253
255 Handle<HestonModel> hestonModel,
256 const Date& endDate,
258 const bool logging,
259 std::vector<Date> mandatoryDates,
260 const Real mixingFactor)
261 : localVol_(std::move(localVol)), hestonModel_(std::move(hestonModel)), endDate_(endDate),
262 params_(std::move(params)), mandatoryDates_(std::move(mandatoryDates)),
263 mixingFactor_(mixingFactor), logging_(logging) {
264
267 }
268
269 ext::shared_ptr<HestonProcess> HestonSLVFDMModel::hestonProcess() const {
270 return hestonModel_->process();
271 }
272
273 ext::shared_ptr<LocalVolTermStructure> HestonSLVFDMModel::localVol() const {
274 return localVol_.currentLink();
275 }
276
277 ext::shared_ptr<LocalVolTermStructure>
279 calculate();
280
281 return leverageFunction_;
282 }
283
285 logEntries_.clear();
286
287 const ext::shared_ptr<HestonProcess> hestonProcess
288 = hestonModel_->process();
289 const ext::shared_ptr<Quote> spot
290 = hestonProcess->s0().currentLink();
291 const ext::shared_ptr<YieldTermStructure> rTS
292 = hestonProcess->riskFreeRate().currentLink();
293 const ext::shared_ptr<YieldTermStructure> qTS
294 = hestonProcess->dividendYield().currentLink();
295
296 const Real v0 = hestonProcess->v0();
297 const Real kappa = hestonProcess->kappa();
298 const Real theta = hestonProcess->theta();
299 const Real sigma = hestonProcess->sigma();
300 const Real mixedSigma = mixingFactor_ * sigma;
301 const Real alpha = 2*kappa*theta/(mixedSigma*mixedSigma);
302
303 const Size xGrid = params_.xGrid;
304 const Size vGrid = params_.vGrid;
305
306 const DayCounter dc = rTS->dayCounter();
307 const Date referenceDate = rTS->referenceDate();
308
309 const Time T = dc.yearFraction(referenceDate, endDate_);
310
311 QL_REQUIRE(referenceDate < endDate_,
312 "reference date must be smaller than final calibration date");
313
314 QL_REQUIRE(localVol_->maxTime() >= T,
315 "final calibration maturity exceeds local volatility surface");
316
317 // set-up exponential time step scheme
318 const Time maxDt = 1.0/params_.tMaxStepsPerYear;
319 const Time minDt = 1.0/params_.tMinStepsPerYear;
320
321 Time tIdx=0.0;
322 std::vector<Time> times(1, tIdx);
323 times.reserve(Size(T*params_.tMinStepsPerYear));
324 while (tIdx < T) {
325 const Real decayFactor = std::exp(-params_.tStepNumberDecay*tIdx);
326 const Time dt = maxDt*decayFactor + minDt*(1.0-decayFactor);
327
328 times.push_back(std::min(T, tIdx+=dt));
329 }
330
331 for (auto mandatoryDate : mandatoryDates_) {
332 times.push_back(dc.yearFraction(referenceDate, mandatoryDate));
333 }
334
335 const ext::shared_ptr<TimeGrid> timeGrid(
336 new TimeGrid(times.begin(), times.end()));
337
338 // build 1d meshers
339 const LocalVolRNDCalculator localVolRND(
340 spot, rTS, qTS, localVol_.currentLink(),
341 timeGrid, xGrid,
345
346 const std::vector<Size> rescaleSteps
347 = localVolRND.rescaleTimeSteps();
348
349 const SquareRootProcessRNDCalculator squareRootRnd(
350 v0, kappa, theta, mixedSigma);
351
354
355 std::vector<ext::shared_ptr<Fdm1dMesher> > xMesher, vMesher;
356 xMesher.reserve(timeGrid->size());
357 vMesher.reserve(timeGrid->size());
358
359 xMesher.push_back(localVolRND.mesher(0.0));
360 vMesher.push_back(ext::make_shared<Predefined1dMesher>(
361 std::vector<Real>(vGrid, v0)));
362
363 Size rescaleIdx = 0;
364 for (Size i=1; i < timeGrid->size(); ++i) {
365 xMesher.push_back(localVolRND.mesher(timeGrid->at(i)));
366
367 if ((rescaleIdx < rescaleSteps.size())
368 && (i == rescaleSteps[rescaleIdx])) {
369 ++rescaleIdx;
370 vMesher.push_back(varianceMesher(squareRootRnd,
371 timeGrid->at(rescaleSteps[rescaleIdx-1]),
372 (rescaleIdx < rescaleSteps.size())
373 ? timeGrid->at(rescaleSteps[rescaleIdx])
374 : timeGrid->back(),
375 vGrid, v0, params_));
376 }
377 else
378 vMesher.push_back(vMesher.back());
379 }
380
381 // start probability distribution
382 ext::shared_ptr<FdmMesherComposite> mesher
383 = ext::make_shared<FdmMesherComposite>(
384 xMesher.at(1), vMesher.at(1));
385
386 const Volatility lv0
387 = localVol_->localVol(0.0, spot->value())/std::sqrt(v0);
388
389 ext::shared_ptr<Matrix> L(new Matrix(xGrid, timeGrid->size()));
390
391 const Real l0 = lv0;
392 std::fill(L->column_begin(0),L->column_end(0), l0);
393 std::fill(L->column_begin(1),L->column_end(1), l0);
394
395 // create strikes from meshers
396 std::vector<ext::shared_ptr<std::vector<Real> > > vStrikes(
397 timeGrid->size());
398
399 for (Size i=0; i < timeGrid->size(); ++i) {
400 vStrikes[i] = ext::make_shared<std::vector<Real> >(xGrid);
401 if (xMesher[i]->locations().front()
402 == xMesher[i]->locations().back()) {
403 std::fill(vStrikes[i]->begin(), vStrikes[i]->end(),
404 std::exp(xMesher[i]->locations().front()));
405 }
406 else {
407 std::transform(xMesher[i]->locations().begin(),
408 xMesher[i]->locations().end(),
409 vStrikes[i]->begin(),
410 [](Real x) -> Real { return std::exp(x); });
411 }
412 }
413
414 const ext::shared_ptr<FixedLocalVolSurface> leverageFct(
415 new FixedLocalVolSurface(referenceDate, times, vStrikes, L, dc));
416
417 ext::shared_ptr<FdmLinearOpComposite> hestonFwdOp(
418 new FdmHestonFwdOp(mesher, hestonProcess, trafoType, leverageFct, mixingFactor_));
419
420 Array p = FdmHestonGreensFct(mesher, hestonProcess, trafoType, lv0)
421 .get(timeGrid->at(1), params_.greensAlgorithm);
422
423 if (logging_) {
424 const LogEntry entry = { timeGrid->at(1),
425 ext::make_shared<Array>(p), mesher };
426 logEntries_.push_back(entry);
427 }
428
429 for (Size i=2; i < times.size(); ++i) {
430 const Time t = timeGrid->at(i);
431 const Time dt = t - timeGrid->at(i-1);
432
433 if ( mesher->getFdm1dMeshers()[0] != xMesher[i]
434 || mesher->getFdm1dMeshers()[1] != vMesher[i]) {
435 const ext::shared_ptr<FdmMesherComposite> newMesher(
436 new FdmMesherComposite(xMesher[i], vMesher[i]));
437
438 p = reshapePDF<Bilinear>(p, mesher, newMesher);
439 mesher = newMesher;
440
441 p = rescalePDF(p, mesher, trafoType, alpha);
442
443 hestonFwdOp = ext::shared_ptr<FdmLinearOpComposite>(
444 new FdmHestonFwdOp(mesher, hestonProcess,
445 trafoType, leverageFct, mixingFactor_));
446 }
447
448 Array pn = p;
449 const Array x(Exp(
450 Array(mesher->getFdm1dMeshers()[0]->locations().begin(),
451 mesher->getFdm1dMeshers()[0]->locations().end())));
452 const Array v(
453 mesher->getFdm1dMeshers()[1]->locations().begin(),
454 mesher->getFdm1dMeshers()[1]->locations().end());
455
456 // predictor corrector steps
457 for (Size r=0; r < params_.predictionCorretionSteps; ++r) {
458 const FdmSchemeDesc fdmSchemeDesc
459 = (i < params_.nRannacherTimeSteps + 2)
462
463 const ext::shared_ptr<FdmScheme> fdmScheme(
464 fdmSchemeFactory(fdmSchemeDesc, hestonFwdOp));
465
466 for (Size j=0; j < x.size(); ++j) {
467 Array pSlice(vGrid);
468 for (Size k=0; k < vGrid; ++k)
469 pSlice[k] = pn[j + k*xGrid];
470
471 const Real pInt = (trafoType == FdmSquareRootFwdOp::Power)
472 ? DiscreteSimpsonIntegral()(v, Pow(v, alpha-1)*pSlice)
473 : DiscreteSimpsonIntegral()(v, pSlice);
474
475 const Real vpInt = (trafoType == FdmSquareRootFwdOp::Log)
476 ? DiscreteSimpsonIntegral()(v, Exp(v)*pSlice)
477 : (trafoType == FdmSquareRootFwdOp::Power)
478 ? DiscreteSimpsonIntegral()(v, Pow(v, alpha)*pSlice)
479 : DiscreteSimpsonIntegral()(v, v*pSlice);
480
481 const Real scale = pInt/vpInt;
482 const Volatility localVol = localVol_->localVol(t, x[j]);
483
484 const Real l = (scale >= 0.0)
485 ? localVol*std::sqrt(scale) : Real(1.0);
486
487 (*L)[j][i] = std::min(50.0, std::max(0.001, l));
488
489 leverageFct->setInterpolation(Linear());
490 }
491
492 const Real sLowerBound = std::max(x.front(),
493 std::exp(localVolRND.invcdf(
495 const Real sUpperBound = std::min(x.back(),
496 std::exp(localVolRND.invcdf(
497 1.0-params_.leverageFctPropEps, t)));
498
499 const Real lowerL = leverageFct->localVol(t, sLowerBound);
500 const Real upperL = leverageFct->localVol(t, sUpperBound);
501
502 for (Size j=0; j < x.size(); ++j) {
503 if (x[j] < sLowerBound)
504 std::fill(L->row_begin(j)+i,
505 std::min(L->row_begin(j)+i+1, L->row_end(j)),
506 lowerL);
507 else if (x[j] > sUpperBound)
508 std::fill(L->row_begin(j)+i,
509 std::min(L->row_begin(j)+i+1, L->row_end(j)),
510 upperL);
511 else if ((*L)[j][i] == Null<Real>())
512 QL_FAIL("internal error");
513 }
514 leverageFct->setInterpolation(Linear());
515
516 pn = p;
517
518 fdmScheme->setStep(dt);
519 fdmScheme->step(pn, t);
520 }
521 p = pn;
522 p = rescalePDF(p, mesher, trafoType, alpha);
523
524 if (logging_) {
525 const LogEntry entry
526 = { t, ext::make_shared<Array>(p), mesher };
527 logEntries_.push_back(entry);
528 }
529 }
530
531 leverageFunction_ = leverageFct;
532 }
533
534 const std::list<HestonSLVFDMModel::LogEntry>& HestonSLVFDMModel::logEntries()
535 const {
537 return logEntries_;
538 }
539}
540
1-D array used in linear algebra.
Definition: array.hpp:52
Real back() const
Definition: array.hpp:458
Size size() const
dimension of the array
Definition: array.hpp:495
Real front() const
Definition: array.hpp:451
Concrete date class.
Definition: date.hpp:125
day counter class
Definition: daycounter.hpp:44
Time yearFraction(const Date &, const Date &, const Date &refPeriodStart=Date(), const Date &refPeriodEnd=Date()) const
Returns the period between two dates as a fraction of year.
Definition: daycounter.hpp:128
Array get(Time t, Algorithm algorithm) const
Shared handle to an observable.
Definition: handle.hpp:41
ext::shared_ptr< LocalVolTermStructure > leverageFunction() const
void performCalculations() const override
HestonSLVFDMModel(Handle< LocalVolTermStructure > localVol, Handle< HestonModel > hestonModel, const Date &endDate, HestonSLVFokkerPlanckFdmParams params, bool logging=false, std::vector< Date > mandatoryDates=std::vector< Date >(), Real mixingFactor=1.0)
const std::list< LogEntry > & logEntries() const
const HestonSLVFokkerPlanckFdmParams params_
ext::shared_ptr< LocalVolTermStructure > leverageFunction_
std::list< LogEntry > logEntries_
const Handle< LocalVolTermStructure > localVol_
const Handle< HestonModel > hestonModel_
const std::vector< Date > mandatoryDates_
ext::shared_ptr< HestonProcess > hestonProcess() const
ext::shared_ptr< LocalVolTermStructure > localVol() const
virtual void calculate() const
Definition: lazyobject.hpp:253
Linear-interpolation factory and traits
std::vector< Size > rescaleTimeSteps() const
ext::shared_ptr< Fdm1dMesher > mesher(Time t) const
Real invcdf(Real p, Time t) const override
Matrix used in linear algebra.
Definition: matrix.hpp:41
template class providing a null value for a given type.
Definition: null.hpp:76
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:228
time grid class
Definition: timegrid.hpp:43
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
QL_REAL Real
real number
Definition: types.hpp:50
Real Volatility
volatility
Definition: types.hpp:78
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
Array Pow(const Array &v, Real alpha)
Definition: array.hpp:889
Array Exp(const Array &v)
Definition: array.hpp:875
STL namespace.
static FdmSchemeDesc ImplicitEuler()
const FdmSquareRootFwdOp::TransformationType trafoType
const FdmHestonGreensFct::Algorithm greensAlgorithm