Loading [MathJax]/extensions/tex2jax.js
QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.38
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
analytichestonengine.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) 2004, 2005, 2008 Klaus Spanderen
5 Copyright (C) 2007 StatPro Italia srl
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/*! \file hestonmodel.hpp
22 \brief analytic pricing engine for a heston option
23 based on fourier transformation
24*/
25
26#include <ql/functional.hpp>
36#include <ql/math/expm1.hpp>
40
41#include <boost/math/tools/minima.hpp>
42#include <boost/math/special_functions/sign.hpp>
43
44#include <cmath>
45#include <limits>
46#include <utility>
47
48#if defined(QL_PATCH_MSVC)
49#pragma warning(disable: 4180)
50#endif
51
52namespace QuantLib {
53
54 namespace {
55
56 class integrand1 {
57 private:
58 const Real c_inf_;
59 const std::function<Real(Real)> f_;
60 public:
61 integrand1(Real c_inf, std::function<Real(Real)> f) : c_inf_(c_inf), f_(std::move(f)) {}
62 Real operator()(Real x) const {
63 if ((1.0-x)*c_inf_ > QL_EPSILON)
64 return f_(-std::log(0.5-0.5*x)/c_inf_)/((1.0-x)*c_inf_);
65 else
66 return 0.0;
67 }
68 };
69
70 class integrand2 {
71 private:
72 const Real c_inf_;
73 const std::function<Real(Real)> f_;
74 public:
75 integrand2(Real c_inf, std::function<Real(Real)> f) : c_inf_(c_inf), f_(std::move(f)) {}
76 Real operator()(Real x) const {
77 if (x*c_inf_ > QL_EPSILON) {
78 return f_(-std::log(x)/c_inf_)/(x*c_inf_);
79 } else {
80 return 0.0;
81 }
82 }
83 };
84
85 class integrand3 {
86 private:
87 const integrand2 int_;
88 public:
89 integrand3(Real c_inf, const std::function<Real(Real)>& f)
90 : int_(c_inf, f) {}
91
92 Real operator()(Real x) const { return int_(1.0-x); }
93 };
94
95 class u_Max {
96 public:
97 u_Max(Real c_inf, Real epsilon) : c_inf_(c_inf), logEpsilon_(std::log(epsilon)) {}
98
99 Real operator()(Real u) const {
100 ++evaluations_;
101 return c_inf_*u + std::log(u) + logEpsilon_;
102 }
103
104 Size evaluations() const { return evaluations_; }
105
106 private:
108 mutable Size evaluations_ = 0;
109 };
110
111
112 class uHat_Max {
113 public:
114 uHat_Max(Real v0T2, Real epsilon) : v0T2_(v0T2), logEpsilon_(std::log(epsilon)) {}
115
116 Real operator()(Real u) const {
117 ++evaluations_;
118 return v0T2_*u*u + std::log(u) + logEpsilon_;
119 }
120
121 Size evaluations() const { return evaluations_; }
122
123 private:
125 mutable Size evaluations_ = 0;
126 };
127 }
128
129 // helper class for integration
130 class AnalyticHestonEngine::Fj_Helper {
131 public:
132 Fj_Helper(Real kappa, Real theta, Real sigma, Real v0,
133 Real s0, Real rho,
134 const AnalyticHestonEngine* engine,
135 ComplexLogFormula cpxLog,
136 Time term, Real strike, Real ratio, Size j);
137
138 Real operator()(Real phi) const;
139
140 private:
141 const Size j_;
142 // const VanillaOption::arguments& arg_;
143 const Real kappa_, theta_, sigma_, v0_;
144 const ComplexLogFormula cpxLog_;
145
146 // helper variables
147 const Time term_;
148 const Real x_, sx_, dd_;
149 const Real sigma2_, rsigma_;
150 const Real t0_;
151
152 // log branch counter
153 mutable int b_ = 0; // log branch counter
154 mutable Real g_km1_ = 0; // imag part of last log value
155
156 const AnalyticHestonEngine* const engine_;
157 };
158
159 AnalyticHestonEngine::Fj_Helper::Fj_Helper(Real kappa, Real theta,
160 Real sigma, Real v0, Real s0, Real rho,
161 const AnalyticHestonEngine* const engine,
162 ComplexLogFormula cpxLog,
163 Time term, Real strike, Real ratio, Size j)
164 :
165 j_(j),
166 kappa_(kappa),
167 theta_(theta),
168 sigma_(sigma),
169 v0_(v0),
170 cpxLog_(cpxLog),
171 term_(term),
172 x_(std::log(s0)),
173 sx_(std::log(strike)),
174 dd_(x_-std::log(ratio)),
175 sigma2_(sigma_*sigma_),
176 rsigma_(rho*sigma_),
177 t0_(kappa - ((j== 1)? rho*sigma : Real(0))),
178
179 engine_(engine)
180 {
181 }
182
183 Real AnalyticHestonEngine::Fj_Helper::operator()(Real phi) const
184 {
185 const Real rpsig(rsigma_*phi);
186
187 const std::complex<Real> t1 = t0_+std::complex<Real>(0, -rpsig);
188 const std::complex<Real> d =
189 std::sqrt(t1*t1 - sigma2_*phi
190 *std::complex<Real>(-phi, (j_== 1)? 1 : -1));
191 const std::complex<Real> ex = std::exp(-d*term_);
192 const std::complex<Real> addOnTerm =
193 engine_ != nullptr ? engine_->addOnTerm(phi, term_, j_) : Real(0.0);
194
195 if (cpxLog_ == Gatheral) {
196 if (phi != 0.0) {
197 if (sigma_ > 1e-5) {
198 const std::complex<Real> p = (t1-d)/(t1+d);
199 const std::complex<Real> g
200 = std::log((1.0 - p*ex)/(1.0 - p));
201
202 return
203 std::exp(v0_*(t1-d)*(1.0-ex)/(sigma2_*(1.0-ex*p))
204 + (kappa_*theta_)/sigma2_*((t1-d)*term_-2.0*g)
205 + std::complex<Real>(0.0, phi*(dd_-sx_))
206 + addOnTerm
207 ).imag()/phi;
208 }
209 else {
210 const std::complex<Real> td = phi/(2.0*t1)
211 *std::complex<Real>(-phi, (j_== 1)? 1 : -1);
212 const std::complex<Real> p = td*sigma2_/(t1+d);
213 const std::complex<Real> g = p*(1.0-ex);
214
215 return
216 std::exp(v0_*td*(1.0-ex)/(1.0-p*ex)
217 + (kappa_*theta_)*(td*term_-2.0*g/sigma2_)
218 + std::complex<Real>(0.0, phi*(dd_-sx_))
219 + addOnTerm
220 ).imag()/phi;
221 }
222 }
223 else {
224 // use l'Hospital's rule to get lim_{phi->0}
225 if (j_ == 1) {
226 const Real kmr = rsigma_-kappa_;
227 if (std::fabs(kmr) > 1e-7) {
228 return dd_-sx_
229 + (std::exp(kmr*term_)*kappa_*theta_
230 -kappa_*theta_*(kmr*term_+1.0) ) / (2*kmr*kmr)
231 - v0_*(1.0-std::exp(kmr*term_)) / (2.0*kmr);
232 }
233 else
234 // \kappa = \rho * \sigma
235 return dd_-sx_ + 0.25*kappa_*theta_*term_*term_
236 + 0.5*v0_*term_;
237 }
238 else {
239 return dd_-sx_
240 - (std::exp(-kappa_*term_)*kappa_*theta_
241 +kappa_*theta_*(kappa_*term_-1.0))/(2*kappa_*kappa_)
242 - v0_*(1.0-std::exp(-kappa_*term_))/(2*kappa_);
243 }
244 }
245 }
246 else if (cpxLog_ == BranchCorrection) {
247 const std::complex<Real> p = (t1+d)/(t1-d);
248
249 // next term: g = std::log((1.0 - p*std::exp(d*term_))/(1.0 - p))
250 std::complex<Real> g;
251
252 // the exp of the following expression is needed.
253 const std::complex<Real> e = std::log(p)+d*term_;
254
255 // does it fit to the machine precision?
256 if (std::exp(-e.real()) > QL_EPSILON) {
257 g = std::log((1.0 - p/ex)/(1.0 - p));
258 } else {
259 // use a "big phi" approximation
260 g = d*term_ + std::log(p/(p - 1.0));
261
262 if (g.imag() > M_PI || g.imag() <= -M_PI) {
263 // get back to principal branch of the complex logarithm
264 Real im = std::fmod(g.imag(), 2*M_PI);
265 if (im > M_PI)
266 im -= 2*M_PI;
267 else if (im <= -M_PI)
268 im += 2*M_PI;
269
270 g = std::complex<Real>(g.real(), im);
271 }
272 }
273
274 // be careful here as we have to use a log branch correction
275 // to deal with the discontinuities of the complex logarithm.
276 // the principal branch is not always the correct one.
277 // (s. A. Sepp, chapter 4)
278 // remark: there is still the change that we miss a branch
279 // if the order of the integration is not high enough.
280 const Real tmp = g.imag() - g_km1_;
281 if (tmp <= -M_PI)
282 ++b_;
283 else if (tmp > M_PI)
284 --b_;
285
286 g_km1_ = g.imag();
287 g += std::complex<Real>(0, 2*b_*M_PI);
288
289 return std::exp(v0_*(t1+d)*(ex-1.0)/(sigma2_*(ex-p))
290 + (kappa_*theta_)/sigma2_*((t1+d)*term_-2.0*g)
291 + std::complex<Real>(0,phi*(dd_-sx_))
292 + addOnTerm
293 ).imag()/phi;
294 }
295 else {
296 QL_FAIL("unknown complex logarithm formula");
297 }
298 }
299
300 AnalyticHestonEngine::OptimalAlpha::OptimalAlpha(
301 const Time t,
302 const AnalyticHestonEngine* const enginePtr)
303 : t_(t),
304 fwd_(enginePtr->model_->process()->s0()->value()
305 * enginePtr->model_->process()->dividendYield()->discount(t)
306 / enginePtr->model_->process()->riskFreeRate()->discount(t)),
307 kappa_(enginePtr->model_->kappa()),
308 theta_(enginePtr->model_->theta()),
309 sigma_(enginePtr->model_->sigma()),
310 rho_(enginePtr->model_->rho()),
311 eps_(std::pow(2, -int(0.5*std::numeric_limits<Real>::digits))),
312 enginePtr_(enginePtr)
313 {
314 km_ = k(0.0, -1);
315 kp_ = k(0.0, 1);
316 }
317
319 const Real eps = 1e-8;
320 const auto cm = [this](Real k) -> Real { return M(k) - t_; };
321
322 Real alpha_max;
323 const Real adx = kappa_ - sigma_*rho_;
324 if (adx > 0.0) {
325 const Real kp_2pi = k(2*M_PI, 1);
326
327 alpha_max = Brent().solve(
328 cm, eps_, 0.5*(kp_ + kp_2pi), (1+eps)*kp_, (1-eps)*kp_2pi
329 ) - 1.0;
330 }
331 else if (adx < 0.0) {
332 const Time tCut = -2/(kappa_ - sigma_*rho_*kp_);
333 if (t_ < tCut) {
334 const Real kp_pi = k(M_PI, 1);
335 alpha_max = Brent().solve(
336 cm, eps_, 0.5*(kp_ + kp_pi), (1+eps)*kp_, (1-eps)*kp_pi
337 ) - 1.0;
338 }
339 else {
340 alpha_max = Brent().solve(
341 cm, eps_, 0.5*(1.0 + kp_),
342 1 + eps, (1-eps)*kp_
343 ) - 1.0;
344 }
345 }
346 else { // adx == 0.0
347 const Real kp_pi = k(M_PI, 1);
348 alpha_max = Brent().solve(
349 cm, eps_, 0.5*(kp_ + kp_pi), (1+eps)*kp_, (1-eps)*kp_pi
350 ) - 1.0;
351 }
352
353 QL_REQUIRE(alpha_max >= 0.0, "alpha max must be larger than zero");
354
355 return alpha_max;
356 }
357
358 std::pair<Real, Real>
360 const Real alpha_max = alphaMax(strike);
361
362 return findMinima(eps_, std::max(2*eps_, (1.0-1e-6)*alpha_max), strike);
363 }
364
366 const auto cm = [this](Real k) -> Real { return M(k) - t_; };
367
368 const Real km_2pi = k(2*M_PI, -1);
369
370 const Real alpha_min = Brent().solve(
371 cm, eps_, 0.5*(km_2pi + km_), (1-1e-8)*km_2pi, (1+1e-8)*km_
372 ) - 1.0;
373
374 QL_REQUIRE(alpha_min <= -1.0, "alpha min must be smaller than minus one");
375
376 return alpha_min;
377 }
378
379 std::pair<Real, Real>
381 const Real alpha_min = alphaMin(strike);
382
383 return findMinima(
384 std::min(-1.0-1e-6, -1.0 + (1.0-1e-6)*(alpha_min + 1.0)),
385 -1.0 - eps_, strike
386 );
387 }
388
390 try {
391 const std::pair<Real, Real> minusOne = alphaSmallerMinusOne(strike);
392 const std::pair<Real, Real> greaterZero = alphaGreaterZero(strike);
393
394 if (minusOne.second < greaterZero.second) {
395 return minusOne.first;
396 }
397 else {
398 return greaterZero.first;
399 }
400 }
401 catch (const Error&) {
402 return -0.5;
403 }
404
405 }
406
408 return evaluations_;
409 }
410
412 Real lower, Real upper, Real strike) const {
413 const Real freq = std::log(fwd_/strike);
414
415 return boost::math::tools::brent_find_minima(
416 [freq, this](Real alpha) -> Real {
417 ++evaluations_;
418 const std::complex<Real> z(0, -(alpha+1));
419 return enginePtr_->lnChF(z, t_).real()
420 - std::log(alpha*(alpha+1)) + alpha*freq;
421 },
422 lower, upper, int(0.5*std::numeric_limits<Real>::digits)
423 );
424 }
425
427 const Real beta = kappa_ - sigma_*rho_*k;
428
429 if (k >= km_ && k <= kp_) {
430 const Real D = std::sqrt(beta*beta - sigma_*sigma_*k*(k-1));
431 return std::log((beta-D)/(beta+D)) / D;
432 }
433 else {
434 const Real D_imag =
435 std::sqrt(-(beta*beta - sigma_*sigma_*k*(k-1)));
436
437 return 2/D_imag
438 * ( ((beta>0.0)? M_PI : 0.0) - std::atan(D_imag/beta) );
439 }
440 }
441
443 return ( (sigma_ - 2*rho_*kappa_)
444 + sgn*std::sqrt(
445 squared(sigma_-2*rho_*kappa_)
446 + 4*(kappa_*kappa_ + x*x/(t_*t_))*(1-rho_*rho_)))
447 /(2*sigma_*(1-rho_*rho_));
448 }
449
451 Time term, Real fwd, Real strike, ComplexLogFormula cpxLog,
452 const AnalyticHestonEngine* const enginePtr,
453 const Real alpha)
454 : term_(term),
455 fwd_(fwd),
456 strike_(strike),
457 freq_(std::log(fwd/strike)),
458 cpxLog_(cpxLog),
459 enginePtr_(enginePtr),
460 alpha_(alpha),
461 s_alpha_(std::exp(alpha*freq_))
462 {
463 QL_REQUIRE(enginePtr != nullptr, "pricing engine required");
464
465 const Real v0 = enginePtr->model_->v0();
466 const Real kappa = enginePtr->model_->kappa();
467 const Real theta = enginePtr->model_->theta();
468 const Real sigma = enginePtr->model_->sigma();
469 const Real rho = enginePtr->model_->rho();
470
471 switch(cpxLog_) {
473 vAvg_ = (1-std::exp(-kappa*term))*(v0 - theta)
474 /(kappa*term) + theta;
475 break;
477 vAvg_ = -8.0*std::log(enginePtr->chF(
478 std::complex<Real>(0, alpha_), term).real())/term;
479 break;
480 case AsymptoticChF:
481 phi_ = -(v0+term*kappa*theta)/sigma
482 * std::complex<Real>(std::sqrt(1-rho*rho), rho);
483
484 psi_ = std::complex<Real>(
485 (kappa- 0.5*rho*sigma)*(v0 + term*kappa*theta)
486 + kappa*theta*std::log(4*(1-rho*rho)),
487 - ((0.5*rho*rho*sigma - kappa*rho)/std::sqrt(1-rho*rho)
488 *(v0 + kappa*theta*term)
489 - 2*kappa*theta*std::atan(rho/std::sqrt(1-rho*rho))))
490 /(sigma*sigma);
491 [[fallthrough]];
492 case AngledContour:
493 vAvg_ = (1-std::exp(-kappa*term))*(v0 - theta)
494 /(kappa*term) + theta;
495 [[fallthrough]];
497 {
498 const Real r = rho - sigma*freq_ / (v0 + kappa*theta*term);
499 tanPhi_ = std::tan(
500 (r*freq_ < 0.0)? M_PI/12*boost::math::sign(freq_) : 0.0
501 );
502 }
503 break;
504 default:
505 QL_FAIL("unknown control variate");
506 }
507 }
508
510 QL_REQUIRE( enginePtr_->addOnTerm(u, term_, 1)
511 == std::complex<Real>(0.0)
512 && enginePtr_->addOnTerm(u, term_, 2)
513 == std::complex<Real>(0.0),
514 "only Heston model is supported");
515
516 constexpr std::complex<double> i(0, 1);
517
519 const std::complex<Real> h_u(u, u*tanPhi_ - alpha_);
520 const std::complex<Real> hPrime(h_u-i);
521
522 std::complex<Real> phiBS(0.0);
523 if (cpxLog_ == AngledContour)
524 phiBS = std::exp(
525 -0.5*vAvg_*term_*(hPrime*hPrime +
526 std::complex<Real>(-hPrime.imag(), hPrime.real())));
527 else if (cpxLog_ == AsymptoticChF)
528 phiBS = std::exp(u*std::complex<Real>(1, tanPhi_)*phi_ + psi_);
529
530 return std::exp(-u*tanPhi_*freq_)
531 *(std::exp(std::complex<Real>(0.0, u*freq_))
532 *std::complex<Real>(1, tanPhi_)
533 *(phiBS - enginePtr_->chF(hPrime, term_))/(h_u*hPrime)
534 ).real()*s_alpha_;
535 }
537 const std::complex<Real> z(u, -alpha_);
538 const std::complex<Real> zPrime(u, -alpha_-1);
539 const std::complex<Real> phiBS = std::exp(
540 -0.5*vAvg_*term_*(zPrime*zPrime +
541 std::complex<Real>(-zPrime.imag(), zPrime.real()))
542 );
543
544 return (std::exp(std::complex<Real> (0.0, u*freq_))
545 * (phiBS - enginePtr_->chF(zPrime, term_)) / (z*zPrime)
546 ).real()*s_alpha_;
547 }
548 else
549 QL_FAIL("unknown control variate");
550 }
551
553 if ( cpxLog_ == AngledContour
555 return BlackCalculator(
556 Option::Call, strike_, fwd_, std::sqrt(vAvg_*term_))
557 .value();
558 }
559 else if (cpxLog_ == AsymptoticChF) {
560 QL_REQUIRE(alpha_ == -0.5, "alpha must be equal to -0.5");
561
562 const std::complex<Real> phiFreq(phi_.real(), phi_.imag() + freq_);
563
564 using namespace ExponentialIntegral;
565 return fwd_ - std::sqrt(strike_*fwd_)/M_PI*
566 (std::exp(psi_)*(
567 -2.0*Ci(-0.5*phiFreq)*std::sin(0.5*phiFreq)
568 +std::cos(0.5*phiFreq)*(M_PI+2.0*Si(0.5*phiFreq)))).real();
569 }
570 else if (cpxLog_ == AngledContourNoCV) {
571 return ((alpha_ <= 0.0)? fwd_ : 0.0)
572 - ((alpha_ <= -1.0)? strike_ : 0.0)
573 -0.5*((alpha_ == 0.0)? fwd_ :0.0)
574 +0.5*((alpha_ == -1.0)? strike_: 0.0);
575 }
576 else
577 QL_FAIL("unknown control variate");
578 }
579
580 std::complex<Real> AnalyticHestonEngine::chF(
581 const std::complex<Real>& z, Time t) const {
582 if (model_->sigma() > 1e-6 || model_->kappa() < 1e-8) {
583 return std::exp(lnChF(z, t));
584 }
585 else {
586 const Real kappa = model_->kappa();
587 const Real sigma = model_->sigma();
588 const Real theta = model_->theta();
589 const Real rho = model_->rho();
590 const Real v0 = model_->v0();
591
592 const Real sigma2 = sigma*sigma;
593
594 const Real kt = kappa*t;
595 const Real ekt = std::exp(kt);
596 const Real e2kt = std::exp(2*kt);
597 const Real rho2 = rho*rho;
598 const std::complex<Real> zpi = z + std::complex<Real>(0.0, 1.0);
599
600 return std::exp(-(((theta - v0 + ekt*((-1 + kt)*theta + v0))
601 *z*zpi)/ekt)/(2.*kappa))
602
603 + (std::exp(-(kt) - ((theta - v0 + ekt
604 *((-1 + kt)*theta + v0))*z*zpi)
605 /(2.*ekt*kappa))*rho*(2*theta + kt*theta -
606 v0 - kt*v0 + ekt*((-2 + kt)*theta + v0))
607 *(1.0 - std::complex<Real>(-z.imag(),z.real()))*z*z)
608 /(2.*kappa*kappa)*sigma
609
610 + (std::exp(-2*kt - ((theta - v0 + ekt
611 *((-1 + kt)*theta + v0))*z*zpi)/(2.*ekt*kappa))*z*z*zpi
612 *(-2*rho2*squared(2*theta + kt*theta - v0 -
613 kt*v0 + ekt*((-2 + kt)*theta + v0))
614 *z*z*zpi + 2*kappa*v0*(-zpi
615 + e2kt*(zpi + 4*rho2*z) - 2*ekt*(2*rho2*z
616 + kt*(zpi + rho2*(2 + kt)*z))) + kappa*theta*(zpi + e2kt
617 *(-5.0*zpi - 24*rho2*z+ 2*kt*(zpi + 4*rho2*z)) +
618 4*ekt*(zpi + 6*rho2*z + kt*(zpi + rho2*(4 + kt)*z)))))
619 /(16.*squared(squared(kappa)))*sigma2;
620 }
621 }
622
623 std::complex<Real> AnalyticHestonEngine::lnChF(
624 const std::complex<Real>& z, Time t) const {
625
626 const Real kappa = model_->kappa();
627 const Real sigma = model_->sigma();
628 const Real theta = model_->theta();
629 const Real rho = model_->rho();
630 const Real v0 = model_->v0();
631
632 const Real sigma2 = sigma*sigma;
633
634 const std::complex<Real> g
635 = kappa + rho*sigma*std::complex<Real>(z.imag(), -z.real());
636
637 const std::complex<Real> D = std::sqrt(
638 g*g + (z*z + std::complex<Real>(-z.imag(), z.real()))*sigma2);
639
640 // reduce cancelation errors, see. L. Andersen and M. Lake
641 std::complex<Real> r(g-D);
642 if (g.real()*D.real() + g.imag()*D.imag() > 0.0) {
643 r = -sigma2*z*std::complex<Real>(z.real(), z.imag()+1)/(g+D);
644 }
645
646 std::complex<Real> y;
647 if (D.real() != 0.0 || D.imag() != 0.0) {
648 y = expm1(-D*t)/(2.0*D);
649 }
650 else
651 y = -0.5*t;
652
653 const std::complex<Real> A
654 = kappa*theta/sigma2*(r*t - 2.0*log1p(-r*y));
655 const std::complex<Real> B
656 = z*std::complex<Real>(z.real(), z.imag()+1)*y/(1.0-r*y);
657
658 return A+v0*B;
659 }
660
662 const ext::shared_ptr<HestonModel>& model,
663 Size integrationOrder)
666 VanillaOption::results>(model),
667 evaluations_(0),
670 Integration::gaussLaguerre(integrationOrder))),
672 alpha_(-0.5) {
673 }
674
676 const ext::shared_ptr<HestonModel>& model,
677 Real relTolerance, Size maxEvaluations)
680 VanillaOption::results>(model),
681 evaluations_(0),
682 cpxLog_(OptimalCV),
683 integration_(new Integration(Integration::gaussLobatto(
684 relTolerance, Null<Real>(), maxEvaluations))),
685 andersenPiterbargEpsilon_(1e-40),
686 alpha_(-0.5) {
687 }
688
690 const ext::shared_ptr<HestonModel>& model,
691 ComplexLogFormula cpxLog,
692 const Integration& integration,
693 Real andersenPiterbargEpsilon,
694 Real alpha)
697 VanillaOption::results>(model),
698 evaluations_(0),
699 cpxLog_(cpxLog),
700 integration_(new Integration(integration)),
701 andersenPiterbargEpsilon_(andersenPiterbargEpsilon),
702 alpha_(alpha) {
704 || !integration.isAdaptiveIntegration(),
705 "Branch correction does not work in conjunction "
706 "with adaptive integration methods");
707 }
708
712
713 if (t > 0.15 && (v0+t*kappa*theta)/sigma*std::sqrt(1-rho*rho) < 0.15
714 && ((kappa- 0.5*rho*sigma)*(v0 + t*kappa*theta)
715 + kappa*theta*std::log(4*(1-rho*rho)))/(sigma*sigma) < 0.1) {
716 return AsymptoticChF;
717 }
718 else {
719 return AngledContour;
720 }
721 }
722
724 return evaluations_;
725 }
726
728 const ext::shared_ptr<PlainVanillaPayoff>& payoff,
729 const Date& maturity) const {
730
731 const ext::shared_ptr<HestonProcess>& process = model_->process();
732 const Real fwd = process->s0()->value()
733 * process->dividendYield()->discount(maturity)
734 / process->riskFreeRate()->discount(maturity);
735
736 return priceVanillaPayoff(payoff, process->time(maturity), fwd);
737 }
738
740 const ext::shared_ptr<PlainVanillaPayoff>& payoff, Time maturity) const {
741
742 const ext::shared_ptr<HestonProcess>& process = model_->process();
743 const Real fwd = process->s0()->value()
744 * process->dividendYield()->discount(maturity)
745 / process->riskFreeRate()->discount(maturity);
746
747 return priceVanillaPayoff(payoff, maturity, fwd);
748 }
749
751 const ext::shared_ptr<PlainVanillaPayoff>& payoff,
752 Time maturity, Real fwd) const {
753
754 Real value;
755
756 const ext::shared_ptr<HestonProcess>& process = model_->process();
757 const DiscountFactor dr = process->riskFreeRate()->discount(maturity);
758
759 const Real strike = payoff->strike();
760 const Real spot = process->s0()->value();
761 QL_REQUIRE(spot > 0.0, "negative or null underlying given");
762
763 const DiscountFactor df = spot/fwd;
764 const DiscountFactor dd = dr/df;
765
766 const Real kappa = model_->kappa();
767 const Real sigma = model_->sigma();
768 const Real theta = model_->theta();
769 const Real rho = model_->rho();
770 const Real v0 = model_->v0();
771
772 evaluations_ = 0;
773
774 switch(cpxLog_) {
775 case Gatheral:
776 case BranchCorrection: {
777 const Real c_inf = std::min(0.2, std::max(0.0001,
778 std::sqrt(1.0-rho*rho)/sigma))*(v0 + kappa*theta*maturity);
779
780 const Real p1 = integration_->calculate(c_inf,
781 Fj_Helper(kappa, theta, sigma, v0, spot, rho, this,
782 cpxLog_, maturity, strike, df, 1))/M_PI;
783 evaluations_ += integration_->numberOfEvaluations();
784
785 const Real p2 = integration_->calculate(c_inf,
786 Fj_Helper(kappa, theta, sigma, v0, spot, rho, this,
787 cpxLog_, maturity, strike, df, 2))/M_PI;
788 evaluations_ += integration_->numberOfEvaluations();
789
790 switch (payoff->optionType())
791 {
792 case Option::Call:
793 value = spot*dd*(p1+0.5)
794 - strike*dr*(p2+0.5);
795 break;
796 case Option::Put:
797 value = spot*dd*(p1-0.5)
798 - strike*dr*(p2-0.5);
799 break;
800 default:
801 QL_FAIL("unknown option type");
802 }
803 }
804 break;
807 case AsymptoticChF:
808 case AngledContour:
810 case OptimalCV: {
811 const Real c_inf =
812 std::sqrt(1.0-rho*rho)*(v0 + kappa*theta*maturity)/sigma;
813
814 const Real epsilon = andersenPiterbargEpsilon_
815 *M_PI/(std::sqrt(strike*fwd)*dr);
816
817 const std::function<Real()> uM = [&](){
819 c_inf, epsilon, v0, maturity);
820 };
821
822 const ComplexLogFormula finalLog = (cpxLog_ == OptimalCV)
824 : cpxLog_;
825
826 const AP_Helper cvHelper(
827 maturity, fwd, strike, finalLog, this, alpha_
828 );
829
830 const Real cvValue = cvHelper.controlVariateValue();
831
832 const Real vAvg = (1-std::exp(-kappa*maturity))*(v0-theta)/(kappa*maturity) + theta;
833
834 const Real scalingFactor = (cpxLog_ != OptimalCV && cpxLog_ != AsymptoticChF)
835 ? std::max(0.25, std::min(1000.0, 0.25/std::sqrt(0.5*vAvg*maturity)))
836 : Real(1.0);
837
838 const Real h_cv =
839 fwd/M_PI*integration_->calculate(c_inf, cvHelper, uM, scalingFactor);
840
841 evaluations_ += integration_->numberOfEvaluations();
842
843 switch (payoff->optionType())
844 {
845 case Option::Call:
846 value = (cvValue + h_cv)*dr;
847 break;
848 case Option::Put:
849 value = (cvValue + h_cv - (fwd - strike))*dr;
850 break;
851 default:
852 QL_FAIL("unknown option type");
853 }
854 }
855 break;
856 default:
857 QL_FAIL("unknown complex log formula");
858 }
859
860 return value;
861 }
862
864 {
865 // this is a european option pricer
866 QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
867 "not an European option");
868
869 // plain vanilla
870 ext::shared_ptr<PlainVanillaPayoff> payoff =
871 ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
872 QL_REQUIRE(payoff, "non plain vanilla payoff given");
873
874 const Date exerciseDate = arguments_.exercise->lastDate();
875
876 results_.value = priceVanillaPayoff(payoff, exerciseDate);
877 }
878
879
881 ext::shared_ptr<Integrator> integrator)
882 : intAlgo_(intAlgo), integrator_(std::move(integrator)) {}
883
885 Algorithm intAlgo, ext::shared_ptr<GaussianQuadrature> gaussianQuadrature)
886 : intAlgo_(intAlgo), gaussianQuadrature_(std::move(gaussianQuadrature)) {}
887
890 Real relTolerance, Real absTolerance, Size maxEvaluations, bool useConvergenceEstimate) {
891 return Integration(GaussLobatto,
892 ext::shared_ptr<Integrator>(
893 new GaussLobattoIntegral(maxEvaluations,
894 absTolerance,
895 relTolerance,
896 useConvergenceEstimate)));
897 }
898
901 Size maxEvaluations) {
902 return Integration(GaussKronrod,
903 ext::shared_ptr<Integrator>(
904 new GaussKronrodAdaptive(absTolerance,
905 maxEvaluations)));
906 }
907
910 Size maxEvaluations) {
911 return Integration(Simpson,
912 ext::shared_ptr<Integrator>(
913 new SimpsonIntegral(absTolerance,
914 maxEvaluations)));
915 }
916
919 Size maxEvaluations) {
920 return Integration(Trapezoid,
921 ext::shared_ptr<Integrator>(
922 new TrapezoidIntegral<Default>(absTolerance,
923 maxEvaluations)));
924 }
925
928 QL_REQUIRE(intOrder <= 192, "maximum integraton order (192) exceeded");
929 return Integration(GaussLaguerre,
930 ext::shared_ptr<GaussianQuadrature>(
931 new GaussLaguerreIntegration(intOrder)));
932 }
933
936 return Integration(GaussLegendre,
937 ext::shared_ptr<GaussianQuadrature>(
938 new GaussLegendreIntegration(intOrder)));
939 }
940
943 return Integration(GaussChebyshev,
944 ext::shared_ptr<GaussianQuadrature>(
945 new GaussChebyshevIntegration(intOrder)));
946 }
947
950 return Integration(GaussChebyshev2nd,
951 ext::shared_ptr<GaussianQuadrature>(
952 new GaussChebyshev2ndIntegration(intOrder)));
953 }
954
957 return Integration(
958 DiscreteSimpson, ext::shared_ptr<Integrator>(
959 new DiscreteSimpsonIntegrator(evaluations)));
960 }
961
964 return Integration(
965 DiscreteTrapezoid, ext::shared_ptr<Integrator>(
966 new DiscreteTrapezoidIntegrator(evaluations)));
967 }
968
971 return Integration(
972 ExpSinh, ext::shared_ptr<Integrator>(
973 new ExpSinhIntegral(relTolerance)));
974 }
975
977 if (integrator_ != nullptr) {
978 return integrator_->numberOfEvaluations();
979 } else if (gaussianQuadrature_ != nullptr) {
980 return gaussianQuadrature_->order();
981 } else {
982 QL_FAIL("neither Integrator nor GaussianQuadrature given");
983 }
984 }
985
987 return intAlgo_ == GaussLobatto
988 || intAlgo_ == GaussKronrod
989 || intAlgo_ == Simpson
990 || intAlgo_ == Trapezoid
991 || intAlgo_ == ExpSinh;
992 }
993
995 Real c_inf,
996 const std::function<Real(Real)>& f,
997 const std::function<Real()>& maxBound,
998 const Real scaling) const {
999
1000 Real retVal;
1001
1002 switch(intAlgo_) {
1003 case GaussLaguerre:
1004 retVal = (*gaussianQuadrature_)(f);
1005 break;
1006 case GaussLegendre:
1007 case GaussChebyshev:
1008 case GaussChebyshev2nd:
1009 retVal = (*gaussianQuadrature_)(integrand1(c_inf, f));
1010 break;
1011 case ExpSinh:
1012 retVal = scaling*(*integrator_)(
1013 [scaling, f](Real x) -> Real { return f(scaling*x);},
1014 0.0, std::numeric_limits<Real>::max());
1015 break;
1016 case Simpson:
1017 case Trapezoid:
1018 case GaussLobatto:
1019 case GaussKronrod:
1020 if (maxBound && maxBound() != Null<Real>())
1021 retVal = (*integrator_)(f, 0.0, maxBound());
1022 else
1023 retVal = (*integrator_)(integrand2(c_inf, f), 0.0, 1.0);
1024 break;
1025 case DiscreteTrapezoid:
1026 case DiscreteSimpson:
1027 if (maxBound && maxBound() != Null<Real>())
1028 retVal = (*integrator_)(f, 0.0, maxBound());
1029 else
1030 retVal = (*integrator_)(integrand3(c_inf, f), 0.0, 1.0);
1031 break;
1032 default:
1033 QL_FAIL("unknwon integration algorithm");
1034 }
1035
1036 return retVal;
1037 }
1038
1040 Real c_inf,
1041 const std::function<Real(Real)>& f,
1042 Real maxBound) const {
1043
1045 c_inf, f, [=](){ return maxBound; });
1046 }
1047
1049 Real c_inf, Real epsilon, Real v0, Real t) {
1050
1051 const Real uMaxGuess = -std::log(epsilon)/c_inf;
1052 const Real uMaxStep = 0.1*uMaxGuess;
1053
1054 const Real uMax = Brent().solve(u_Max(c_inf, epsilon),
1055 QL_EPSILON*uMaxGuess, uMaxGuess, uMaxStep);
1056
1057 try {
1058 const Real uHatMaxGuess = std::sqrt(-std::log(epsilon)/(0.5*v0*t));
1059 const Real uHatMax = Brent().solve(uHat_Max(0.5*v0*t, epsilon),
1060 QL_EPSILON*uHatMaxGuess, uHatMaxGuess, 0.001*uHatMaxGuess);
1061
1062 return std::max(uMax, uHatMax);
1063 }
1064 catch (const Error&) {
1065 return uMax;
1066 }
1067 }
1068
1069
1071 Real dividendDiscount,
1072 Real spotPrice,
1073 Real strikePrice,
1074 Real term,
1076 const TypePayoff& type,
1077 const Integration& integration,
1078 const ComplexLogFormula cpxLog,
1079 const AnalyticHestonEngine* const enginePtr,
1080 Real& value,
1081 Size& evaluations) {
1082
1083 const Real ratio = riskFreeDiscount/dividendDiscount;
1084
1085 evaluations = 0;
1086
1087 switch(cpxLog) {
1088 case Gatheral:
1089 case BranchCorrection: {
1090 const Real c_inf = std::min(0.2, std::max(0.0001,
1091 std::sqrt(1.0-rho*rho)/sigma))*(v0 + kappa*theta*term);
1092
1093 const Real p1 = integration.calculate(c_inf,
1094 Fj_Helper(kappa, theta, sigma, v0, spotPrice, rho, enginePtr,
1095 cpxLog, term, strikePrice, ratio, 1))/M_PI;
1096 evaluations += integration.numberOfEvaluations();
1097
1098 const Real p2 = integration.calculate(c_inf,
1099 Fj_Helper(kappa, theta, sigma, v0, spotPrice, rho, enginePtr,
1100 cpxLog, term, strikePrice, ratio, 2))/M_PI;
1101 evaluations += integration.numberOfEvaluations();
1102
1103 switch (type.optionType())
1104 {
1105 case Option::Call:
1106 value = spotPrice*dividendDiscount*(p1+0.5)
1107 - strikePrice*riskFreeDiscount*(p2+0.5);
1108 break;
1109 case Option::Put:
1110 value = spotPrice*dividendDiscount*(p1-0.5)
1111 - strikePrice*riskFreeDiscount*(p2-0.5);
1112 break;
1113 default:
1114 QL_FAIL("unknown option type");
1115 }
1116 }
1117 break;
1118 case AndersenPiterbarg:
1120 case AsymptoticChF:
1121 case OptimalCV: {
1122 const Real c_inf =
1123 std::sqrt(1.0-rho*rho)*(v0 + kappa*theta*term)/sigma;
1124
1125 const Real fwdPrice = spotPrice / ratio;
1126
1127 const Real epsilon = enginePtr->andersenPiterbargEpsilon_
1128 *M_PI/(std::sqrt(strikePrice*fwdPrice)*riskFreeDiscount);
1129
1130 const std::function<Real()> uM = [&](){
1131 return Integration::andersenPiterbargIntegrationLimit(c_inf, epsilon, v0, term);
1132 };
1133
1134 AP_Helper cvHelper(term, fwdPrice, strikePrice,
1135 (cpxLog == OptimalCV)
1137 : cpxLog,
1138 enginePtr
1139 );
1140
1141 const Real cvValue = cvHelper.controlVariateValue();
1142
1143 const Real h_cv = integration.calculate(c_inf, cvHelper, uM)
1144 * std::sqrt(strikePrice * fwdPrice)/M_PI;
1145 evaluations += integration.numberOfEvaluations();
1146
1147 switch (type.optionType())
1148 {
1149 case Option::Call:
1150 value = (cvValue + h_cv)*riskFreeDiscount;
1151 break;
1152 case Option::Put:
1153 value = (cvValue + h_cv - (fwdPrice - strikePrice))*riskFreeDiscount;
1154 break;
1155 default:
1156 QL_FAIL("unknown option type");
1157 }
1158 }
1159 break;
1160
1161 default:
1162 QL_FAIL("unknown complex log formula");
1163 }
1164 }
1165}
const Real kappa_
const integrand2 int_
Size evaluations_
const Real v0T2_
const Real logEpsilon_
analytic Heston-model engine
Black-formula calculator class.
Brent 1-D solver.
ext::shared_ptr< PricingEngine > engine_
Definition: cdsoption.cpp:60
AP_Helper(Time term, Real fwd, Real strike, ComplexLogFormula cpxLog, const AnalyticHestonEngine *enginePtr, Real alpha=-0.5)
static Real andersenPiterbargIntegrationLimit(Real c_inf, Real epsilon, Real v0, Real t)
Integration(Algorithm intAlgo, ext::shared_ptr< GaussianQuadrature > quadrature)
static Integration discreteSimpson(Size evaluation=1000)
static Integration gaussChebyshev2nd(Size integrationOrder=128)
static Integration simpson(Real absTolerance, Size maxEvaluations=1000)
static Integration gaussLegendre(Size integrationOrder=128)
static Integration gaussLobatto(Real relTolerance, Real absTolerance, Size maxEvaluations=1000, bool useConvergenceEstimate=false)
static Integration gaussChebyshev(Size integrationOrder=128)
static Integration expSinh(Real relTolerance=1e-8)
Real calculate(Real c_inf, const std::function< Real(Real)> &f, const std::function< Real()> &maxBound={}, Real scaling=1.0) const
static Integration trapezoid(Real absTolerance, Size maxEvaluations=1000)
static Integration gaussLaguerre(Size integrationOrder=128)
static Integration gaussKronrod(Real absTolerance, Size maxEvaluations=1000)
static Integration discreteTrapezoid(Size evaluation=1000)
std::pair< Real, Real > alphaGreaterZero(Real strike) const
std::pair< Real, Real > alphaSmallerMinusOne(Real strike) const
std::pair< Real, Real > findMinima(Real lower, Real upper, Real strike) const
analytic Heston-model engine based on Fourier transform
std::complex< Real > lnChF(const std::complex< Real > &z, Time t) const
static void doCalculation(Real riskFreeDiscount, Real dividendDiscount, Real spotPrice, Real strikePrice, Real term, Real kappa, Real theta, Real sigma, Real v0, Real rho, const TypePayoff &type, const Integration &integration, ComplexLogFormula cpxLog, const AnalyticHestonEngine *enginePtr, Real &value, Size &evaluations)
Real priceVanillaPayoff(const ext::shared_ptr< PlainVanillaPayoff > &payoff, const Date &maturity) const
AnalyticHestonEngine(const ext::shared_ptr< HestonModel > &model, Real relTolerance, Size maxEvaluations)
const ext::shared_ptr< Integration > integration_
std::complex< Real > chF(const std::complex< Real > &z, Time t) const
static ComplexLogFormula optimalControlVariate(Time t, Real v0, Real kappa, Real theta, Real sigma, Real rho)
Black 1976 calculator class.
Brent 1-D solver
Definition: brent.hpp:37
Concrete date class.
Definition: date.hpp:125
Base error class.
Definition: errors.hpp:39
Gauss-Chebyshev integration (second kind)
Integral of a 1-dimensional function using the Gauss-Kronrod methods.
generalized Gauss-Laguerre integration
Integral of a one-dimensional function.
Base class for some pricing engine on a particular model.
Heston model for the stochastic volatility of an asset.
Definition: hestonmodel.hpp:42
template class providing a null value for a given type.
Definition: null.hpp:59
Integral of a one-dimensional function.
Real solve(const F &f, Real accuracy, Real guess, Real step) const
Definition: solver1d.hpp:84
Integral of a one-dimensional function.
Intermediate class for put/call payoffs.
Definition: payoffs.hpp:49
Option::Type optionType() const
Definition: payoffs.hpp:51
Vanilla option (no discrete dividends, no barriers) on a single asset.
std::function< Real(Real)> f_
const DefaultType & t
integrals on non uniform grids
#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
Date d
complex versions of expm1 and logp1
Maps function, bind and cref to either the boost or std implementation.
integral of a one-dimensional function using the adaptive Gauss-Lobatto integral
#define QL_EPSILON
Definition: qldefines.hpp:178
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
QL_REAL Real
real number
Definition: types.hpp:50
Real DiscountFactor
discount factor between dates
Definition: types.hpp:66
QL_INTEGER Integer
integer number
Definition: types.hpp:35
std::size_t Size
size of a container
Definition: types.hpp:58
Real kappa
Real theta
const Real c_inf_
Real v0
Real rho
Real sigma
ext::shared_ptr< QuantLib::Payoff > payoff
Integral of a 1-dimensional function using the Gauss-Kronrod method.
const VF_R b_
functionals and combinators not included in the STL
#define M_PI
Definition: any.hpp:37
std::complex< Real > log1p(const std::complex< Real > &z)
Definition: expm1.cpp:42
T squared(T x)
Definition: functional.hpp:37
std::complex< Real > expm1(const std::complex< Real > &z)
Definition: expm1.cpp:26
STL namespace.
Payoffs for various options.
ext::shared_ptr< YieldTermStructure > r
Real beta
Definition: sabr.cpp:200
Real alpha
Definition: sabr.cpp:200
integral of a one-dimensional function using Simpson formula
integral of a one-dimensional function using the trapezoid formula
std::uint64_t x_