Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
baroneadesiwhaleyengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18/*
19 Copyright (C) 2003, 2006 Ferdinando Ametrano
20 Copyright (C) 2007 StatPro Italia srl
21
22 This file is part of QuantLib, a free-software/open-source library
23 for financial quantitative analysts and developers - http://quantlib.org/
24
25 QuantLib is free software: you can redistribute it and/or modify it
26 under the terms of the QuantLib license. You should have received a
27 copy of the license along with this program; if not, please email
28 <quantlib-dev@lists.sf.net>. The license is also available online at
29 <http://quantlib.org/license.shtml>.
30
31 This program is distributed in the hope that it will be useful, but WITHOUT
32 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
33 FOR A PARTICULAR PURPOSE. See the license for more details.
34*/
35
36#include <ql/exercise.hpp>
37#include <ql/math/comparison.hpp>
38#include <ql/math/distributions/normaldistribution.hpp>
39#include <ql/pricingengines/blackcalculator.hpp>
40#include <ql/pricingengines/blackformula.hpp>
42
43using namespace QuantLib;
44
45namespace QuantExt {
46
48 const ext::shared_ptr<GeneralizedBlackScholesProcess>& process)
49 : process_(process) {
50 registerWith(process_);
51}
52
53// critical commodity price
54Real BaroneAdesiWhaleyApproximationEngine::criticalPrice(const QuantLib::ext::shared_ptr<StrikedTypePayoff>& payoff,
55 DiscountFactor riskFreeDiscount,
56 DiscountFactor dividendDiscount, Real variance,
57 Real tolerance) {
58
59 // Calculation of seed value, Si
60 Real n = 2.0 * std::log(dividendDiscount / riskFreeDiscount) / (variance);
61 Real m = -2.0 * std::log(riskFreeDiscount) / (variance);
62 Real bT = std::log(dividendDiscount / riskFreeDiscount);
63
64 Real qu, Su, h, Si;
65 switch (payoff->optionType()) {
66 case Option::Call:
67 qu = (-(n - 1.0) + std::sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * m)) / 2.0;
68 Su = payoff->strike() / (1.0 - 1.0 / qu);
69 h = -(bT + 2.0 * std::sqrt(variance)) * payoff->strike() / (Su - payoff->strike());
70 Si = payoff->strike() + (Su - payoff->strike()) * (1.0 - std::exp(h));
71 break;
72 case Option::Put:
73 qu = (-(n - 1.0) - std::sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * m)) / 2.0;
74 Su = payoff->strike() / (1.0 - 1.0 / qu);
75 h = (bT - 2.0 * std::sqrt(variance)) * payoff->strike() / (payoff->strike() - Su);
76 Si = Su + (payoff->strike() - Su) * std::exp(h);
77 break;
78 default:
79 QL_FAIL("unknown option type");
80 }
81
82 Real maxIterations = 100;
83 // Newton Raphson algorithm for finding critical price Si
84 Real Q, LHS, RHS, bi;
85 Real forwardSi = Si * dividendDiscount / riskFreeDiscount;
86 Real d1 = (std::log(forwardSi / payoff->strike()) + 0.5 * variance) / std::sqrt(variance);
87 CumulativeNormalDistribution cumNormalDist;
88 Real K = (!close(riskFreeDiscount, 1.0, 1000))
89 ? -2.0 * std::log(riskFreeDiscount) / (variance * (1.0 - riskFreeDiscount))
90 : 2.0 / variance;
91 Real temp = blackFormula(payoff->optionType(), payoff->strike(), forwardSi, std::sqrt(variance)) * riskFreeDiscount;
92 Real i;
93 switch (payoff->optionType()) {
94 case Option::Call:
95 Q = (-(n - 1.0) + std::sqrt(((n - 1.0) * (n - 1.0)) + 4 * K)) / 2;
96 LHS = Si - payoff->strike();
97 RHS = temp + (1 - dividendDiscount * cumNormalDist(d1)) * Si / Q;
98 bi = dividendDiscount * cumNormalDist(d1) * (1 - 1 / Q) +
99 (1 - dividendDiscount * cumNormalDist.derivative(d1) / std::sqrt(variance)) / Q;
100 i = 0;
101 while ((std::fabs(LHS - RHS) / payoff->strike()) > tolerance) {
102 if (i > maxIterations)
103 QL_FAIL("Failed to find critical price after " << maxIterations << "iterations.");
104 Si = (payoff->strike() + RHS - bi * Si) / (1 - bi);
105 forwardSi = Si * dividendDiscount / riskFreeDiscount;
106 d1 = (std::log(forwardSi / payoff->strike()) + 0.5 * variance) / std::sqrt(variance);
107 LHS = Si - payoff->strike();
108 Real temp2 =
109 blackFormula(payoff->optionType(), payoff->strike(), forwardSi, std::sqrt(variance)) * riskFreeDiscount;
110 RHS = temp2 + (1 - dividendDiscount * cumNormalDist(d1)) * Si / Q;
111 bi = dividendDiscount * cumNormalDist(d1) * (1 - 1 / Q) +
112 (1 - dividendDiscount * cumNormalDist.derivative(d1) / std::sqrt(variance)) / Q;
113 i++;
114 }
115 break;
116 case Option::Put:
117 Q = (-(n - 1.0) - std::sqrt(((n - 1.0) * (n - 1.0)) + 4 * K)) / 2;
118 LHS = payoff->strike() - Si;
119 RHS = temp - (1 - dividendDiscount * cumNormalDist(-d1)) * Si / Q;
120 bi = -dividendDiscount * cumNormalDist(-d1) * (1 - 1 / Q) -
121 (1 + dividendDiscount * cumNormalDist.derivative(-d1) / std::sqrt(variance)) / Q;
122 i = 0;
123 while ((std::fabs(LHS - RHS) / payoff->strike()) > tolerance) {
124 if (i > maxIterations)
125 QL_FAIL("Failed to find critical price after " << maxIterations << "iterations.");
126 Si = (payoff->strike() - RHS + bi * Si) / (1 + bi);
127 forwardSi = Si * dividendDiscount / riskFreeDiscount;
128 d1 = (std::log(forwardSi / payoff->strike()) + 0.5 * variance) / std::sqrt(variance);
129 LHS = payoff->strike() - Si;
130 Real temp2 =
131 blackFormula(payoff->optionType(), payoff->strike(), forwardSi, std::sqrt(variance)) * riskFreeDiscount;
132 RHS = temp2 - (1 - dividendDiscount * cumNormalDist(-d1)) * Si / Q;
133 bi = -dividendDiscount * cumNormalDist(-d1) * (1 - 1 / Q) -
134 (1 + dividendDiscount * cumNormalDist.derivative(-d1) / std::sqrt(variance)) / Q;
135 i++;
136 }
137 break;
138 default:
139 QL_FAIL("unknown option type");
140 }
141
142 return Si;
143}
144
146
147 QL_REQUIRE(arguments_.exercise->type() == Exercise::American, "not an American Option");
148
149 ext::shared_ptr<AmericanExercise> ex = ext::dynamic_pointer_cast<AmericanExercise>(arguments_.exercise);
150 QL_REQUIRE(ex, "non-American exercise given");
151 QL_REQUIRE(!ex->payoffAtExpiry(), "payoff at expiry not handled");
152
153 ext::shared_ptr<StrikedTypePayoff> payoff = ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
154 QL_REQUIRE(payoff, "non-striked payoff given");
155
156 Real variance = process_->blackVolatility()->blackVariance(ex->lastDate(), payoff->strike());
157 DiscountFactor dividendDiscount = process_->dividendYield()->discount(ex->lastDate());
158 DiscountFactor riskFreeDiscount = process_->riskFreeRate()->discount(ex->lastDate());
159 Real spot = process_->stateVariable()->value();
160 QL_REQUIRE(spot > 0.0, "negative or null underlying given");
161 Real forwardPrice = spot * dividendDiscount / riskFreeDiscount;
162 BlackCalculator black(payoff, forwardPrice, std::sqrt(variance), riskFreeDiscount);
163 bool useEuropeanPrice = (payoff->optionType() == Option::Call &&
164 ((dividendDiscount >= 1.0 && riskFreeDiscount < 1.0) ||
165 (riskFreeDiscount >= 1.0 && (dividendDiscount / riskFreeDiscount) >= 1.0))) ||
166 (dividendDiscount < 1.0 && riskFreeDiscount >= 1.0 && payoff->optionType() == Option::Put);
167
168 Real tolerance = 1e-6;
169 Real Sk = 0.0;
170 // try to determine the critical price for the american option
171 // in some case, for example r < 0, q < 0 it is never optimal to exercise early
172 // and we cannot solve for critical price, the newton raphson solver diverges
173 // with Si turning negative
174 try {
175 Sk = criticalPrice(payoff, riskFreeDiscount, dividendDiscount, variance, tolerance);
176 } catch (...) {
177 // failed to calculate the critical price
178 useEuropeanPrice = true;
179 }
180
181 if (useEuropeanPrice) {
182 // early exercise never optimal
183 results_.value = black.value();
184 results_.delta = black.delta(spot);
185 results_.deltaForward = black.deltaForward();
186 results_.elasticity = black.elasticity(spot);
187 results_.gamma = black.gamma(spot);
188
189 DayCounter rfdc = process_->riskFreeRate()->dayCounter();
190 DayCounter divdc = process_->dividendYield()->dayCounter();
191 DayCounter voldc = process_->blackVolatility()->dayCounter();
192 Time t = rfdc.yearFraction(process_->riskFreeRate()->referenceDate(), arguments_.exercise->lastDate());
193 results_.rho = black.rho(t);
194
195 t = divdc.yearFraction(process_->dividendYield()->referenceDate(), arguments_.exercise->lastDate());
196 results_.dividendRho = black.dividendRho(t);
197
198 t = voldc.yearFraction(process_->blackVolatility()->referenceDate(), arguments_.exercise->lastDate());
199 results_.vega = black.vega(t);
200 results_.theta = black.theta(spot, t);
201 results_.thetaPerDay = black.thetaPerDay(spot, t);
202
203 results_.strikeSensitivity = black.strikeSensitivity();
204 results_.itmCashProbability = black.itmCashProbability();
205 } else {
206 // early exercise can be optimal
207 CumulativeNormalDistribution cumNormalDist;
208 Real forwardSk = Sk * dividendDiscount / riskFreeDiscount;
209 Real d1 = (std::log(forwardSk / payoff->strike()) + 0.5 * variance) / std::sqrt(variance);
210 Real n = 2.0 * std::log(dividendDiscount / riskFreeDiscount) / variance;
211 Real K = (!close(riskFreeDiscount, 1.0, 1000))
212 ? -2.0 * std::log(riskFreeDiscount) / (variance * (1.0 - riskFreeDiscount))
213 : 2.0 / variance;
214 Real Q, a;
215 switch (payoff->optionType()) {
216 case Option::Call:
217 Q = (-(n - 1.0) + std::sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * K)) / 2.0;
218 a = (Sk / Q) * (1.0 - dividendDiscount * cumNormalDist(d1));
219 if (spot < Sk) {
220 results_.value = black.value() + a * std::pow((spot / Sk), Q);
221 } else {
222 results_.value = spot - payoff->strike();
223 }
224 break;
225 case Option::Put:
226 Q = (-(n - 1.0) - std::sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * K)) / 2.0;
227 a = -(Sk / Q) * (1.0 - dividendDiscount * cumNormalDist(-d1));
228 if (spot > Sk) {
229 results_.value = black.value() + a * std::pow((spot / Sk), Q);
230 } else {
231 results_.value = payoff->strike() - spot;
232 }
233 break;
234 default:
235 QL_FAIL("unknown option type");
236 }
237 } // end of "early exercise can be optimal"
238}
239
240} // namespace QuantExt
Barone-Adesi and Whaley approximation engine.
const Instrument::results * results_
Definition: cdsoption.cpp:81
QuantLib::ext::shared_ptr< QuantLib::GeneralizedBlackScholesProcess > process_
static QuantLib::Real criticalPrice(const QuantLib::ext::shared_ptr< QuantLib::StrikedTypePayoff > &payoff, QuantLib::DiscountFactor riskFreeDiscount, QuantLib::DiscountFactor dividendDiscount, QuantLib::Real variance, QuantLib::Real tolerance=1e-6)
BaroneAdesiWhaleyApproximationEngine(const QuantLib::ext::shared_ptr< QuantLib::GeneralizedBlackScholesProcess > &)
RandomVariable variance(const RandomVariable &r)
RandomVariable black(const RandomVariable &omega, const RandomVariable &t, const RandomVariable &strike, const RandomVariable &forward, const RandomVariable &impliedVol)
Swap::arguments * arguments_