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