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