Coverage Report

Created: 2025-08-11 06:28

/src/quantlib/ql/processes/hestonprocess.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) 2005, 2007, 2009, 2014 Klaus Spanderen
5
6
 This file is part of QuantLib, a free-software/open-source library
7
 for financial quantitative analysts and developers - http://quantlib.org/
8
9
 QuantLib is free software: you can redistribute it and/or modify it
10
 under the terms of the QuantLib license.  You should have received a
11
 copy of the license along with this program; if not, please email
12
 <quantlib-dev@lists.sf.net>. The license is also available online at
13
 <http://quantlib.org/license.shtml>.
14
15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19
20
#include <ql/math/distributions/chisquaredistribution.hpp>
21
#include <ql/math/distributions/normaldistribution.hpp>
22
#include <ql/math/functional.hpp>
23
#include <ql/math/integrals/gaussianquadratures.hpp>
24
#include <ql/math/integrals/gausslobattointegral.hpp>
25
#include <ql/math/integrals/segmentintegral.hpp>
26
#include <ql/math/modifiedbessel.hpp>
27
#include <ql/math/solvers1d/brent.hpp>
28
#include <ql/processes/eulerdiscretization.hpp>
29
#include <ql/processes/hestonprocess.hpp>
30
#include <ql/quotes/simplequote.hpp>
31
#include <boost/math/distributions/non_central_chi_squared.hpp>
32
#include <complex>
33
#include <utility>
34
35
namespace QuantLib {
36
37
    HestonProcess::HestonProcess(Handle<YieldTermStructure> riskFreeRate,
38
                                 Handle<YieldTermStructure> dividendYield,
39
                                 Handle<Quote> s0,
40
                                 Real v0,
41
                                 Real kappa,
42
                                 Real theta,
43
                                 Real sigma,
44
                                 Real rho,
45
                                 Discretization d)
46
0
    : StochasticProcess(ext::shared_ptr<discretization>(new EulerDiscretization)),
47
0
      riskFreeRate_(std::move(riskFreeRate)), dividendYield_(std::move(dividendYield)),
48
0
      s0_(std::move(s0)), v0_(v0), kappa_(kappa), theta_(theta), sigma_(sigma), rho_(rho),
49
0
      discretization_(d) {
50
51
0
        registerWith(riskFreeRate_);
52
0
        registerWith(dividendYield_);
53
0
        registerWith(s0_);
54
0
    }
55
56
0
    Size HestonProcess::size() const {
57
0
        return 2;
58
0
    }
59
60
0
    Size HestonProcess::factors() const {
61
0
        return (   discretization_ == BroadieKayaExactSchemeLobatto
62
0
                || discretization_ == BroadieKayaExactSchemeTrapezoidal
63
0
                || discretization_ == BroadieKayaExactSchemeLaguerre) ? 3 : 2;
64
0
    }
65
66
0
    Array HestonProcess::initialValues() const {
67
0
        return { s0_->value(), v0_ };
68
0
    }
69
70
0
    Array HestonProcess::drift(Time t, const Array& x) const {
71
0
        const Real vol = (x[1] > 0.0) ? std::sqrt(x[1])
72
0
                         : (discretization_ == Reflection) ? Real(- std::sqrt(-x[1]))
73
0
                         : 0.0;
74
75
0
        return {
76
0
            riskFreeRate_->forwardRate(t, t, Continuous).rate()
77
0
               - dividendYield_->forwardRate(t, t, Continuous).rate()
78
0
               - 0.5 * vol * vol,
79
0
            kappa_* (theta_-((discretization_==PartialTruncation) ? x[1] : vol*vol))
80
0
        };
81
0
    }
82
83
0
    Matrix HestonProcess::diffusion(Time, const Array& x) const {
84
        /* the correlation matrix is
85
           |  1   rho |
86
           | rho   1  |
87
           whose square root (which is used here) is
88
           |  1          0       |
89
           | rho   sqrt(1-rho^2) |
90
        */
91
0
        Matrix tmp(2,2);
92
0
        const Real vol = (x[1] > 0.0) ? std::sqrt(x[1])
93
0
                         : (discretization_ == Reflection) ? Real(-std::sqrt(-x[1]))
94
0
                         : 1e-8; // set vol to (almost) zero but still
95
                                 // expose some correlation information
96
0
        const Real sigma2 = sigma_ * vol;
97
0
        const Real sqrhov = std::sqrt(1.0 - rho_*rho_);
98
99
0
        tmp[0][0] = vol;          tmp[0][1] = 0.0;
100
0
        tmp[1][0] = rho_*sigma2;  tmp[1][1] = sqrhov*sigma2;
101
0
        return tmp;
102
0
    }
103
104
    Array HestonProcess::apply(const Array& x0,
105
0
                               const Array& dx) const {
106
0
        return {
107
0
            x0[0] * std::exp(dx[0]),
108
0
            x0[1] + dx[1]
109
0
        };
110
0
    }
111
112
    namespace {
113
        // This is the continuous version of a characteristic function
114
        // for the exact sampling of the Heston process, s. page 8, formula 13,
115
        // M. Broadie, O. Kaya, Exact Simulation of Stochastic Volatility and
116
        // other Affine Jump Diffusion Processes
117
        // http://finmath.stanford.edu/seminars/documents/Broadie.pdf
118
        //
119
        // This version does not need a branch correction procedure.
120
        // For details please see:
121
        // Roger Lord, "Efficient Pricing Algorithms for exotic Derivatives",
122
        // http://repub.eur.nl/pub/13917/LordR-Thesis.pdf
123
        std::complex<Real> Phi(const HestonProcess& process,
124
                               const std::complex<Real>& a,
125
0
                               Real nu_0, Real nu_t, Time dt) {
126
0
            const Real theta = process.theta();
127
0
            const Real kappa = process.kappa();
128
0
            const Real sigma = process.sigma();
129
130
0
            const Volatility sigma2 = sigma*sigma;
131
0
            const std::complex<Real> ga = std::sqrt(
132
0
                    kappa*kappa - 2*sigma2*a*std::complex<Real>(0.0, 1.0));
133
0
            const Real d = 4*theta*kappa/sigma2;
134
135
0
            const Real nu = 0.5*d-1;
136
0
            const std::complex<Real> z
137
0
                = ga*std::exp(-0.5*ga*dt)/(1.0-std::exp(-ga*dt));
138
0
            const std::complex<Real> log_z
139
0
                = -0.5*ga*dt + std::log(ga/(1.0-std::exp(-ga*dt)));
140
141
0
            const std::complex<Real> alpha
142
0
                = 4.0*ga*std::exp(-0.5*ga*dt)/(sigma2*(1.0-std::exp(-ga*dt)));
143
0
            const std::complex<Real> beta = 4.0*kappa*std::exp(-0.5*kappa*dt)
144
0
                                           /(sigma2*(1.0-std::exp(-kappa*dt)));
145
146
0
            return ga*std::exp(-0.5*(ga-kappa)*dt)*(1-std::exp(-kappa*dt))
147
0
                    / (kappa*(1.0-std::exp(-ga*dt)))
148
0
                   *std::exp((nu_0+nu_t)/sigma2 * (
149
0
                      kappa*(1.0+std::exp(-kappa*dt))/(1.0-std::exp(-kappa*dt))
150
0
                        - ga*(1.0+std::exp(-ga*dt))/(1.0-std::exp(-ga*dt))))
151
0
                   *std::exp(nu*log_z)/std::pow(z, nu)
152
0
                   *((nu_t > 1e-8)
153
0
                           ?   modifiedBesselFunction_i(
154
0
                                   nu, std::sqrt(nu_0*nu_t)*alpha)
155
0
                             / modifiedBesselFunction_i(
156
0
                                   nu, std::sqrt(nu_0*nu_t)*beta)
157
0
                           : std::pow(alpha/beta, nu)
158
0
                     );
159
0
        }
160
161
        Real ch(const HestonProcess& process,
162
0
                Real x, Real u, Real nu_0, Real nu_t, Time dt) {
163
0
            return M_2_PI*std::sin(u*x)/u
164
0
                    * Phi(process, u, nu_0, nu_t, dt).real();
165
0
        }
166
167
        Real ph(const HestonProcess& process,
168
0
                Real x, Real u, Real nu_0, Real nu_t, Time dt) {
169
0
            return M_2_PI*std::cos(u*x)*Phi(process, u, nu_0, nu_t, dt).real();
170
0
        }
171
172
        Real int_ph(const HestonProcess& process,
173
0
                    Real a, Real x, Real y, Real nu_0, Real nu_t, Time t) {
174
0
            static const GaussLaguerreIntegration gaussLaguerreIntegration(128);
175
176
0
            const Real rho   = process.rho();
177
0
            const Real kappa = process.kappa();
178
0
            const Real sigma = process.sigma();
179
0
            const Real x0    = std::log(process.s0()->value());
180
181
0
            return gaussLaguerreIntegration(
182
0
                [&](Real u){ return ph(process, y, u, nu_0, nu_t, t); })
183
0
                / std::sqrt(2*M_PI*(1-rho*rho)*y)
184
0
                * std::exp(-0.5*squared(x - x0 - a + y*(0.5-rho*kappa/sigma))
185
0
                           /(y*(1-rho*rho)));
186
0
        }
187
188
189
0
        Real pade(Real x, const Real* nominator, const Real* denominator, Size m) {
190
0
            Real n=0.0, d=0.0;
191
0
            for (Integer i=m-1; i >= 0; --i) {
192
0
                n = (n+nominator[i])*x;
193
0
                d = (d+denominator[i])*x;
194
0
            }
195
0
            return (1+n)/(1+d);
196
0
        }
197
198
        // For the definition of the Pade approximation please see e.g.
199
        // http://wikipedia.org/wiki/Sine_integral#Sine_integral
200
0
        Real Si(Real x) {
201
0
            if (x <=4.0) {
202
0
                const Real n[] =
203
0
                    { -4.54393409816329991e-2,1.15457225751016682e-3,
204
0
                      -1.41018536821330254e-5,9.43280809438713025e-8,
205
0
                      -3.53201978997168357e-10,7.08240282274875911e-13,
206
0
                      -6.05338212010422477e-16 };
207
0
                const Real d[] =
208
0
                    {  1.01162145739225565e-2,4.99175116169755106e-5,
209
0
                       1.55654986308745614e-7,3.28067571055789734e-10,
210
0
                       4.5049097575386581e-13,3.21107051193712168e-16,
211
0
                       0.0 };
212
213
0
                return x*pade(x*x, n, d, sizeof(n)/sizeof(Real));
214
0
            }
215
0
            else {
216
0
                const Real y = 1/(x*x);
217
0
                const Real fn[] =
218
0
                    { 7.44437068161936700618e2,1.96396372895146869801e5,
219
0
                      2.37750310125431834034e7,1.43073403821274636888e9,
220
0
                      4.33736238870432522765e10,6.40533830574022022911e11,
221
0
                      4.20968180571076940208e12,1.00795182980368574617e13,
222
0
                      4.94816688199951963482e12,-4.94701168645415959931e11 };
223
0
                const Real fd[] =
224
0
                    { 7.46437068161927678031e2,1.97865247031583951450e5,
225
0
                      2.41535670165126845144e7,1.47478952192985464958e9,
226
0
                      4.58595115847765779830e10,7.08501308149515401563e11,
227
0
                      5.06084464593475076774e12,1.43468549171581016479e13,
228
0
                      1.11535493509914254097e13, 0.0 };
229
0
                const Real f = pade(y, fn, fd, 10)/x;
230
231
0
                const Real gn[] =
232
0
                    { 8.1359520115168615e2,2.35239181626478200e5,
233
0
                      3.12557570795778731e7,2.06297595146763354e9,
234
0
                      6.83052205423625007e10,1.09049528450362786e12,
235
0
                      7.57664583257834349e12,1.81004487464664575e13,
236
0
                      6.43291613143049485e12,-1.36517137670871689e12 };
237
0
                const Real gd[] =
238
0
                    { 8.19595201151451564e2,2.40036752835578777e5,
239
0
                      3.26026661647090822e7,2.23355543278099360e9,
240
0
                      7.87465017341829930e10,1.39866710696414565e12,
241
0
                      1.17164723371736605e13,4.01839087307656620e13,
242
0
                      3.99653257887490811e13, 0.0};
243
0
                const Real g = y*pade(y, gn, gd, 10);
244
245
0
                return M_PI_2 - f*std::cos(x)-g*std::sin(x);
246
0
            }
247
0
        }
248
249
        Real cornishFisherEps(const HestonProcess& process,
250
0
                              Real nu_0, Real nu_t, Time dt, Real eps) {
251
            // use moment generating function to get the
252
            // first,second, third and fourth moment of the distribution
253
0
            const Real d = 1e-2;
254
0
            const Real p2 = Phi(process, std::complex<Real>(0, -2*d),
255
0
                                                nu_0, nu_t, dt).real();
256
0
            const Real p1 = Phi(process, std::complex<Real>(0, -d),
257
0
                                                nu_0, nu_t, dt).real();
258
0
            const Real p0 = Phi(process, std::complex<Real>(0, 0),
259
0
                                                nu_0, nu_t, dt).real();
260
0
            const Real pm1= Phi(process, std::complex<Real>(0, d),
261
0
                                                 nu_0, nu_t, dt).real();
262
0
            const Real pm2= Phi(process, std::complex<Real>(0, 2*d),
263
0
                                                 nu_0, nu_t, dt).real();
264
265
0
            const Real avg    = (pm2-8*pm1+8*p1-p2)/(12*d);
266
0
            const Real m2     = (-pm2+16*pm1-30*p0+16*p1-p2)/(12*d*d);
267
0
            const Real var    = m2 - avg*avg;
268
0
            const Real stdDev = std::sqrt(var);
269
270
0
            const Real m3 = (-0.5*pm2 + pm1 - p1 + 0.5*p2)/(d*d*d);
271
0
            const Real skew
272
0
                = (m3 - 3*var*avg - avg*avg*avg) / (var*stdDev);
273
274
0
            const Real m4 = (pm2 - 4*pm1 + 6*p0 - 4*p1 + p2)/(d*d*d*d);
275
0
            const Real kurt
276
0
                 =  (m4 - 4*m3*avg + 6*m2*avg*avg - 3*avg*avg*avg*avg)
277
0
                   /(var*var);
278
279
            // Cornish-Fisher relation to come up with an improved
280
            // estimate of 1-F(u_\eps) < \eps
281
0
            const Real q = InverseCumulativeNormal()(1-eps);
282
0
            const Real w =  q + (q*q-1)/6*skew + (q*q*q-3*q)/24*(kurt-3)
283
0
                          - (2*q*q*q-5*q)/36*skew*skew;
284
285
0
            return avg + w*stdDev;
286
0
        }
287
288
        Real cdf_nu_ds(const HestonProcess& process,
289
                       Real x, Real nu_0, Real nu_t, Time dt,
290
0
                       HestonProcess::Discretization discretization) {
291
0
            const Real eps = 1e-4;
292
0
            const Real u_eps = std::min(100.0,
293
0
                std::max(0.1, cornishFisherEps(process, nu_0, nu_t, dt, eps)));
294
295
0
            switch (discretization) {
296
0
              case HestonProcess::BroadieKayaExactSchemeLaguerre:
297
0
              {
298
0
                  static const GaussLaguerreIntegration
299
0
                      gaussLaguerreIntegration(128);
300
301
                // get the upper bound for the integration
302
0
                Real upper = u_eps/2.0;
303
0
                while (std::abs(Phi(process,upper,nu_0,nu_t,dt)/upper)
304
0
                        > eps) upper*=2.0;
305
306
0
                return (x < upper)
307
0
                    ? std::max(0.0, std::min(1.0,
308
0
                        gaussLaguerreIntegration(
309
0
                            [&](Real u){ return ch(process, x, u, nu_0, nu_t, dt); })))
310
0
                    : Real(1.0);
311
0
              }
312
0
              case HestonProcess::BroadieKayaExactSchemeLobatto:
313
0
              {
314
                // get the upper bound for the integration
315
0
                Real upper = u_eps/2.0;
316
0
                while (std::abs(Phi(process, upper,nu_0,nu_t,dt)/upper)
317
0
                        >  eps) upper*=2.0;
318
319
0
                return (x < upper)
320
0
                    ? std::max(0.0, std::min(1.0,
321
0
                        GaussLobattoIntegral(Null<Size>(), eps)(
322
0
                            [&](Real xi){ return ch(process, x, xi, nu_0, nu_t, dt); },
323
0
                            QL_EPSILON, upper)))
324
0
                    : Real(1.0);
325
0
              }
326
0
              case HestonProcess::BroadieKayaExactSchemeTrapezoidal:
327
0
              {
328
0
                const Real h = 0.05;
329
330
0
                Real si = Si(0.5*h*x);
331
0
                Real s = M_2_PI*si;
332
0
                std::complex<Real> f;
333
0
                Size j = 0;
334
0
                do {
335
0
                    ++j;
336
0
                    const Real u = h*j;
337
0
                    const Real si_n = Si(x*(u+0.5*h));
338
339
0
                    f = Phi(process, u, nu_0, nu_t, dt);
340
0
                    s+= M_2_PI*f.real()*(si_n-si);
341
0
                    si = si_n;
342
0
                }
343
0
                while (M_2_PI*std::abs(f)/j > eps);
344
345
0
                return s;
346
0
              }
347
0
              default:
348
0
                QL_FAIL("unknown integration method");
349
0
            }
350
0
        }
351
    }
352
353
    Real cdf_nu_ds_minus_x(const HestonProcess &process, Real x, Real nu_0,
354
                           Real nu_t, Time dt,
355
                           HestonProcess::Discretization discretization,
356
0
                           Real x0) {
357
0
        return cdf_nu_ds(process, x, nu_0, nu_t, dt, discretization) - x0;
358
0
    }
359
360
0
    Real HestonProcess::pdf(Real x, Real v, Time t, Real eps) const {
361
0
         const Real k = sigma_*sigma_*(1-std::exp(-kappa_*t))/(4*kappa_);
362
0
         const Real a = std::log(  dividendYield_->discount(t)
363
0
                                   / riskFreeRate_->discount(t))
364
0
                      + rho_/sigma_*(v - v0_ - kappa_*theta_*t);
365
366
0
         const Real x0 = std::log(s0()->value());
367
0
         Real upper = std::max(0.1, -(x-x0-a)/(0.5-rho_*kappa_/sigma_)), f=0, df=1;
368
369
0
         while (df > 0.0 || f > 0.1*eps) {
370
0
             const Real f1 = x-x0-a+upper*(0.5-rho_*kappa_/sigma_);
371
0
             const Real f2 = -0.5*f1*f1/(upper*(1-rho_*rho_));
372
373
0
             df = 1/std::sqrt(2*M_PI*(1-rho_*rho_))
374
0
                 * ( -0.5/(upper*std::sqrt(upper))*std::exp(f2)
375
0
                    + 1/std::sqrt(upper)*std::exp(f2)*(-0.5/(1-rho_*rho_))
376
0
                           *(-1/(upper*upper)*f1*f1
377
0
                             + 2/upper*f1*(0.5-rho_*kappa_/sigma_)));
378
379
0
             f = std::exp(f2)/ std::sqrt(2*M_PI*(1-rho_*rho_)*upper);
380
0
             upper*=1.5;
381
0
         }
382
383
0
         upper = 2.0*cornishFisherEps(*this, v0_, v, t,1e-3);
384
385
0
         return SegmentIntegral(100)(
386
0
               [&](Real xi){ return int_ph(*this, a, x, xi, v0_, v, t); },
387
0
               QL_EPSILON, upper)
388
0
               * boost::math::pdf(
389
0
                     boost::math::non_central_chi_squared_distribution<Real>(
390
0
                         4*theta_*kappa_/(sigma_*sigma_),
391
0
                         4*kappa_*std::exp(-kappa_*t)
392
0
                         /((sigma_*sigma_)*(1-std::exp(-kappa_*t)))*v0_),
393
0
                     v/k) / k;
394
0
     }
395
396
    Array HestonProcess::evolve(Time t0, const Array& x0,
397
0
                                Time dt, const Array& dw) const {
398
0
        Array retVal(2);
399
0
        Real vol, vol2, mu, nu, dy;
400
401
0
        const Real sdt = std::sqrt(dt);
402
0
        const Real sqrhov = std::sqrt(1.0 - rho_*rho_);
403
404
0
        switch (discretization_) {
405
          // For the definition of PartialTruncation, FullTruncation
406
          // and Reflection  see Lord, R., R. Koekkoek and D. van Dijk (2006),
407
          // "A Comparison of biased simulation schemes for
408
          //  stochastic volatility models",
409
          // Working Paper, Tinbergen Institute
410
0
          case PartialTruncation:
411
0
            vol = (x0[1] > 0.0) ? std::sqrt(x0[1]) : Real(0.0);
412
0
            vol2 = sigma_ * vol;
413
0
            mu =    riskFreeRate_->forwardRate(t0, t0+dt, Continuous).rate()
414
0
                  - dividendYield_->forwardRate(t0, t0+dt, Continuous).rate()
415
0
                    - 0.5 * vol * vol;
416
0
            nu = kappa_*(theta_ - x0[1]);
417
418
0
            retVal[0] = x0[0] * std::exp(mu*dt+vol*dw[0]*sdt);
419
0
            retVal[1] = x0[1] + nu*dt + vol2*sdt*(rho_*dw[0] + sqrhov*dw[1]);
420
0
            break;
421
0
          case FullTruncation:
422
0
            vol = (x0[1] > 0.0) ? std::sqrt(x0[1]) : Real(0.0);
423
0
            vol2 = sigma_ * vol;
424
0
            mu =    riskFreeRate_->forwardRate(t0, t0+dt, Continuous).rate()
425
0
                  - dividendYield_->forwardRate(t0, t0+dt, Continuous).rate()
426
0
                    - 0.5 * vol * vol;
427
0
            nu = kappa_*(theta_ - vol*vol);
428
429
0
            retVal[0] = x0[0] * std::exp(mu*dt+vol*dw[0]*sdt);
430
0
            retVal[1] = x0[1] + nu*dt + vol2*sdt*(rho_*dw[0] + sqrhov*dw[1]);
431
0
            break;
432
0
          case Reflection:
433
0
            vol = std::sqrt(std::fabs(x0[1]));
434
0
            vol2 = sigma_ * vol;
435
0
            mu =    riskFreeRate_->forwardRate(t0, t0+dt, Continuous).rate()
436
0
                  - dividendYield_->forwardRate(t0, t0+dt, Continuous).rate()
437
0
                    - 0.5 * vol*vol;
438
0
            nu = kappa_*(theta_ - vol*vol);
439
440
0
            retVal[0] = x0[0]*std::exp(mu*dt+vol*dw[0]*sdt);
441
0
            retVal[1] = vol*vol
442
0
                        +nu*dt + vol2*sdt*(rho_*dw[0] + sqrhov*dw[1]);
443
0
            break;
444
0
          case NonCentralChiSquareVariance:
445
            // use Alan Lewis trick to decorrelate the equity and the variance
446
            // process by using y(t)=x(t)-\frac{rho}{sigma}\nu(t)
447
            // and Ito's Lemma. Then use exact sampling for the variance
448
            // process. For further details please read the Wilmott thread
449
            // "QuantLib code is very high quality"
450
0
            vol = (x0[1] > 0.0) ? std::sqrt(x0[1]) : Real(0.0);
451
0
            mu =   riskFreeRate_->forwardRate(t0, t0+dt, Continuous).rate()
452
0
                 - dividendYield_->forwardRate(t0, t0+dt, Continuous).rate()
453
0
                   - 0.5 * vol*vol;
454
455
0
            retVal[1] = varianceDistribution(x0[1], dw[1], dt);
456
0
            dy = (mu - rho_/sigma_*kappa_
457
0
                          *(theta_-vol*vol)) * dt + vol*sqrhov*dw[0]*sdt;
458
459
0
            retVal[0] = x0[0]*std::exp(dy + rho_/sigma_*(retVal[1]-x0[1]));
460
0
            break;
461
0
          case QuadraticExponential:
462
0
          case QuadraticExponentialMartingale:
463
0
          {
464
            // for details of the quadratic exponential discretization scheme
465
            // see Leif Andersen,
466
            // Efficient Simulation of the Heston Stochastic Volatility Model
467
0
            const Real ex = std::exp(-kappa_*dt);
468
469
0
            const Real m  =  theta_+(x0[1]-theta_)*ex;
470
0
            const Real s2 =  x0[1]*sigma_*sigma_*ex/kappa_*(1-ex)
471
0
                           + theta_*sigma_*sigma_/(2*kappa_)*(1-ex)*(1-ex);
472
0
            const Real psi = s2/(m*m);
473
474
0
            const Real g1 =  0.5;
475
0
            const Real g2 =  0.5;
476
0
                  Real k0 = -rho_*kappa_*theta_*dt/sigma_;
477
0
            const Real k1 =  g1*dt*(kappa_*rho_/sigma_-0.5)-rho_/sigma_;
478
0
            const Real k2 =  g2*dt*(kappa_*rho_/sigma_-0.5)+rho_/sigma_;
479
0
            const Real k3 =  g1*dt*(1-rho_*rho_);
480
0
            const Real k4 =  g2*dt*(1-rho_*rho_);
481
0
            const Real A  =  k2+0.5*k4;
482
483
0
            if (psi < 1.5) {
484
0
                const Real b2 = 2/psi-1+std::sqrt(2/psi*(2/psi-1));
485
0
                const Real b  = std::sqrt(b2);
486
0
                const Real a  = m/(1+b2);
487
488
0
                if (discretization_ == QuadraticExponentialMartingale) {
489
                    // martingale correction
490
0
                    QL_REQUIRE(A < 1/(2*a), "illegal value");
491
0
                    k0 = -A*b2*a/(1-2*A*a)+0.5*std::log(1-2*A*a)
492
0
                         -(k1+0.5*k3)*x0[1];
493
0
                }
494
0
                retVal[1] = a*(b+dw[1])*(b+dw[1]);
495
0
            }
496
0
            else {
497
0
                const Real p = (psi-1)/(psi+1);
498
0
                const Real beta = (1-p)/m;
499
500
0
                const Real u = CumulativeNormalDistribution()(dw[1]);
501
502
0
                if (discretization_ == QuadraticExponentialMartingale) {
503
                    // martingale correction
504
0
                    QL_REQUIRE(A < beta, "illegal value");
505
0
                    k0 = -std::log(p+beta*(1-p)/(beta-A))-(k1+0.5*k3)*x0[1];
506
0
                }
507
0
                retVal[1] = ((u <= p) ? Real(0.0) : std::log((1-p)/(1-u))/beta);
508
0
            }
509
510
0
            mu =   riskFreeRate_->forwardRate(t0, t0+dt, Continuous).rate()
511
0
                 - dividendYield_->forwardRate(t0, t0+dt, Continuous).rate();
512
513
0
            retVal[0] = x0[0]*std::exp(mu*dt + k0 + k1*x0[1] + k2*retVal[1]
514
0
                                       +std::sqrt(k3*x0[1]+k4*retVal[1])*dw[0]);
515
0
          }
516
0
          break;
517
0
          case BroadieKayaExactSchemeLobatto:
518
0
          case BroadieKayaExactSchemeLaguerre:
519
0
          case BroadieKayaExactSchemeTrapezoidal:
520
0
          {
521
0
            const Real nu_0 = x0[1];
522
0
            const Real nu_t = varianceDistribution(nu_0, dw[1], dt);
523
524
0
            const Real x = std::min(1.0-QL_EPSILON,
525
0
                std::max(0.0, CumulativeNormalDistribution()(dw[2])));
526
527
0
            const Real vds = Brent().solve(
528
0
                [&](Real xi){ return cdf_nu_ds_minus_x(*this, xi, nu_0, nu_t, dt, discretization_, x); },
529
0
                1e-5, theta_*dt, 0.1*theta_*dt);
530
531
0
            const Real vdw
532
0
                = (nu_t - nu_0 - kappa_*theta_*dt + kappa_*vds)/sigma_;
533
534
0
            mu = ( riskFreeRate_->forwardRate(t0, t0+dt, Continuous).rate()
535
0
                  -dividendYield_->forwardRate(t0, t0+dt, Continuous).rate())*dt
536
0
                - 0.5*vds + rho_*vdw;
537
538
0
            const Volatility sig = std::sqrt((1-rho_*rho_)*vds);
539
0
            const Real s = x0[0]*std::exp(mu + sig*dw[0]);
540
541
0
            retVal[0] = s;
542
0
            retVal[1] = nu_t;
543
0
          }
544
0
          break;
545
0
          default:
546
0
            QL_FAIL("unknown discretization schema");
547
0
        }
548
549
0
        return retVal;
550
0
    }
551
552
0
    const Handle<Quote>& HestonProcess::s0() const {
553
0
        return s0_;
554
0
    }
555
556
0
    const Handle<YieldTermStructure>& HestonProcess::dividendYield() const {
557
0
        return dividendYield_;
558
0
    }
559
560
0
    const Handle<YieldTermStructure>& HestonProcess::riskFreeRate() const {
561
0
        return riskFreeRate_;
562
0
    }
563
564
0
    Time HestonProcess::time(const Date& d) const {
565
0
        return riskFreeRate_->dayCounter().yearFraction(
566
0
                                           riskFreeRate_->referenceDate(), d);
567
0
    }
568
569
0
    Real HestonProcess::varianceDistribution(Real v, Real dw, Time dt) const {
570
0
        const Real df  = 4*theta_*kappa_/(sigma_*sigma_);
571
0
        const Real ncp = 4*kappa_*std::exp(-kappa_*dt)
572
0
            /(sigma_*sigma_*(1-std::exp(-kappa_*dt)))*v;
573
574
0
        const Real p = std::min(1.0-QL_EPSILON,
575
0
            std::max(0.0, CumulativeNormalDistribution()(dw)));
576
577
0
        return sigma_*sigma_*(1-std::exp(-kappa_*dt))/(4*kappa_)
578
0
            *InverseNonCentralCumulativeChiSquareDistribution(df, ncp, 100)(p);
579
0
    }
580
}