/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 | | } |