QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.38
Loading...
Searching...
No Matches
hestonslvfdmmodel.cpp
Go to the documentation of this file.
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
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<std::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 std::make_tuple(lowerBound, lowerBoundDensity, false),
85 std::make_tuple(v0Center, v0Density, true),
86 std::make_tuple(upperBound, upperBoundDensity, false)
87 };
88
89 return ext::make_shared<Concentrating1dMesher>(
90 lowerBound, upperBound, vGrid, cPoints, 1e-8);
91 }
93 {
94 const Real v0Center = v0;
95
96 cPoints = {
97 std::make_tuple(lowerBound, lowerBoundDensity, false),
98 std::make_tuple(v0Center, v0Density, true),
99 std::make_tuple(upperBound, upperBoundDensity, false)
100 };
101
102 return ext::make_shared<Concentrating1dMesher>(
103 lowerBound, upperBound, vGrid, cPoints, 1e-8);
104 }
106 {
107 const Real v0Center = v0;
108
109 cPoints = {
110 std::make_tuple(lowerBound, lowerBoundDensity, false),
111 std::make_tuple(v0Center, v0Density, true),
112 std::make_tuple(upperBound, upperBoundDensity, false)
113 };
114
115 return ext::make_shared<Concentrating1dMesher>(
116 lowerBound, upperBound, vGrid, cPoints, 1e-8);
117 }
118 default:
119 QL_FAIL("transformation type is not implemented");
120 }
121 }
122
123 Real integratePDF(const Array& p,
124 const ext::shared_ptr<FdmMesherComposite>& mesher,
126 Real alpha) {
127
128 if (trafoType != FdmSquareRootFwdOp::Power) {
129 return FdmMesherIntegral(
130 mesher, DiscreteSimpsonIntegral()).integrate(p);
131 }
132 else {
133 Array tp(p.size());
134 for (const auto& iter : *mesher->layout()) {
135 const Size idx = iter.index();
136 const Real nu = mesher->location(iter, 1);
137
138 tp[idx] = p[idx]*std::pow(nu, alpha-1);
139 }
140
141 return FdmMesherIntegral(
142 mesher, DiscreteSimpsonIntegral()).integrate(tp);
143 }
144 }
145
146
147 Array rescalePDF(
148 const Array& p,
149 const ext::shared_ptr<FdmMesherComposite>& mesher,
151
152 return p/integratePDF(p, mesher, trafoType, alpha);
153 }
154
155
156 template <class Interpolator>
157 Array reshapePDF(
158 const Array& p,
159 const ext::shared_ptr<FdmMesherComposite>& oldMesher,
160 const ext::shared_ptr<FdmMesherComposite>& newMesher,
161 const Interpolator& interp = Interpolator()) {
162
163 QL_REQUIRE( oldMesher->layout()->size() == newMesher->layout()->size()
164 && oldMesher->layout()->size() == p.size(),
165 "inconsistent mesher or vector size given");
166
167 Matrix m(oldMesher->layout()->dim()[1], oldMesher->layout()->dim()[0]);
168 for (Size i=0; i < m.rows(); ++i) {
169 std::copy(p.begin() + i*m.columns(),
170 p.begin() + (i+1)*m.columns(), m.row_begin(i));
171 }
172 const Interpolation2D interpol = interp.interpolate(
173 oldMesher->getFdm1dMeshers()[0]->locations().begin(),
174 oldMesher->getFdm1dMeshers()[0]->locations().end(),
175 oldMesher->getFdm1dMeshers()[1]->locations().begin(),
176 oldMesher->getFdm1dMeshers()[1]->locations().end(), m);
177
178 Array pNew(p.size());
179 for (const auto& iter : *newMesher->layout()) {
180 const Real x = newMesher->location(iter, 0);
181 const Real v = newMesher->location(iter, 1);
182
183 if ( x > interpol.xMax() || x < interpol.xMin()
184 || v > interpol.yMax() || v < interpol.yMin() ) {
185 pNew[iter.index()] = 0;
186 }
187 else {
188 pNew[iter.index()] = interpol(x, v);
189 }
190 }
191
192 return pNew;
193 }
194
195 class FdmScheme {
196 public:
197 virtual ~FdmScheme() = default;
198 virtual void step(Array& a, Time t) = 0;
199 virtual void setStep(Time dt) = 0;
200 };
201
202 template <class T>
203 class FdmSchemeWrapper : public FdmScheme {
204 public:
205 explicit FdmSchemeWrapper(T* scheme)
206 : scheme_(scheme) { }
207
208 void step(Array& a, Time t) override { scheme_->step(a, t); }
209 void setStep(Time dt) override { scheme_->setStep(dt); }
210
211 private:
212 const std::unique_ptr<T> scheme_;
213 };
214
215 ext::shared_ptr<FdmScheme> fdmSchemeFactory(
216 const FdmSchemeDesc desc,
217 const ext::shared_ptr<FdmLinearOpComposite>& op) {
218
219 switch (desc.type) {
221 return ext::shared_ptr<FdmScheme>(
222 new FdmSchemeWrapper<HundsdorferScheme>(
223 new HundsdorferScheme(desc.theta, desc.mu, op)));
225 return ext::shared_ptr<FdmScheme>(
226 new FdmSchemeWrapper<DouglasScheme>(
227 new DouglasScheme(desc.theta, op)));
229 return ext::shared_ptr<FdmScheme>(
230 new FdmSchemeWrapper<CraigSneydScheme>(
231 new CraigSneydScheme(desc.theta, desc.mu, op)));
233 return ext::shared_ptr<FdmScheme>(
234 new FdmSchemeWrapper<ModifiedCraigSneydScheme>(
235 new ModifiedCraigSneydScheme(
236 desc.theta, desc.mu, op)));
238 return ext::shared_ptr<FdmScheme>(
239 new FdmSchemeWrapper<ImplicitEulerScheme>(
240 new ImplicitEulerScheme(op)));
242 return ext::shared_ptr<FdmScheme>(
243 new FdmSchemeWrapper<ExplicitEulerScheme>(
244 new ExplicitEulerScheme(op)));
245 default:
246 QL_FAIL("Unknown scheme type");
247 }
248 }
249 }
250
252 Handle<HestonModel> hestonModel,
253 const Date& endDate,
255 const bool logging,
256 std::vector<Date> mandatoryDates,
257 const Real mixingFactor)
258 : localVol_(std::move(localVol)), hestonModel_(std::move(hestonModel)), endDate_(endDate),
259 params_(params), mandatoryDates_(std::move(mandatoryDates)),
260 mixingFactor_(mixingFactor), logging_(logging) {
261
264 }
265
266 ext::shared_ptr<HestonProcess> HestonSLVFDMModel::hestonProcess() const {
267 return hestonModel_->process();
268 }
269
270 ext::shared_ptr<LocalVolTermStructure> HestonSLVFDMModel::localVol() const {
271 return localVol_.currentLink();
272 }
273
274 ext::shared_ptr<LocalVolTermStructure>
276 calculate();
277
278 return leverageFunction_;
279 }
280
282 logEntries_.clear();
283
284 const ext::shared_ptr<HestonProcess> hestonProcess
285 = hestonModel_->process();
286 const ext::shared_ptr<Quote> spot
287 = hestonProcess->s0().currentLink();
288 const ext::shared_ptr<YieldTermStructure> rTS
289 = hestonProcess->riskFreeRate().currentLink();
290 const ext::shared_ptr<YieldTermStructure> qTS
291 = hestonProcess->dividendYield().currentLink();
292
293 const Real v0 = hestonProcess->v0();
294 const Real kappa = hestonProcess->kappa();
295 const Real theta = hestonProcess->theta();
296 const Real sigma = hestonProcess->sigma();
297 const Real mixedSigma = mixingFactor_ * sigma;
298 const Real alpha = 2*kappa*theta/(mixedSigma*mixedSigma);
299
300 const Size xGrid = params_.xGrid;
301 const Size vGrid = params_.vGrid;
302
303 const DayCounter dc = rTS->dayCounter();
304 const Date referenceDate = rTS->referenceDate();
305
306 const Time T = dc.yearFraction(referenceDate, endDate_);
307
308 QL_REQUIRE(referenceDate < endDate_,
309 "reference date must be smaller than final calibration date");
310
311 QL_REQUIRE(localVol_->maxTime() >= T,
312 "final calibration maturity exceeds local volatility surface");
313
314 // set-up exponential time step scheme
315 const Time maxDt = 1.0/params_.tMaxStepsPerYear;
316 const Time minDt = 1.0/params_.tMinStepsPerYear;
317
318 Time tIdx=0.0;
319 std::vector<Time> times(1, tIdx);
320 times.reserve(Size(T*params_.tMinStepsPerYear));
321 while (tIdx < T) {
322 const Real decayFactor = std::exp(-params_.tStepNumberDecay*tIdx);
323 const Time dt = maxDt*decayFactor + minDt*(1.0-decayFactor);
324
325 times.push_back(std::min(T, tIdx+=dt));
326 }
327
328 for (auto mandatoryDate : mandatoryDates_) {
329 times.push_back(dc.yearFraction(referenceDate, mandatoryDate));
330 }
331
332 const ext::shared_ptr<TimeGrid> timeGrid(
333 new TimeGrid(times.begin(), times.end()));
334
335 // build 1d meshers
336 const LocalVolRNDCalculator localVolRND(
337 spot, rTS, qTS, localVol_.currentLink(),
338 timeGrid, xGrid,
342
343 const std::vector<Size> rescaleSteps
344 = localVolRND.rescaleTimeSteps();
345
346 const SquareRootProcessRNDCalculator squareRootRnd(
347 v0, kappa, theta, mixedSigma);
348
351
352 std::vector<ext::shared_ptr<Fdm1dMesher> > xMesher, vMesher;
353 xMesher.reserve(timeGrid->size());
354 vMesher.reserve(timeGrid->size());
355
356 xMesher.push_back(localVolRND.mesher(0.0));
357 vMesher.push_back(ext::make_shared<Predefined1dMesher>(
358 std::vector<Real>(vGrid, v0)));
359
360 Size rescaleIdx = 0;
361 for (Size i=1; i < timeGrid->size(); ++i) {
362 xMesher.push_back(localVolRND.mesher(timeGrid->at(i)));
363
364 if ((rescaleIdx < rescaleSteps.size())
365 && (i == rescaleSteps[rescaleIdx])) {
366 ++rescaleIdx;
367 vMesher.push_back(varianceMesher(squareRootRnd,
368 timeGrid->at(rescaleSteps[rescaleIdx-1]),
369 (rescaleIdx < rescaleSteps.size())
370 ? timeGrid->at(rescaleSteps[rescaleIdx])
371 : timeGrid->back(),
372 vGrid, v0, params_));
373 }
374 else
375 vMesher.push_back(vMesher.back());
376 }
377
378 // start probability distribution
379 ext::shared_ptr<FdmMesherComposite> mesher
380 = ext::make_shared<FdmMesherComposite>(
381 xMesher.at(1), vMesher.at(1));
382
383 const Volatility lv0
384 = localVol_->localVol(0.0, spot->value())/std::sqrt(v0);
385
386 ext::shared_ptr<Matrix> L(new Matrix(xGrid, timeGrid->size()));
387
388 const Real l0 = lv0;
389 std::fill(L->column_begin(0),L->column_end(0), l0);
390 std::fill(L->column_begin(1),L->column_end(1), l0);
391
392 // create strikes from meshers
393 std::vector<ext::shared_ptr<std::vector<Real> > > vStrikes(
394 timeGrid->size());
395
396 for (Size i=0; i < timeGrid->size(); ++i) {
397 vStrikes[i] = ext::make_shared<std::vector<Real> >(xGrid);
398 if (xMesher[i]->locations().front()
399 == xMesher[i]->locations().back()) {
400 std::fill(vStrikes[i]->begin(), vStrikes[i]->end(),
401 std::exp(xMesher[i]->locations().front()));
402 }
403 else {
404 std::transform(xMesher[i]->locations().begin(),
405 xMesher[i]->locations().end(),
406 vStrikes[i]->begin(),
407 [](Real x) -> Real { return std::exp(x); });
408 }
409 }
410
411 const ext::shared_ptr<FixedLocalVolSurface> leverageFct(
412 new FixedLocalVolSurface(referenceDate, times, vStrikes, L, dc));
413
414 ext::shared_ptr<FdmLinearOpComposite> hestonFwdOp(
415 new FdmHestonFwdOp(mesher, hestonProcess, trafoType, leverageFct, mixingFactor_));
416
417 Array p = FdmHestonGreensFct(mesher, hestonProcess, trafoType, lv0)
418 .get(timeGrid->at(1), params_.greensAlgorithm);
419
420 if (logging_) {
421 const LogEntry entry = { timeGrid->at(1),
422 ext::make_shared<Array>(p), mesher };
423 logEntries_.push_back(entry);
424 }
425
426 for (Size i=2; i < times.size(); ++i) {
427 const Time t = timeGrid->at(i);
428 const Time dt = t - timeGrid->at(i-1);
429
430 if ( mesher->getFdm1dMeshers()[0] != xMesher[i]
431 || mesher->getFdm1dMeshers()[1] != vMesher[i]) {
432 const ext::shared_ptr<FdmMesherComposite> newMesher(
433 new FdmMesherComposite(xMesher[i], vMesher[i]));
434
435 p = reshapePDF<Bilinear>(p, mesher, newMesher);
436 mesher = newMesher;
437
438 p = rescalePDF(p, mesher, trafoType, alpha);
439
440 hestonFwdOp = ext::shared_ptr<FdmLinearOpComposite>(
441 new FdmHestonFwdOp(mesher, hestonProcess,
442 trafoType, leverageFct, mixingFactor_));
443 }
444
445 Array pn = p;
446 const Array x(Exp(
447 Array(mesher->getFdm1dMeshers()[0]->locations().begin(),
448 mesher->getFdm1dMeshers()[0]->locations().end())));
449 const Array v(
450 mesher->getFdm1dMeshers()[1]->locations().begin(),
451 mesher->getFdm1dMeshers()[1]->locations().end());
452
453 // predictor corrector steps
454 for (Size r=0; r < params_.predictionCorretionSteps; ++r) {
455 const FdmSchemeDesc fdmSchemeDesc
456 = (i < params_.nRannacherTimeSteps + 2)
459
460 const ext::shared_ptr<FdmScheme> fdmScheme(
461 fdmSchemeFactory(fdmSchemeDesc, hestonFwdOp));
462
463 for (Size j=0; j < x.size(); ++j) {
464 Array pSlice(vGrid);
465 for (Size k=0; k < vGrid; ++k)
466 pSlice[k] = pn[j + k*xGrid];
467
468 const Real pInt = (trafoType == FdmSquareRootFwdOp::Power)
469 ? DiscreteSimpsonIntegral()(v, Pow(v, alpha-1)*pSlice)
470 : DiscreteSimpsonIntegral()(v, pSlice);
471
472 const Real vpInt = (trafoType == FdmSquareRootFwdOp::Log)
473 ? DiscreteSimpsonIntegral()(v, Exp(v)*pSlice)
474 : (trafoType == FdmSquareRootFwdOp::Power)
475 ? DiscreteSimpsonIntegral()(v, Pow(v, alpha)*pSlice)
476 : DiscreteSimpsonIntegral()(v, v*pSlice);
477
478 const Real scale = pInt/vpInt;
479 const Volatility localVol = localVol_->localVol(t, x[j]);
480
481 const Real l = (scale >= 0.0)
482 ? localVol*std::sqrt(scale) : Real(1.0);
483
484 (*L)[j][i] = std::min(50.0, std::max(0.001, l));
485
486 leverageFct->setInterpolation(Linear());
487 }
488
489 const Real sLowerBound = std::max(x.front(),
490 std::exp(localVolRND.invcdf(
492 const Real sUpperBound = std::min(x.back(),
493 std::exp(localVolRND.invcdf(
495
496 const Real lowerL = leverageFct->localVol(t, sLowerBound);
497 const Real upperL = leverageFct->localVol(t, sUpperBound);
498
499 for (Size j=0; j < x.size(); ++j) {
500 if (x[j] < sLowerBound)
501 std::fill(L->row_begin(j)+i,
502 std::min(L->row_begin(j)+i+1, L->row_end(j)),
503 lowerL);
504 else if (x[j] > sUpperBound)
505 std::fill(L->row_begin(j)+i,
506 std::min(L->row_begin(j)+i+1, L->row_end(j)),
507 upperL);
508 else if ((*L)[j][i] == Null<Real>())
509 QL_FAIL("internal error");
510 }
511 leverageFct->setInterpolation(Linear());
512
513 pn = p;
514
515 fdmScheme->setStep(dt);
516 fdmScheme->step(pn, t);
517 }
518 p = pn;
519 p = rescalePDF(p, mesher, trafoType, alpha);
520
521 if (logging_) {
522 const LogEntry entry
523 = { t, ext::make_shared<Array>(p), mesher };
524 logEntries_.push_back(entry);
525 }
526 }
527
528 leverageFunction_ = leverageFct;
529 }
530
531 const std::list<HestonSLVFDMModel::LogEntry>& HestonSLVFDMModel::logEntries()
532 const {
534 return logEntries_;
535 }
536}
537
bilinear interpolation between discrete points
1-D array used in linear algebra.
Definition: array.hpp:52
Real back() const
Definition: array.hpp:446
Size size() const
dimension of the array
Definition: array.hpp:483
Real front() const
Definition: array.hpp:439
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:59
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:226
time grid class
Definition: timegrid.hpp:43
One-dimensional grid mesher concentrating around critical points.
Craig-Sneyd operator splitting.
const DefaultType & t
integrals on non uniform grids
Douglas operator splitting.
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
Definition: errors.hpp:117
#define QL_FAIL(message)
throw an error (possibly with file and line information)
Definition: errors.hpp:92
explicit-Euler scheme
Heston Fokker-Planck forward operator.
memory layout of a fdm linear operator
FdmMesher which is a composite of Fdm1dMesher.
mesher based integral over target function.
Local volatility surface based on fixed values plus interpolation.
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
Heston model for the stochastic volatility of an asset.
Real kappa
Real theta
Real v0
Real sigma
const std::unique_ptr< T > scheme_
Heston stochastic local volatility model.
Hundsdorfer operator splitting.
Implicit-Euler scheme.
local volatility risk neutral terminal density calculation
Local volatility term structure base class.
modified Craig-Sneyd operator splitting
Definition: any.hpp:37
Array Pow(const Array &v, Real alpha)
Definition: array.hpp:877
Array Exp(const Array &v)
Definition: array.hpp:863
STL namespace.
normal, cumulative and inverse cumulative distributions
ext::shared_ptr< YieldTermStructure > r
ext::shared_ptr< BlackVolTermStructure > v
One-dimensional mesher build from a given set of points.
Real nu
Definition: sabr.cpp:200
Real alpha
Definition: sabr.cpp:200
simple quote class
risk neutral terminal density calculator for the square root process
static FdmSchemeDesc ImplicitEuler()
FdmSquareRootFwdOp::TransformationType trafoType
FdmHestonGreensFct::Algorithm greensAlgorithm
discrete time grid