/src/quantlib/ql/pricingengines/vanilla/baroneadesiwhaleyengine.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2003, 2006 Ferdinando Ametrano |
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 | | #include <ql/exercise.hpp> |
22 | | #include <ql/math/comparison.hpp> |
23 | | #include <ql/math/distributions/normaldistribution.hpp> |
24 | | #include <ql/pricingengines/blackcalculator.hpp> |
25 | | #include <ql/pricingengines/blackformula.hpp> |
26 | | #include <ql/pricingengines/vanilla/baroneadesiwhaleyengine.hpp> |
27 | | #include <utility> |
28 | | |
29 | | namespace QuantLib { |
30 | | |
31 | | BaroneAdesiWhaleyApproximationEngine::BaroneAdesiWhaleyApproximationEngine( |
32 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process) |
33 | 1.11k | : process_(std::move(process)) { |
34 | 1.11k | registerWith(process_); |
35 | 1.11k | } |
36 | | |
37 | | |
38 | | // critical commodity price |
39 | | Real BaroneAdesiWhaleyApproximationEngine::criticalPrice( |
40 | | const ext::shared_ptr<StrikedTypePayoff>& payoff, |
41 | | DiscountFactor riskFreeDiscount, |
42 | | DiscountFactor dividendDiscount, |
43 | 145 | Real variance, Real tolerance) { |
44 | | |
45 | 145 | QL_REQUIRE(riskFreeDiscount <= 1.0, |
46 | 118 | "the Barone-Adesi-Whaley approximation is not applicable " |
47 | 118 | "with negative interest rates " |
48 | 118 | "(risk-free discount factor: " << riskFreeDiscount << ")"); |
49 | | |
50 | | // Calculation of seed value, Si |
51 | 118 | Real n= 2.0*std::log(dividendDiscount/riskFreeDiscount)/(variance); |
52 | 118 | Real m=-2.0*std::log(riskFreeDiscount)/(variance); |
53 | 118 | Real bT = std::log(dividendDiscount/riskFreeDiscount); |
54 | | |
55 | 118 | Real qu, Su, h, Si; |
56 | 118 | switch (payoff->optionType()) { |
57 | 49 | case Option::Call: |
58 | 49 | qu = (-(n-1.0) + std::sqrt(((n-1.0)*(n-1.0)) + 4.0*m))/2.0; |
59 | 49 | Su = payoff->strike() / (1.0 - 1.0/qu); |
60 | 49 | h = -(bT + 2.0*std::sqrt(variance)) * payoff->strike() / |
61 | 49 | (Su - payoff->strike()); |
62 | 49 | Si = payoff->strike() + (Su - payoff->strike()) * |
63 | 49 | (1.0 - std::exp(h)); |
64 | 49 | break; |
65 | 69 | case Option::Put: |
66 | 69 | qu = (-(n-1.0) - std::sqrt(((n-1.0)*(n-1.0)) + 4.0*m))/2.0; |
67 | 69 | Su = payoff->strike() / (1.0 - 1.0/qu); |
68 | 69 | h = (bT - 2.0*std::sqrt(variance)) * payoff->strike() / |
69 | 69 | (payoff->strike() - Su); |
70 | 69 | Si = Su + (payoff->strike() - Su) * std::exp(h); |
71 | 69 | break; |
72 | 0 | default: |
73 | 0 | QL_FAIL("unknown option type"); |
74 | 118 | } |
75 | | |
76 | | |
77 | | // Newton Raphson algorithm for finding critical price Si |
78 | 118 | Real Q, LHS, RHS, bi; |
79 | 118 | Real forwardSi = Si * dividendDiscount / riskFreeDiscount; |
80 | 118 | Real d1 = (std::log(forwardSi/payoff->strike()) + 0.5*variance) / |
81 | 118 | std::sqrt(variance); |
82 | 118 | CumulativeNormalDistribution cumNormalDist; |
83 | 118 | Real K = (!close(riskFreeDiscount, 1.0, 1000)) |
84 | 118 | ? Real(-2.0*std::log(riskFreeDiscount) |
85 | 107 | / (variance*(1.0-riskFreeDiscount))) |
86 | 118 | : 2.0/variance; |
87 | 118 | Real temp = blackFormula(payoff->optionType(), payoff->strike(), |
88 | 118 | forwardSi, std::sqrt(variance))*riskFreeDiscount; |
89 | 118 | switch (payoff->optionType()) { |
90 | 45 | case Option::Call: |
91 | 45 | Q = (-(n-1.0) + std::sqrt(((n-1.0)*(n-1.0)) + 4 * K)) / 2; |
92 | 45 | LHS = Si - payoff->strike(); |
93 | 45 | RHS = temp + (1 - dividendDiscount * cumNormalDist(d1)) * Si / Q; |
94 | 45 | bi = dividendDiscount * cumNormalDist(d1) * (1 - 1/Q) + |
95 | 45 | (1 - dividendDiscount * |
96 | 45 | cumNormalDist.derivative(d1) / std::sqrt(variance)) / Q; |
97 | 248 | while (std::fabs(LHS - RHS)/payoff->strike() > tolerance) { |
98 | 203 | Si = (payoff->strike() + RHS - bi * Si) / (1 - bi); |
99 | 203 | forwardSi = Si * dividendDiscount / riskFreeDiscount; |
100 | 203 | d1 = (std::log(forwardSi/payoff->strike())+0.5*variance) |
101 | 203 | /std::sqrt(variance); |
102 | 203 | LHS = Si - payoff->strike(); |
103 | 203 | Real temp2 = blackFormula(payoff->optionType(), payoff->strike(), |
104 | 203 | forwardSi, std::sqrt(variance))*riskFreeDiscount; |
105 | 203 | RHS = temp2 + (1 - dividendDiscount * cumNormalDist(d1)) * Si / Q; |
106 | 203 | bi = dividendDiscount * cumNormalDist(d1) * (1 - 1 / Q) |
107 | 203 | + (1 - dividendDiscount * |
108 | 203 | cumNormalDist.derivative(d1) / std::sqrt(variance)) |
109 | 203 | / Q; |
110 | 203 | } |
111 | 45 | break; |
112 | 63 | case Option::Put: |
113 | 63 | Q = (-(n-1.0) - std::sqrt(((n-1.0)*(n-1.0)) + 4 * K)) / 2; |
114 | 63 | LHS = payoff->strike() - Si; |
115 | 63 | RHS = temp - (1 - dividendDiscount * cumNormalDist(-d1)) * Si / Q; |
116 | 63 | bi = -dividendDiscount * cumNormalDist(-d1) * (1 - 1/Q) |
117 | 63 | - (1 + dividendDiscount * cumNormalDist.derivative(-d1) |
118 | 63 | / std::sqrt(variance)) / Q; |
119 | 282 | while (std::fabs(LHS - RHS)/payoff->strike() > tolerance) { |
120 | 219 | Si = (payoff->strike() - RHS + bi * Si) / (1 + bi); |
121 | 219 | forwardSi = Si * dividendDiscount / riskFreeDiscount; |
122 | 219 | d1 = (std::log(forwardSi/payoff->strike())+0.5*variance) |
123 | 219 | /std::sqrt(variance); |
124 | 219 | LHS = payoff->strike() - Si; |
125 | 219 | Real temp2 = blackFormula(payoff->optionType(), payoff->strike(), |
126 | 219 | forwardSi, std::sqrt(variance))*riskFreeDiscount; |
127 | 219 | RHS = temp2 - (1 - dividendDiscount * cumNormalDist(-d1)) * Si / Q; |
128 | 219 | bi = -dividendDiscount * cumNormalDist(-d1) * (1 - 1 / Q) |
129 | 219 | - (1 + dividendDiscount * cumNormalDist.derivative(-d1) |
130 | 219 | / std::sqrt(variance)) / Q; |
131 | 219 | } |
132 | 63 | break; |
133 | 0 | default: |
134 | 0 | QL_FAIL("unknown option type"); |
135 | 118 | } |
136 | | |
137 | 103 | return Si; |
138 | 118 | } |
139 | | |
140 | 150 | void BaroneAdesiWhaleyApproximationEngine::calculate() const { |
141 | | |
142 | 150 | QL_REQUIRE(arguments_.exercise->type() == Exercise::American, |
143 | 150 | "not an American Option"); |
144 | | |
145 | 150 | ext::shared_ptr<AmericanExercise> ex = |
146 | 150 | ext::dynamic_pointer_cast<AmericanExercise>(arguments_.exercise); |
147 | 150 | QL_REQUIRE(ex, "non-American exercise given"); |
148 | 150 | QL_REQUIRE(!ex->payoffAtExpiry(), |
149 | 150 | "payoff at expiry not handled"); |
150 | | |
151 | 150 | ext::shared_ptr<StrikedTypePayoff> payoff = |
152 | 150 | ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff); |
153 | 150 | QL_REQUIRE(payoff, "non-striked payoff given"); |
154 | | |
155 | 150 | Real variance = process_->blackVolatility()->blackVariance( |
156 | 150 | ex->lastDate(), payoff->strike()); |
157 | 150 | DiscountFactor dividendDiscount = process_->dividendYield()->discount( |
158 | 150 | ex->lastDate()); |
159 | 150 | DiscountFactor riskFreeDiscount = process_->riskFreeRate()->discount( |
160 | 150 | ex->lastDate()); |
161 | 150 | Real spot = process_->stateVariable()->value(); |
162 | 150 | QL_REQUIRE(spot > 0.0, "negative or null underlying given"); |
163 | 150 | Real forwardPrice = spot * dividendDiscount / riskFreeDiscount; |
164 | 150 | BlackCalculator black(payoff, forwardPrice, std::sqrt(variance), |
165 | 150 | riskFreeDiscount); |
166 | | |
167 | 150 | if (dividendDiscount>=1.0 && payoff->optionType()==Option::Call) { |
168 | | // early exercise never optimal |
169 | 5 | results_.value = black.value(); |
170 | 5 | results_.delta = black.delta(spot); |
171 | 5 | results_.deltaForward = black.deltaForward(); |
172 | 5 | results_.elasticity = black.elasticity(spot); |
173 | 5 | results_.gamma = black.gamma(spot); |
174 | | |
175 | 5 | DayCounter rfdc = process_->riskFreeRate()->dayCounter(); |
176 | 5 | DayCounter divdc = process_->dividendYield()->dayCounter(); |
177 | 5 | DayCounter voldc = process_->blackVolatility()->dayCounter(); |
178 | 5 | Time t = |
179 | 5 | rfdc.yearFraction(process_->riskFreeRate()->referenceDate(), |
180 | 5 | arguments_.exercise->lastDate()); |
181 | 5 | results_.rho = black.rho(t); |
182 | | |
183 | 5 | t = divdc.yearFraction(process_->dividendYield()->referenceDate(), |
184 | 5 | arguments_.exercise->lastDate()); |
185 | 5 | results_.dividendRho = black.dividendRho(t); |
186 | | |
187 | 5 | t = voldc.yearFraction(process_->blackVolatility()->referenceDate(), |
188 | 5 | arguments_.exercise->lastDate()); |
189 | 5 | results_.vega = black.vega(t); |
190 | 5 | results_.theta = black.theta(spot, t); |
191 | 5 | results_.thetaPerDay = black.thetaPerDay(spot, t); |
192 | | |
193 | 5 | results_.strikeSensitivity = black.strikeSensitivity(); |
194 | 5 | results_.itmCashProbability = black.itmCashProbability(); |
195 | 145 | } else { |
196 | | // early exercise can be optimal |
197 | 145 | CumulativeNormalDistribution cumNormalDist; |
198 | 145 | Real tolerance = 1e-6; |
199 | 145 | Real Sk = criticalPrice(payoff, riskFreeDiscount, |
200 | 145 | dividendDiscount, variance, tolerance); |
201 | 145 | Real forwardSk = Sk * dividendDiscount / riskFreeDiscount; |
202 | 145 | Real d1 = (std::log(forwardSk/payoff->strike()) + 0.5*variance) |
203 | 145 | /std::sqrt(variance); |
204 | 145 | Real n = 2.0*std::log(dividendDiscount/riskFreeDiscount)/variance; |
205 | 145 | Real K = (!close(riskFreeDiscount, 1.0, 1000)) |
206 | 145 | ? Real(-2.0*std::log(riskFreeDiscount) |
207 | 92 | / (variance*(1.0-riskFreeDiscount))) |
208 | 145 | : 2.0/variance; |
209 | 145 | Real Q, a; |
210 | 145 | switch (payoff->optionType()) { |
211 | 45 | case Option::Call: |
212 | 45 | Q = (-(n-1.0) + std::sqrt(((n-1.0)*(n-1.0))+4.0*K))/2.0; |
213 | 45 | a = (Sk/Q) * (1.0 - dividendDiscount * cumNormalDist(d1)); |
214 | 45 | if (spot<Sk) { |
215 | 40 | results_.value = black.value() + |
216 | 40 | a * std::pow((spot/Sk), Q); |
217 | 40 | } else { |
218 | 5 | results_.value = spot - payoff->strike(); |
219 | 5 | } |
220 | 45 | break; |
221 | 58 | case Option::Put: |
222 | 58 | Q = (-(n-1.0) - std::sqrt(((n-1.0)*(n-1.0))+4.0*K))/2.0; |
223 | 58 | a = -(Sk/Q) * |
224 | 58 | (1.0 - dividendDiscount * cumNormalDist(-d1)); |
225 | 58 | if (spot>Sk) { |
226 | 42 | results_.value = black.value() + |
227 | 42 | a * std::pow((spot/Sk), Q); |
228 | 42 | } else { |
229 | 16 | results_.value = payoff->strike() - spot; |
230 | 16 | } |
231 | 58 | break; |
232 | 0 | default: |
233 | | QL_FAIL("unknown option type"); |
234 | 145 | } |
235 | 145 | } // end of "early exercise can be optimal" |
236 | 150 | } |
237 | | |
238 | | } |