Coverage Report

Created: 2026-02-03 07:02

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