Coverage Report

Created: 2025-08-28 06:30

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