QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.38
Loading...
Searching...
No Matches
qdplusamericanengine.cpp
Go to the documentation of this file.
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2022 Klaus Spanderen
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20/*! \file qrplusamericanengine.cpp
21*/
22
23#include <algorithm>
24#include <ql/exercise.hpp>
25#include <ql/utilities/null.hpp>
35#ifndef QL_BOOST_HAS_TANH_SINH
37#endif
38
39namespace QuantLib {
40
41 class QdPlusBoundaryEvaluator {
42 public:
43 QdPlusBoundaryEvaluator(
44 Real S, Real strike, Rate rf, Rate dy, Volatility vol, Time t, Time T)
45 : tau(t), K(strike), sigma(vol), sigma2(sigma * sigma), v(sigma * std::sqrt(tau)), r(rf),
46 q(dy), dr(std::exp(-r * tau)), dq(std::exp(-q * tau)),
47 ddr((std::abs(r*tau) > 1e-5)? Real(r/(1-dr)) : Real(1/(tau*(1-0.5*r*tau*(1-r*tau/3))))),
48 omega(2 * (r - q) / sigma2),
49 lambda(0.5 *
50 (-(omega - 1) - std::sqrt(squared(omega - 1) + 8 * ddr / sigma2))),
51 lambdaPrime(2 * ddr*ddr /
52 (sigma2 * std::sqrt(squared(omega - 1) + 8 * ddr / sigma2))),
53 alpha(2 * dr / (sigma2 * (2 * lambda + omega - 1))),
54 beta(alpha * (ddr + lambdaPrime / (2 * lambda + omega - 1)) - lambda),
55 xMax(QdPlusAmericanEngine::xMax(strike, r, q)),
56 xMin(QL_EPSILON * 1e4 * std::min(0.5 * (strike + S), xMax)),
57
58 sc(Null<Real>()) {}
59
60 Real operator()(Real S) const {
61 ++nrEvaluations;
62
63 if (S != sc)
64 preCalculate(S);
65
66 if (close_enough(K-S, npv)) {
67 return (1-dq*Phi_dp)*S + alpha*theta/dr;
68 }
69 else {
70 const Real c0 = -beta - lambda + alpha*theta/(dr*(K-S-npv));
71 return (1-dq*Phi_dp)*S + (lambda+c0)*(K-S-npv);
72 }
73 }
74 Real derivative(Real S) const {
75 if (S != sc)
76 preCalculate(S);
77
78 return 1 - dq*Phi_dp + dq/v*phi_dp + beta*(1-dq*Phi_dp)
79 + alpha/dr*charm;
80 }
81 Real fprime2(Real S) const {
82 if (S != sc)
83 preCalculate(S);
84
85 const Real gamma = phi_dp*dq/(v*S);
86 const Real colour = gamma*(q + (r-q)*dp/v + (1-dp*dm)/(2*tau));
87
88 return dq*(phi_dp/(S*v) - phi_dp*dp/(S*v*v))
89 + beta*gamma + alpha/dr*colour;
90 }
91
92 Real xmin() const { return xMin; }
93 Real xmax() const { return xMax; }
94 Size evaluations() const { return nrEvaluations; }
95
96 private:
97 void preCalculate(Real S) const {
98 S = std::max(QL_EPSILON, S);
99 sc = S;
100 dp = std::log(S*dq/(K*dr))/v + 0.5*v;
101 dm = dp - v;
102 Phi_dp = Phi(-dp);
103 Phi_dm = Phi(-dm);
104 phi_dp = phi(dp);
105
106 npv = dr*K*Phi_dm - S*dq*Phi_dp;
107 theta = r*K*dr*Phi_dm - q*S*dq*Phi_dp - sigma2*S/(2*v)*dq*phi_dp;
108 charm = -dq*(phi_dp*((r-q)/v - dm/(2*tau)) +q*Phi_dp);
109 }
110
111 const CumulativeNormalDistribution Phi;
112 const NormalDistribution phi;
113 const Time tau;
114 const Real K;
115 const Volatility sigma, sigma2, v;
116 const Rate r, q;
117 const DiscountFactor dr, dq, ddr;
118 const Real omega, lambda, lambdaPrime, alpha, beta, xMax, xMin;
119 mutable Size nrEvaluations = 0;
120 mutable Real sc, dp, dm, Phi_dp, Phi_dm, phi_dp;
121 mutable Real npv, theta, charm;
122 };
123
124
126 Time T, Real S, Real K, Rate r, Rate q, Volatility vol,
127 const Real xmax, ext::shared_ptr<Interpolation> q_z)
128 : T_(T), S_(S), K_(K), xmax_(xmax),
129 r_(r), q_(q), vol_(vol), q_z_(std::move(q_z)) {}
130
131
133 const Real t = z*z;
134 const Real q = (*q_z_)(2*std::sqrt(std::max(0.0, T_-t)/T_) - 1, true);
135 const Real b_t = xmax_*std::exp(-std::sqrt(std::max(0.0, q)));
136
137 const Real dr = std::exp(-r_*t);
138 const Real dq = std::exp(-q_*t);
139 const Real v = vol_*std::sqrt(t);
140
141 Real r;
142 if (v >= QL_EPSILON) {
143 if (b_t > QL_EPSILON) {
144 const Real dp = std::log(S_*dq/(b_t*dr))/v + 0.5*v;
145 r = 2*z*(r_*K_*dr*Phi_(-dp+v) - q_*S_*dq*Phi_(-dp));
146 }
147 else
148 r = 0.0;
149 }
150 else if (close_enough(S_*dq, b_t*dr))
151 r = z*(r_*K_*dr - q_*S_*dq);
152 else if (b_t*dr > S_*dq)
153 r = 2*z*(r_*K_*dr - q_*S_*dq);
154 else
155 r = 0.0;
156
157 return r;
158 }
159
160
162 ext::shared_ptr<GeneralizedBlackScholesProcess> process)
163 : process_(std::move(process)) {
165 }
166
168 QL_REQUIRE(arguments_.exercise->type() == Exercise::American,
169 "not an American option");
170
171 const auto payoff =
172 ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
173 QL_REQUIRE(payoff, "non-striked payoff given");
174
175 const Real spot = process_->x0();
176 QL_REQUIRE(spot >= 0.0, "negative underlying given");
177
178 const auto maturity = arguments_.exercise->lastDate();
179 const Time T = process_->time(maturity);
180 const Real S = process_->x0();
181 const Real K = payoff->strike();
182 const Rate r = -std::log(process_->riskFreeRate()->discount(maturity))/T;
183 const Rate q = -std::log(process_->dividendYield()->discount(maturity))/T;
184 const Volatility vol = process_->blackVolatility()->blackVol(T, K);
185
186 QL_REQUIRE(S >= 0, "zero or positive underlying value is required");
187 QL_REQUIRE(K >= 0, "zero or positive strike is required");
188 QL_REQUIRE(vol >= 0, "zero or positive volatility is required");
189
190 if (payoff->optionType() == Option::Put)
191 results_.value = calculatePutWithEdgeCases(S, K, r, q, vol, T);
192 else if (payoff->optionType() == Option::Call)
193 results_.value = calculatePutWithEdgeCases(K, S, q, r, vol, T);
194 else
195 QL_FAIL("unknown option type");
196 }
197
199 Real S, Real K, Rate r, Rate q, Volatility vol, Time T) const {
200
201 if (close(K, 0.0))
202 return 0.0;
203
204 if (close(S, 0.0))
205 return std::max(K, K*std::exp(-r*T));
206
207 if (r <= 0.0 && r <= q)
208 return std::max(0.0,
209 BlackCalculator(Option::Put, K, S*std::exp((r-q)*T),
210 vol*std::sqrt(T), std::exp(-r*T)).value());
211
212 if (close(vol, 0.0)) {
213 const auto intrinsic = [&](Real t) -> Real {
214 return std::max(0.0, K*std::exp(-r*t)-S*std::exp(-q*t));
215 };
216 const Real npv0 = intrinsic(0.0);
217 const Real npvT = intrinsic(T);
218 const Real extremT
219 = close_enough(r, q)? QL_MAX_REAL : Real(std::log(r*K/(q*S))/(r-q));
220
221 if (extremT > 0.0 && extremT < T)
222 return std::max({npv0, npvT, intrinsic(extremT)});
223 else
224 return std::max(npv0, npvT);
225 }
226
227 return calculatePut(S, K, r, q, vol, T);
228 }
229
230
232 //Table 2 from Leif Andersen, Mark Lake (2021)
233 //"Fast American Option Pricing: The Double-Boundary Case"
234
235 if (r > 0.0 && q > 0.0)
236 return K*std::min(1.0, r/q);
237 else if (r > 0.0 && q <= 0.0)
238 return K;
239 else if (r == 0.0 && q < 0.0)
240 return K;
241 else if (r == 0.0 && q >= 0.0)
242 return 0.0; // Eurpoean case
243 else if (r < 0.0 && q >= 0.0)
244 return 0.0; // European case
245 else if (r < 0.0 && q < r)
246 return K; // double boundary case
247 else if (r < 0.0 && r <= q && q < 0.0)
248 return 0; // European case
249 else
250 QL_FAIL("internal error");
251 }
252
254 ext::shared_ptr<GeneralizedBlackScholesProcess> process,
255 Size interpolationPoints,
257 Real eps, Size maxIter)
258 : detail::QdPutCallParityEngine(std::move(process)),
259 interpolationPoints_(interpolationPoints),
260 solverType_(solverType),
261 eps_(eps),
262 maxIter_((maxIter == Null<Size>()) ? (
263 (solverType == Newton
264 || solverType == Brent || solverType== Ridder)? 100 : 10)
265 : maxIter ) { }
266
267 template <class Solver>
269 const QdPlusBoundaryEvaluator& eval,
270 Solver solver, Real S, Real strike, Size maxIter, Real guess) const {
271
272 solver.setMaxEvaluations(maxIter);
273 solver.setLowerBound(eval.xmin());
274
275 const Real fxmin = eval(eval.xmin());
276 Real xmax = std::max(0.5*(eval.xmax() + S), eval.xmax());
277 while (eval(xmax)*fxmin > 0.0 && eval.evaluations() < maxIter_)
278 xmax*=2;
279
280 if (guess == Null<Real>())
281 guess = 0.5*(xmax + S);
282
283 if (guess >= xmax)
284 guess = std::nextafter(xmax, Real(-1));
285 else if (guess <= eval.xmin())
286 guess = std::nextafter(eval.xmin(), QL_MAX_REAL);
287
288 return solver.solve(eval, eps_, guess, eval.xmin(), xmax);
289 }
290
292 Real S, Real K, Rate r, Rate q,
293 Volatility vol, Time T, Time tau) const {
294
295 if (tau < QL_EPSILON)
296 return std::pair<Size, Real>(
297 Size(0), xMax(K, r, q));
298
299 const QdPlusBoundaryEvaluator eval(S, K, r, q, vol, tau, T);
300
301 Real x;
302 switch (solverType_) {
303 case Brent:
304 x = buildInSolver(eval, QuantLib::Brent(), S, K, maxIter_);
305 break;
306 case Newton:
307 x = buildInSolver(eval, QuantLib::Newton(), S, K, maxIter_);
308 break;
309 case Ridder:
310 x = buildInSolver(eval, QuantLib::Ridder(), S, K, maxIter_);
311 break;
312 case Halley:
313 case SuperHalley:
314 {
315 bool resultCloseEnough;
316 x = eval.xmax();
317 Real xOld, fx;
318 const Real xmin = eval.xmin();
319
320 do {
321 xOld = x;
322 fx = eval(x);
323 const Real fPrime = eval.derivative(x);
324 const Real lf = fx*eval.fprime2(x)/(fPrime*fPrime);
325 const Real step = (solverType_ == Halley)
326 ? Real(1/(1 - 0.5*lf)*fx/fPrime)
327 : Real((1 + 0.5*lf/(1-lf))*fx/fPrime);
328
329 x = std::max(xmin, x - step);
330 resultCloseEnough = std::fabs(x-xOld) < 0.5*eps_;
331 }
332 while (!resultCloseEnough && eval.evaluations() < maxIter_);
333
334 if (!resultCloseEnough && !close(std::fabs(fx), 0.0)) {
335 x = buildInSolver(eval, QuantLib::Brent(), S, K, 10*maxIter_, x);
336 }
337 }
338 break;
339 default:
340 QL_FAIL("unknown solver type");
341 }
342
343 return std::pair<Size, Real>(eval.evaluations(), x);
344 }
345
346 ext::shared_ptr<ChebyshevInterpolation>
348 Real S, Real K, Rate r, Rate q, Volatility vol, Time T) const {
349
350 const Real xmax = xMax(K, r, q);
351
352 return ext::make_shared<ChebyshevInterpolation>(
354 [&, this](Real z) {
355 const Real x_sq = 0.25*T*squared(1+z);
356 return squared(std::log(
357 this->putExerciseBoundaryAtTau(S, K, r, q, vol, T, x_sq)
358 .second/xmax));
359 },
361 );
362 }
363
365 Real S, Real K, Rate r, Rate q, Volatility vol, Time T) const {
366
367 if (r < 0.0 && q < r)
368 QL_FAIL("double-boundary case q<r<0 for a put option is given");
369
370 const ext::shared_ptr<Interpolation> q_z
371 = getPutExerciseBoundary(S, K, r, q, vol, T);
372
373 const Real xmax = xMax(K, r, q);
374 const detail::QdPlusAddOnValue aov(T, S, K, r, q, vol, xmax, q_z);
375
376#ifdef QL_BOOST_HAS_TANH_SINH
377 const Real addOn = TanhSinhIntegral(eps_)(aov, 0.0, std::sqrt(T));
378#else
379 const Real addOn = GaussLobattoIntegral(100000, QL_MAX_REAL, 0.1*eps_)
380 (aov, 0.0, std::sqrt(T));
381#endif
382
383 QL_REQUIRE(addOn > -10*eps_,
384 "negative early exercise value " << addOn);
385
386 const Real europeanValue = std::max(
387 0.0,
389 Option::Put, K,
390 S*std::exp((r-q)*T),
391 vol*std::sqrt(T), std::exp(-r*T)).value()
392 );
393
394 return europeanValue + std::max(0.0, addOn);
395 }
396}
Black-formula calculator class.
Brent 1-D solver.
ext::shared_ptr< SimpleQuote > vol_
Definition: cdsoption.cpp:62
const Instrument::results * results_
Definition: cdsoption.cpp:63
chebyshev interpolation between discrete Chebyshev nodes
Black 1976 calculator class.
Brent 1-D solver
Definition: brent.hpp:37
Integral of a one-dimensional function.
Newton 1-D solver
Definition: newton.hpp:40
template class providing a null value for a given type.
Definition: null.hpp:59
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:226
ext::shared_ptr< ChebyshevInterpolation > getPutExerciseBoundary(Real S, Real K, Rate r, Rate q, Volatility vol, Time T) const
static Real xMax(Real K, Rate r, Rate q)
std::pair< Size, Real > putExerciseBoundaryAtTau(Real S, Real K, Rate r, Rate q, Volatility vol, Time T, Time tau) const
Real calculatePut(Real S, Real K, Rate r, Rate q, Volatility vol, Time T) const override
QdPlusAmericanEngine(ext::shared_ptr< GeneralizedBlackScholesProcess >, Size interpolationPoints=8, SolverType solverType=Halley, Real eps=1e-6, Size maxIter=Null< Size >())
Real buildInSolver(const QdPlusBoundaryEvaluator &eval, Solver solver, Real S, Real strike, Size maxIter, Real guess=Null< Real >()) const
Ridder 1-D solver
Definition: ridder.hpp:37
QdPlusAddOnValue(Time T, Real S, Real K, Rate r, Rate q, Volatility vol, Real xmax, ext::shared_ptr< Interpolation > q_z)
QdPutCallParityEngine(ext::shared_ptr< GeneralizedBlackScholesProcess > process)
const ext::shared_ptr< GeneralizedBlackScholesProcess > process_
Real calculatePutWithEdgeCases(Real S, Real K, Rate r, Rate q, Volatility vol, Time T) const
floating-point comparisons
const DefaultType & t
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
Definition: errors.hpp:117
#define QL_FAIL(message)
throw an error (possibly with file and line information)
Definition: errors.hpp:92
Option exercise classes and payoff function.
integral of a one-dimensional function using the adaptive Gauss-Lobatto integral
#define QL_MAX_REAL
Definition: qldefines.hpp:176
#define QL_EPSILON
Definition: qldefines.hpp:178
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
QL_REAL Real
real number
Definition: types.hpp:50
Real DiscountFactor
discount factor between dates
Definition: types.hpp:66
Real Volatility
volatility
Definition: types.hpp:78
Real Rate
interest rates
Definition: types.hpp:70
std::size_t Size
size of a container
Definition: types.hpp:58
Real theta
Real sigma
ext::shared_ptr< QuantLib::Payoff > payoff
functionals and combinators not included in the STL
Definition: any.hpp:37
T squared(T x)
Definition: functional.hpp:37
bool close(const Quantity &m1, const Quantity &m2, Size n)
Definition: quantity.cpp:163
bool close_enough(const Quantity &m1, const Quantity &m2, Size n)
Definition: quantity.cpp:182
STL namespace.
Newton 1-D solver.
null values
ext::shared_ptr< YieldTermStructure > q
ext::shared_ptr< YieldTermStructure > r
ext::shared_ptr< BlackVolTermStructure > v
Ridder 1-D solver.
Real beta
Definition: sabr.cpp:200
Real alpha
Definition: sabr.cpp:200