QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
analyticptdhestonengine.cpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2010, 2017 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
24#include <ql/math/functional.hpp>
25#include <ql/instruments/payoffs.hpp>
26#include <ql/pricingengines/vanilla/analyticptdhestonengine.hpp>
27#include <ql/pricingengines/blackcalculator.hpp>
28
29namespace QuantLib {
30
31 // helper class for integration
32 class AnalyticPTDHestonEngine::Fj_Helper {
33 public:
34 Fj_Helper(
35 const Handle<PiecewiseTimeDependentHestonModel>& model,
36 Time term, Real strike, Size j);
37
38 Real operator()(Real phi) const;
39
40 private:
41 const Size j_;
42 const Time term_;
43 const Real v0_, x_, sx_;
44
45 std::vector<Rate> r_, q_;
46 const ext::shared_ptr<YieldTermStructure> qTS_;
47 const Handle<PiecewiseTimeDependentHestonModel> model_;
48
49 const TimeGrid timeGrid_;
50 };
51
52 AnalyticPTDHestonEngine::Fj_Helper::Fj_Helper(
53 const Handle<PiecewiseTimeDependentHestonModel>& model,
54 Time term, Real strike, Size j)
55 : j_(j),
56 term_(term),
57
58 v0_(model->v0()),
59 x_ (std::log(model->s0())),
60 sx_(std::log(strike)),
61 r_(model->timeGrid().size()-1),
62 q_(model->timeGrid().size()-1),
63 model_(model),
64 timeGrid_(model->timeGrid()){
65
66 for (Size i=0; i <timeGrid_.size()-1; ++i) {
67 const Time begin = std::min(term_, timeGrid_[i]);
68 const Time end = std::min(term_, timeGrid_[i+1]);
69 r_[i] = model->riskFreeRate()
70 ->forwardRate(begin, end, Continuous, NoFrequency).rate();
71 q_[i] = model->dividendYield()
72 ->forwardRate(begin, end, Continuous, NoFrequency).rate();
73 }
74 }
75
76 Real AnalyticPTDHestonEngine::Fj_Helper::operator()(Real phi) const {
77
78 // avoid numeric overflow for phi->0.
79 // todo: use l'Hospital's rule use to get lim_{phi->0}
80 phi = std::max(Real(std::numeric_limits<float>::epsilon()), phi);
81
82 std::complex<Real> D = 0.0;
83 std::complex<Real> C = 0.0;
84
85 for (Size i=timeGrid_.size()-1; i > 0; --i) {
86 const Time begin = timeGrid_[i-1];
87 if (begin < term_) {
88 const Time end = std::min(term_, timeGrid_[i]);
89 const Time tau = end-begin;
90 const Time t = 0.5*(end+begin);
91
92 const Real rho = model_->rho(t);
93 const Real sigma = model_->sigma(t);
94 const Real kappa = model_->kappa(t);
95 const Real theta = model_->theta(t);
96
97 const Real sigma2 = sigma*sigma;
98 const Real t0 = kappa - ((j_== 1)? Real(rho*sigma) : 0);
99 const Real rpsig = rho*sigma*phi;
100
101 const std::complex<Real> t1 = t0+std::complex<Real>(0, -rpsig);
102 const std::complex<Real> d = std::sqrt(t1*t1 - sigma2*phi
103 *std::complex<Real>(-phi, (j_== 1)? 1 : -1));
104 const std::complex<Real> g = (t1-d)/(t1+d);
105 const std::complex<Real> gt
106 = (t1-d - D*sigma2)/(t1+d - D*sigma2);
107
108 D = (t1+d)/sigma2*(g-gt*std::exp(-d*tau))
109 /(1.0-gt*std::exp(-d*tau));
110
111 const std::complex<Real> lng
112 = std::log((1.0 - gt*std::exp(-d*tau))/(1.0 - gt));
113
114 C =(kappa*theta)/sigma2*((t1-d)*tau-2.0*lng)
115 + std::complex<Real>(0.0, phi*(r_[i-1]-q_[i-1])*tau) + C;
116 }
117 }
118 return std::exp(v0_*D+C+std::complex<Real>(0.0, phi*(x_ - sx_))).imag()
119 /phi;
120 }
121
122 class AnalyticPTDHestonEngine::AP_Helper {
123 public:
124 AP_Helper(Time term, Real s0, Real strike, Real ratio,
125 Volatility sigmaBS,
126 const AnalyticPTDHestonEngine* const enginePtr)
127 : term_(term),
128 sigmaBS_(sigmaBS),
129 x_(std::log(s0)),
130 sx_(std::log(strike)),
131 dd_(x_-std::log(ratio)),
132 enginePtr_(enginePtr) {
133 QL_REQUIRE(enginePtr != nullptr, "pricing engine required");
134 }
135
136 Real operator()(Real u) const {
137 const std::complex<Real> z(u, -0.5);
138
139 const std::complex<Real> phiBS
140 = std::exp(-0.5*sigmaBS_*sigmaBS_*term_
141 *(z*z + std::complex<Real>(-z.imag(), z.real())));
142
143 return (std::exp(std::complex<Real>(0.0, u*(dd_-sx_)))
144 * (phiBS - enginePtr_->chF(z, term_)) / (u*u + 0.25)).real();
145 }
146
147 private:
148 const Time term_;
149 const Volatility sigmaBS_;
150 const Real x_, sx_, dd_;
151 const AnalyticPTDHestonEngine* const enginePtr_;
152 };
153
154
155 std::complex<Real> AnalyticPTDHestonEngine::lnChF(
156 const std::complex<Real>& z, Time T) const {
157
158 const Real v0 = model_->v0();
159
160 std::complex<Real> D = 0.0;
161 std::complex<Real> C = 0.0;
162
163 const TimeGrid& timeGrid = model_->timeGrid();
164 const Time lastModelTime = timeGrid.back();
165
166 QL_REQUIRE(T <= lastModelTime,
167 "maturity (" << T << ") is too large, "
168 "time grid is bounded by " << lastModelTime);
169
170 const Size lastI = std::distance(timeGrid.begin(),
171 std::lower_bound(timeGrid.begin(), timeGrid.end(), T));
172
173 for (Integer i=lastI-1; i >= 0; --i) {
174 const Time begin = timeGrid[i];
175 const Time end = std::min(T, timeGrid[i+1]);
176 const Time tau = end - begin;
177
178 const Time t = 0.5*(end+begin);
179 const Real kappa = model_->kappa(t);
180 const Real sigma = model_->sigma(t);
181 const Real theta = model_->theta(t);
182 const Real rho = model_->rho(t);
183
184 const Real sigma2 = sigma*sigma;
185
186 const std::complex<Real> k
187 = kappa + rho*sigma*std::complex<Real>(z.imag(), -z.real());
188
189 const std::complex<Real> d = std::sqrt(
190 k*k + (z*z + std::complex<Real>(-z.imag(), z.real()))*sigma2);
191
192 const std::complex<Real> g = (k-d)/(k+d);
193
194 const std::complex<Real> gt = (k-d-D*sigma2)/(k+d-D*sigma2);
195
196 C += kappa*theta/sigma2*( (k-d)*tau
197 - 2.0*std::log((1.0-gt*std::exp(-d*tau))/(1.0-gt)));
198
199 D = (k+d)/sigma2 * (g - gt*std::exp(-d*tau))
200 /(1.0 - gt*std::exp(-d*tau));
201 }
202
203 return D*v0 + C;
204 }
205
206 std::complex<Real> AnalyticPTDHestonEngine::chF(
207 const std::complex<Real>& z, Time T) const {
208 return std::exp(lnChF(z, T));
209 }
210
211 AnalyticPTDHestonEngine::AnalyticPTDHestonEngine(
212 const ext::shared_ptr<PiecewiseTimeDependentHestonModel>& model,
213 Size integrationOrder)
216 VanillaOption::results>(model),
217 evaluations_(0),
218 cpxLog_(Gatheral),
219 integration_(new Integration(
220 Integration::gaussLaguerre(integrationOrder))),
221 andersenPiterbargEpsilon_(Null<Real>()) {
222 }
223
225 const ext::shared_ptr<PiecewiseTimeDependentHestonModel>& model,
226 Real relTolerance, Size maxEvaluations)
229 VanillaOption::results>(model),
230 evaluations_(0),
231 cpxLog_(Gatheral),
232 integration_(new Integration(Integration::gaussLobatto(
233 relTolerance, Null<Real>(), maxEvaluations))),
234 andersenPiterbargEpsilon_(Null<Real>()) {
235 }
236
238 const ext::shared_ptr<PiecewiseTimeDependentHestonModel>& model,
239 ComplexLogFormula cpxLog,
240 const Integration& itg,
241 Real andersenPiterbargEpsilon)
244 VanillaOption::results>(model),
245 evaluations_(0),
246 cpxLog_(cpxLog),
247 integration_(new Integration(itg)),
248 andersenPiterbargEpsilon_(andersenPiterbargEpsilon) {
249 }
250
251
253 // this is an european option pricer
254 QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
255 "not an European option");
256
257 // plain vanilla
258 ext::shared_ptr<PlainVanillaPayoff> payoff =
259 ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
260 QL_REQUIRE(payoff, "non-striked payoff given");
261
262 const Real v0 = model_->v0();
263 const Real spotPrice = model_->s0();
264 QL_REQUIRE(spotPrice > 0.0, "negative or null underlying given");
265
266 const Real strike = payoff->strike();
267 const Real term
268 = model_->riskFreeRate()->dayCounter().yearFraction(
269 model_->riskFreeRate()->referenceDate(),
270 arguments_.exercise->lastDate());
271
272 QL_REQUIRE(term < model_->timeGrid().back() ||
273 close_enough(term, model_->timeGrid().back()),
274 "maturity (" << term << ") is too large, time grid is bounded by "
275 << model_->timeGrid().back());
276
277 const Real riskFreeDiscount = model_->riskFreeRate()->discount(
278 arguments_.exercise->lastDate());
279 const Real dividendDiscount = model_->dividendYield()->discount(
280 arguments_.exercise->lastDate());
281
282 //average values
283 const TimeGrid& timeGrid = model_->timeGrid();
284 QL_REQUIRE(timeGrid.size() > 1, "at least two model points needed");
285
286 const Size n = timeGrid.size()-1;
287 Real kappaAvg = 0.0, thetaAvg = 0.0, sigmaAvg=0.0, rhoAvg = 0.0;
288
289 for (Size i=1; i <= n; ++i) {
290 const Time t = 0.5*(timeGrid[i-1] + timeGrid[i]);
291 kappaAvg += model_->kappa(t);
292 thetaAvg += model_->theta(t);
293 sigmaAvg += model_->sigma(t);
294 rhoAvg += model_->rho(t);
295 }
296 kappaAvg/=n; thetaAvg/=n; sigmaAvg/=n; rhoAvg/=n;
297
298 evaluations_ = 0;
299
300 switch(cpxLog_) {
301 case Gatheral: {
302 const Real c_inf = std::min(0.2, std::max(0.0001,
303 std::sqrt(1.0-squared(rhoAvg))/sigmaAvg))
304 *(v0 + kappaAvg*thetaAvg*term);
305
306 const Real p1 = integration_->calculate(c_inf,
307 Fj_Helper(model_, term, strike, 1))/M_PI;
308 evaluations_ += integration_->numberOfEvaluations();
309
310 const Real p2 = integration_->calculate(c_inf,
311 Fj_Helper(model_, term, strike, 2))/M_PI;
312 evaluations_ += integration_->numberOfEvaluations();
313
314 switch (payoff->optionType())
315 {
316 case Option::Call:
317 results_.value = spotPrice*dividendDiscount*(p1+0.5)
318 - strike*riskFreeDiscount*(p2+0.5);
319 break;
320 case Option::Put:
321 results_.value = spotPrice*dividendDiscount*(p1-0.5)
322 - strike*riskFreeDiscount*(p2-0.5);
323 break;
324 default:
325 QL_FAIL("unknown option type");
326 }
327 }
328 break;
329 case AndersenPiterbarg: {
330 QL_REQUIRE(term <= timeGrid.back(),
331 "maturity (" << term << ") is too large, "
332 "time grid is bounded by " << timeGrid.back());
333
334 const Time t05 = 0.5*timeGrid.at(1);
335
336 const std::complex<Real> D_u_inf =
337 -std::complex<Real>(
338 std::sqrt(1-squared(model_->rho(t05))),
339 model_->rho(t05)) / model_->sigma(t05);
340
341 const Size lastI = std::distance(timeGrid.begin(),
342 std::lower_bound(timeGrid.begin(), timeGrid.end(), term));
343
344 std::complex<Real> C_u_inf(0.0, 0.0);
345 for (Size i=0; i < lastI; ++i) {
346 const Time begin = timeGrid[i];
347 const Time end = std::min(term, timeGrid[i+1]);
348 const Time tau = end - begin;
349 const Time t = 0.5*(end+begin);
350
351 const Real kappa = model_->kappa(t);
352 const Real theta = model_->theta(t);
353 const Real sigma = model_->sigma(t);
354 const Real rho = model_->rho(t);
355
356 C_u_inf += -kappa*theta*tau / sigma
357 *std::complex<Real>(std::sqrt(1-rho*rho), rho);
358 }
359
360 const Real ratio = riskFreeDiscount/dividendDiscount;
361
362 const Real fwdPrice = spotPrice / ratio;
363
364 const Real epsilon = andersenPiterbargEpsilon_
365 *M_PI/(std::sqrt(strike*fwdPrice)*riskFreeDiscount);
366
367 const Real c_inf = -(C_u_inf + D_u_inf*v0).real();
368
369 const ext::function<Real()> uM = [=](){
370 return Integration::andersenPiterbargIntegrationLimit(c_inf, epsilon, v0, term);
371 };
372
373 const Real vAvg
374 = (1-std::exp(-kappaAvg*term))*(v0-thetaAvg)
375 /(kappaAvg*term) + thetaAvg;
376
377 const Real bsPrice
379 fwdPrice, std::sqrt(vAvg*term),
380 riskFreeDiscount).value();
381
382 const Real h_cv = integration_->calculate(c_inf,
383 AP_Helper(term, spotPrice, strike,
384 ratio, std::sqrt(vAvg), this),uM)
385 * std::sqrt(strike * fwdPrice)*riskFreeDiscount/M_PI;
386 evaluations_ += integration_->numberOfEvaluations();
387
388 switch (payoff->optionType())
389 {
390 case Option::Call:
391 results_.value = bsPrice + h_cv;
392 break;
393 case Option::Put:
394 results_.value = bsPrice + h_cv
395 - riskFreeDiscount*(fwdPrice - strike);
396 break;
397 default:
398 QL_FAIL("unknown option type");
399 }
400 }
401 break;
402
403 default:
404 QL_FAIL("unknown complex log formula");
405 }
406 }
407
409 return evaluations_;
410 }
411}
static Real andersenPiterbargIntegrationLimit(Real c_inf, Real epsilon, Real v0, Real t)
AnalyticPTDHestonEngine(const ext::shared_ptr< PiecewiseTimeDependentHestonModel > &model, Real relTolerance, Size maxEvaluations)
const ext::shared_ptr< Integration > integration_
Black 1976 calculator class.
Base class for some pricing engine on a particular model.
template class providing a null value for a given type.
Definition: null.hpp:76
time grid class
Definition: timegrid.hpp:43
Time back() const
Definition: timegrid.hpp:171
const_iterator begin() const
Definition: timegrid.hpp:166
Time at(Size i) const
Definition: timegrid.hpp:163
Size size() const
Definition: timegrid.hpp:164
const_iterator end() const
Definition: timegrid.hpp:167
Vanilla option (no discrete dividends, no barriers) on a single asset.
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
QL_REAL Real
real number
Definition: types.hpp:50
Real Volatility
volatility
Definition: types.hpp:78
QL_INTEGER Integer
integer number
Definition: types.hpp:35
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
T squared(T x)
Definition: functional.hpp:37
bool close_enough(const Quantity &m1, const Quantity &m2, Size n)
Definition: quantity.cpp:182
STL namespace.