/src/quantlib/ql/pricingengines/vanilla/bjerksundstenslandengine.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2003 Ferdinando Ametrano |
5 | | Copyright (C) 2007 StatPro Italia srl |
6 | | Copyright (C) 2023 Klaus Spanderen |
7 | | |
8 | | This file is part of QuantLib, a free-software/open-source library |
9 | | for financial quantitative analysts and developers - http://quantlib.org/ |
10 | | |
11 | | QuantLib is free software: you can redistribute it and/or modify it |
12 | | under the terms of the QuantLib license. You should have received a |
13 | | copy of the license along with this program; if not, please email |
14 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
15 | | <https://www.quantlib.org/license.shtml>. |
16 | | |
17 | | This program is distributed in the hope that it will be useful, but WITHOUT |
18 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
19 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
20 | | */ |
21 | | |
22 | | #include <ql/any.hpp> |
23 | | #include <ql/exercise.hpp> |
24 | | #include <ql/math/functional.hpp> |
25 | | #include <ql/math/comparison.hpp> |
26 | | #include <ql/math/distributions/normaldistribution.hpp> |
27 | | #include <ql/pricingengines/blackcalculator.hpp> |
28 | | #include <ql/pricingengines/vanilla/bjerksundstenslandengine.hpp> |
29 | | #include <utility> |
30 | | #include <cmath> |
31 | | |
32 | | namespace QuantLib { |
33 | | |
34 | | namespace { |
35 | | |
36 | | CumulativeNormalDistribution cumNormalDist; |
37 | | Real phi(Real S, Real gamma, Real H, Real I, |
38 | 0 | Real rT, Real bT, Real variance) { |
39 | |
|
40 | 0 | Real lambda = (-rT + gamma * bT + 0.5 * gamma * (gamma - 1.0) |
41 | 0 | * variance); |
42 | 0 | Real d = -(std::log(S / H) + (bT + (gamma - 0.5) * variance) ) |
43 | 0 | / std::sqrt(variance); |
44 | 0 | Real kappa = 2.0 * bT / variance + (2.0 * gamma - 1.0); |
45 | 0 | return std::exp(lambda) * (cumNormalDist(d) |
46 | 0 | - std::pow((I / S), kappa) * |
47 | 0 | cumNormalDist(d - 2.0 * std::log(I/S) / std::sqrt(variance))); |
48 | 0 | } |
49 | | |
50 | 0 | Real phi_S(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
51 | 0 | const Real lsh = std::log(S/H); |
52 | 0 | const Real lis = std::log(I/S); |
53 | 0 | const Real sv = std::sqrt(v); |
54 | |
|
55 | 0 | return std::exp(bT*gamma - rT + ((-1 +gamma)*gamma*v)/2.)*((-(std::pow(I/S,2*(gamma + bT/v))/(std::exp(squared(2*bT - v + 2*gamma*v + 4*lis + 2*lsh)/(8.*v))*I))- 1/(std::exp(squared(2*bT - v + 2*gamma*v + 2*lsh)/(8.*v))*S))/(M_SQRT2*M_SQRTPI*sv) +(std::pow(I/S,2*(gamma + bT/v))*(2*bT + (-1 + 2*gamma)*v)*std::erfc((2*bT- v + 2*gamma*v + 4*lis + 2*lsh)/(2.*M_SQRT2*sv)))/(2.*I*v)); |
56 | 0 | } |
57 | | |
58 | 0 | Real phi_SS(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
59 | 0 | const Real lsh = std::log(S/H); |
60 | 0 | const Real lis = std::log(I/S); |
61 | 0 | const Real sv = std::sqrt(v); |
62 | 0 | const Real ex = std::exp(squared(2*bT - v + 2*gamma*v + 4*lis + 2*lsh)/(8.*v)); |
63 | 0 | const Real ey = std::exp(squared(2*bT + (-1 + 2*gamma)*v + 2*lsh)/(8.*v)); |
64 | |
|
65 | 0 | return (std::exp(bT*gamma - rT + ((-1 +gamma)*gamma*v)/2.)*((M_SQRT2*I*v*sv)/ey +(2*M_SQRT2*std::pow(I/S,2*(gamma + bT/v))*S*sv*(2*bT +(-1 + 2*gamma)*v))/ex -2*std::sqrt(M_PI)*std::pow(I/S,2*(gamma + bT/v))*S*(bT +gamma*v)*(2*bT + (-1 + 2*gamma)*v)*std::erfc((2*bT - v + 2*gamma*v +4*lis + 2*lsh)/(2.*M_SQRT2*sv)) +(M_SQRT2*I*sv*(bT + (-0.5 + gamma)*v +lsh))/ey - (std::pow(I/S,2*(gamma + bT/v))*S*sv*(2*bT - 3*v + 2*gamma*v + 4*lis +2*lsh))/(M_SQRT2*ex)))/(2.*I*M_SQRTPI*squared(S*v)); |
66 | 0 | } |
67 | | |
68 | 0 | Real phi_gamma(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
69 | 0 | const Real lsh = std::log(S/H); |
70 | 0 | const Real lis = std::log(I/S); |
71 | 0 | const Real sv = std::sqrt(v); |
72 | |
|
73 | 0 | return std::exp(bT*gamma - rT + ((-1 + gamma)*gamma*v)/2)*(((-std::exp(-squared(2*bT - v + 2*gamma*v +2*lsh)/(8*v)) + std::pow(I/S,-1 + 2*gamma +(2*bT)/v)/std::exp(squared(2*bT - v + 2*gamma*v + 4*lis +2*lsh)/(8*v)))*sv)/(M_SQRT2*M_SQRTPI) + ((2*bT+ (-1 + 2*gamma)*v)*std::erfc((2*bT + (-1 + 2*gamma)*v +2*lsh)/(2.*M_SQRT2*sv)))/4. -(std::pow(I/S,-1 + 2*gamma + (2*bT)/v)*std::erfc((2*bT - v + 2*gamma*v + 4*lis +2*lsh)/(2.*M_SQRT2*sv))*(2*bT + (-1 + 2*gamma)*v + 4*lis))/4.); |
74 | 0 | } |
75 | | |
76 | 0 | Real phi_H(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
77 | 0 | const Real lsh = std::log(S/H); |
78 | |
|
79 | 0 | return (std::exp(bT*gamma - rT + ((-1 + gamma)*gamma*v)/2.)*(I/std::exp(squared(2*bT - v + 2*gamma*v + 2*lsh)/(8.*v))- (std::pow(I/S,2*(gamma + bT/v))*S)/std::exp(squared(2*bT - v + 2*gamma*v + 4*std::log(I/S) + 2*lsh)/(8.*v))))/(H*I*std::sqrt(2*M_PI)*std::sqrt(v)); |
80 | 0 | } |
81 | | |
82 | 0 | Real phi_I(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
83 | 0 | const Real lsh = std::log(S/H); |
84 | 0 | const Real lis = std::log(I/S); |
85 | 0 | const Real sv = std::sqrt(v); |
86 | |
|
87 | 0 | return (std::exp(bT*gamma - rT + ((-1 + gamma)*gamma*v)/2.)*std::pow(I/S,2*(gamma + bT/v))*S*((2*std::sqrt(2/M_PI))/(std::exp(squared(2*bT - v + 2*gamma*v + 4*lis + 2*lsh)/(8.*v))*sv) + (1 - 2*gamma - (2*bT)/v)*std::erfc((2*bT - v + 2*gamma*v + 4*lis +2*lsh)/(2.*M_SQRT2*sv))))/(2.*I*I); |
88 | 0 | } |
89 | | |
90 | 0 | Real phi_rt(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
91 | 0 | const Real lsh = std::log(S/H); |
92 | 0 | return (std::exp(bT*gamma - rT + ((-1 + gamma)*gamma*v)/2.)*(-(I*std::erfc((2*bT- v + 2*gamma*v + 2*lsh)/(2.*std::sqrt(2*v)))) +std::pow(I/S,2*(gamma + bT/v))*S*std::erfc((2*bT - v + 2*gamma*v + 4*std::log(I/S) +2*lsh)/(2.*std::sqrt(2*v)))))/(2.*I); |
93 | 0 | } |
94 | | |
95 | 0 | Real phi_bt(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
96 | 0 | const Real lsh = std::log(S/H); |
97 | 0 | const Real lis = std::log(I/S); |
98 | 0 | const Real sv = std::sqrt(v); |
99 | |
|
100 | 0 | return (std::exp(bT*gamma - rT + ((-1 + gamma)*gamma*v)/2.)*(M_SQRT2*(-(I/std::exp(squared(2*bT - v +2*gamma*v + 2*lsh)/(8.*v))) + (std::pow(I/S,2*(gamma +bT/v))*S)/std::exp(squared(2*bT - v + 2*gamma*v + 4*lis +2*lsh)/(8.*v)))*sv + gamma*I*std::sqrt(M_PI)*v*std::erfc((2*bT - v + 2*gamma*v +2*lsh)/(2.*M_SQRT2*sv)) - M_SQRTPI*std::pow(I/S,2*(gamma + bT/v))*S*std::erfc((2*bT - v +2*gamma*v + 4*lis + 2*lsh)/(2.*M_SQRT2*sv))*(gamma*v +2*lis)))/(2.*I*std::sqrt(M_PI)*v); |
101 | 0 | } |
102 | | |
103 | 0 | Real phi_v(Real S, Real gamma, Real H, Real I, Real rT, Real bT, Real v) { |
104 | 0 | const Real lsh = std::log(S/H); |
105 | 0 | const Real lis = std::log(I/S); |
106 | 0 | const Real sv = std::sqrt(v); |
107 | 0 | const Real er = std::erfc((2*bT - v + 2*gamma*v + 4*lis + 2*lsh)/(2.*M_SQRT2*sv)); |
108 | |
|
109 | 0 | return (std::exp(bT*gamma - rT + ((-1 + gamma)*gamma*v)/2.)*(((-1 +gamma)*gamma*(I*std::erfc((2*bT - v + 2*gamma*v + 2*lsh)/(2.*M_SQRT2*sv)) -std::pow(I/S,2*(gamma + bT/v))*S*er))/(2.*I) +(2*bT*std::pow(I/S,-1 + 2*gamma + (2*bT)/v)*er*lis)/(v*v)+ (2*bT + v - 2*gamma*v + 2*lsh)/(2.*std::exp(std::pow(2*bT + (-1 + 2*gamma)*v +2*lsh,2)/(8.*v))*M_SQRT2*M_SQRTPI*v*sv) -(std::pow(I/S,-1 + 2*gamma + (2*bT)/v)*(2*bT + v - 2*gamma*v +4*lis + 2*lsh))/(2.*std::exp(squared(2*bT - v + 2*gamma*v + 4*lis + 2*lsh)/(8.*v))*M_SQRT2*M_SQRTPI*v*sv)))/2.; |
110 | 0 | } |
111 | | } |
112 | | |
113 | | BjerksundStenslandApproximationEngine::BjerksundStenslandApproximationEngine( |
114 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process) |
115 | 0 | : process_(std::move(process)) { |
116 | 0 | registerWith(process_); |
117 | 0 | } |
118 | | |
119 | | OneAssetOption::results |
120 | | BjerksundStenslandApproximationEngine::europeanCallResults( |
121 | 0 | Real S, Real X, Real rfD, Real dD, Real variance) const { |
122 | |
|
123 | 0 | OneAssetOption::results results; |
124 | |
|
125 | 0 | const Real forwardPrice = S * dD/rfD; |
126 | 0 | const BlackCalculator black( |
127 | 0 | Option::Call, X, forwardPrice, std::sqrt(variance), rfD); |
128 | |
|
129 | 0 | results.value = black.value(); |
130 | 0 | results.delta = black.delta(S); |
131 | 0 | results.gamma = black.gamma(S); |
132 | |
|
133 | 0 | const DayCounter rfdc = process_->riskFreeRate()->dayCounter(); |
134 | 0 | const DayCounter divdc = process_->dividendYield()->dayCounter(); |
135 | 0 | const DayCounter voldc = process_->blackVolatility()->dayCounter(); |
136 | 0 | Time t = |
137 | 0 | rfdc.yearFraction(process_->riskFreeRate()->referenceDate(), |
138 | 0 | arguments_.exercise->lastDate()); |
139 | 0 | results.rho = black.rho(t); |
140 | |
|
141 | 0 | t = divdc.yearFraction(process_->dividendYield()->referenceDate(), |
142 | 0 | arguments_.exercise->lastDate()); |
143 | 0 | results.dividendRho = black.dividendRho(t); |
144 | |
|
145 | 0 | t = voldc.yearFraction(process_->blackVolatility()->referenceDate(), |
146 | 0 | arguments_.exercise->lastDate()); |
147 | 0 | results.vega = black.vega(t); |
148 | 0 | results.theta = black.theta(S, t); |
149 | 0 | results.thetaPerDay = black.thetaPerDay(S, t); |
150 | |
|
151 | 0 | results.strikeSensitivity = black.strikeSensitivity(); |
152 | 0 | results.additionalResults["strikeGamma"] = Real(results.gamma*squared(S/X)); |
153 | 0 | results.additionalResults["exerciseType"] = std::string("European"); |
154 | |
|
155 | 0 | return results; |
156 | 0 | } |
157 | | |
158 | | OneAssetOption::results |
159 | 0 | BjerksundStenslandApproximationEngine::immediateExercise(Real S, Real X) const { |
160 | 0 | OneAssetOption::results results; |
161 | 0 | results.value = std::max(0.0, S - X); |
162 | 0 | results.delta = (S >= X)? 1.0 : 0.0; |
163 | 0 | results.gamma = 0.0; |
164 | 0 | results.rho = 0.0; |
165 | 0 | results.dividendRho = 0.0; |
166 | 0 | results.vega = 0.0; |
167 | 0 | results.theta = 0.0; |
168 | 0 | results.thetaPerDay = 0.0; |
169 | |
|
170 | 0 | results.strikeSensitivity = -results.delta; |
171 | 0 | results.additionalResults["strikeGamma"] = Real(0.0); |
172 | 0 | results.additionalResults["exerciseType"] = std::string("Immediate"); |
173 | |
|
174 | 0 | return results; |
175 | 0 | } |
176 | | |
177 | | OneAssetOption::results |
178 | | BjerksundStenslandApproximationEngine::americanCallApproximation( |
179 | 0 | Real S, Real X, Real rfD, Real dD, Real variance) const { |
180 | |
|
181 | 0 | const OneAssetOption::results europeanResults |
182 | 0 | = europeanCallResults(S, X, rfD, dD, variance); |
183 | |
|
184 | 0 | OneAssetOption::results results; |
185 | |
|
186 | 0 | const Real bT = std::log(dD/rfD); |
187 | 0 | const Real rT = std::log(1.0/rfD); |
188 | |
|
189 | 0 | const Real beta = (0.5 - bT/variance) + |
190 | 0 | std::sqrt(squared(bT/variance - 0.5) + 2.0 * rT/variance); |
191 | |
|
192 | 0 | const Real BInfinity = beta / (beta - 1.0) * X; |
193 | 0 | const Real B0 = (bT == rT) ? X : std::max(X, rT / (rT - bT) * X); |
194 | 0 | const Real ht = -(bT + 2.0*std::sqrt(variance)) * B0 / (BInfinity - B0); |
195 | |
|
196 | 0 | const Real I = B0 + (BInfinity - B0) * (1 - std::exp(ht)); |
197 | |
|
198 | 0 | const Real fwd = S * dD/rfD; |
199 | 0 | const Real q = std::log(I/fwd)/std::sqrt(variance); |
200 | |
|
201 | 0 | if (S >= I) { |
202 | 0 | results = immediateExercise(S, X); |
203 | 0 | } |
204 | 0 | else if (q > 12.5) { |
205 | | // We have a run away exercise boundary. It is numerically |
206 | | // more accurate to use the Greeks of the European engine. |
207 | 0 | results = europeanResults; |
208 | 0 | } |
209 | 0 | else { |
210 | 0 | const Real phi_S_beta_I_I_rT_bT_v |
211 | 0 | = phi(S, beta, I, I, rT, bT, variance); |
212 | 0 | const Real phi_S_1_I_I_rT_bT_v |
213 | 0 | = phi(S, 1.0, I, I, rT, bT, variance); |
214 | 0 | const Real phi_S_1_X_I_rT_bT_V |
215 | 0 | = phi(S, 1.0, X, I, rT, bT, variance); |
216 | 0 | results.value = (I - X) * std::pow(S/I, beta) |
217 | 0 | *(1 - phi_S_beta_I_I_rT_bT_v) |
218 | 0 | + S * phi_S_1_I_I_rT_bT_v |
219 | 0 | - S * phi_S_1_X_I_rT_bT_V |
220 | 0 | - X * phi(S, 0.0, I, I, rT, bT, variance) |
221 | 0 | + X * phi(S, 0.0, X, I, rT, bT, variance); |
222 | |
|
223 | 0 | const Real phi_S_S_beta_I_I_rT_bT_v |
224 | 0 | = phi_S(S, beta, I, I, rT, bT, variance); |
225 | 0 | const Real phi_S_S_1_I_I_rT_bT_v |
226 | 0 | = phi_S(S, 1.0, I, I, rT, bT, variance); |
227 | 0 | const Real phi_S_S_1_X_I_rT_bT_v |
228 | 0 | = phi_S(S, 1.0, X, I, rT, bT, variance); |
229 | 0 | results.delta = (I - X) * std::pow(S/I, beta-1)*beta/I |
230 | 0 | * (1 - phi_S_beta_I_I_rT_bT_v) |
231 | 0 | - (I - X) * std::pow(S/I, beta) |
232 | 0 | * phi_S_S_beta_I_I_rT_bT_v |
233 | 0 | + phi_S_1_I_I_rT_bT_v |
234 | 0 | + S*phi_S_S_1_I_I_rT_bT_v |
235 | 0 | - phi_S_1_X_I_rT_bT_V |
236 | 0 | - S*phi_S_S_1_X_I_rT_bT_v |
237 | 0 | - X*phi_S(S, 0.0, I, I, rT, bT, variance) |
238 | 0 | + X*phi_S(S, 0.0, X, I, rT, bT, variance); |
239 | |
|
240 | 0 | const Date refDate = process_->riskFreeRate()->referenceDate(); |
241 | 0 | const Date exerciseDate = arguments_.exercise->lastDate(); |
242 | 0 | const DayCounter qdc = process_->dividendYield()->dayCounter(); |
243 | 0 | const Time tq = qdc.yearFraction(refDate, exerciseDate); |
244 | |
|
245 | 0 | const Real betaDq = tq*(1/variance |
246 | 0 | - 1/(2*std::sqrt(squared(bT/variance - 0.5) + 2.0 * rT/variance)) |
247 | 0 | * 2*(bT/variance-0.5)/variance); |
248 | 0 | const Real BInfinityDq = -X/squared(beta-1.0)*betaDq; |
249 | 0 | const Real B0Dq = (dD <= rfD) ? Real(0.0) |
250 | 0 | : Real(X*std::log(rfD)/squared(std::log(dD))*tq); |
251 | |
|
252 | 0 | const Real htDq = tq * B0 / (BInfinity - B0) |
253 | 0 | - (bT + 2.0*std::sqrt(variance)) |
254 | 0 | *(B0Dq*(BInfinity - B0) - B0*(BInfinityDq - B0Dq)) |
255 | 0 | /squared(BInfinity - B0); |
256 | 0 | const Real IDq = B0Dq + (BInfinityDq - B0Dq) * (1 - std::exp(ht)) |
257 | 0 | - (BInfinity - B0) * std::exp(ht)*htDq; |
258 | |
|
259 | 0 | const Real phi_H_S_beta_I_I_rT_bT_v |
260 | 0 | = phi_H(S, beta, I, I, rT, bT, variance); |
261 | 0 | const Real phi_I_S_beta_I_I_rT_bT_v |
262 | 0 | = phi_I(S, beta, I, I, rT, bT, variance); |
263 | 0 | const Real phi_gamma_S_beta_I_I_rT_bT_v |
264 | 0 | = phi_gamma(S, beta, I, I, rT, bT, variance); |
265 | 0 | const Real phi_bt_S_beta_I_I_rT_bT_v |
266 | 0 | = phi_bt(S, beta, I, I, rT, bT, variance); |
267 | 0 | const Real phi_H_S_1_I_I_rT_bT_v |
268 | 0 | = phi_H(S, 1.0, I, I, rT, bT, variance); |
269 | 0 | const Real phi_I_S_1_I_I_rT_bT_v |
270 | 0 | = phi_I(S, 1.0, I, I, rT, bT, variance); |
271 | 0 | const Real phi_bt_S_1_I_I_rT_bT_v |
272 | 0 | = phi_bt(S, 1.0, I, I, rT, bT, variance); |
273 | 0 | const Real phi_I_S_1_X_I_rT_bT_v |
274 | 0 | = phi_I(S, 1.0, X, I, rT, bT, variance); |
275 | 0 | const Real phi_bt_S_1_X_I_rT_bT_v |
276 | 0 | = phi_bt(S, 1.0, X, I, rT, bT, variance); |
277 | 0 | const Real phi_H_S_0_I_I_rT_bT_v |
278 | 0 | = phi_H(S, 0.0, I, I, rT, bT, variance); |
279 | 0 | const Real phi_I_S_0_I_I_rT_bT_v |
280 | 0 | = phi_I(S, 0.0, I, I, rT, bT, variance); |
281 | 0 | const Real phi_bt_S_0_I_I_rT_bT_v |
282 | 0 | = phi_bt(S, 0.0, I, I, rT, bT, variance); |
283 | 0 | const Real phi_I_S_0_X_I_rT_bT_v |
284 | 0 | = phi_I(S, 0.0, X, I, rT, bT, variance); |
285 | 0 | const Real phi_bt_S_0_X_I_rT_bT_v |
286 | 0 | = phi_bt(S, 0.0, X, I, rT, bT, variance); |
287 | |
|
288 | 0 | results.dividendRho = |
289 | 0 | (IDq*std::pow(S/I, beta) |
290 | 0 | + (I-X)*std::pow(S/I, beta)*(betaDq*std::log(S/I) - beta*1/I*IDq)) |
291 | 0 | * (1 - phi_S_beta_I_I_rT_bT_v) |
292 | 0 | - (I - X) * std::pow(S/I, beta) |
293 | 0 | *( phi_H_S_beta_I_I_rT_bT_v*IDq |
294 | 0 | +phi_I_S_beta_I_I_rT_bT_v*IDq |
295 | 0 | +phi_gamma_S_beta_I_I_rT_bT_v*betaDq |
296 | 0 | -phi_bt_S_beta_I_I_rT_bT_v*tq) |
297 | 0 | + S*( phi_H_S_1_I_I_rT_bT_v*IDq |
298 | 0 | + phi_I_S_1_I_I_rT_bT_v*IDq |
299 | 0 | - phi_bt_S_1_I_I_rT_bT_v*tq) |
300 | 0 | - S*( phi_I_S_1_X_I_rT_bT_v*IDq |
301 | 0 | - phi_bt_S_1_X_I_rT_bT_v*tq) |
302 | 0 | - X*( phi_H_S_0_I_I_rT_bT_v*IDq |
303 | 0 | + phi_I_S_0_I_I_rT_bT_v*IDq |
304 | 0 | - phi_bt_S_0_I_I_rT_bT_v*tq) |
305 | 0 | + X*( phi_I_S_0_X_I_rT_bT_v*IDq |
306 | 0 | - phi_bt_S_0_X_I_rT_bT_v*tq); |
307 | |
|
308 | 0 | const DayCounter rdc = process_->riskFreeRate()->dayCounter(); |
309 | 0 | const Time tr = rdc.yearFraction(refDate, exerciseDate); |
310 | |
|
311 | 0 | const Real betaDr = tr*(-1/variance |
312 | 0 | + 1/(2*std::sqrt(squared(bT/variance - 0.5) + 2.0 * rT/variance)) |
313 | 0 | * 2*((bT/variance-0.5)/variance + 1/variance)); |
314 | 0 | const Real BInfinityDr = -X/squared(beta-1.0)*betaDr; |
315 | 0 | const Real B0Dr = (dD <= rfD) ? Real(0) : Real(-X*tr/std::log(dD)); |
316 | 0 | const Real htDr = -tr * B0 / (BInfinity - B0) |
317 | 0 | - (bT + 2.0*std::sqrt(variance)) |
318 | 0 | *(B0Dr*(BInfinity - B0) - B0*(BInfinityDr - B0Dr)) |
319 | 0 | /squared(BInfinity - B0); |
320 | 0 | const Real IDr = B0Dr + (BInfinityDr - B0Dr) * (1 - std::exp(ht)) |
321 | 0 | - (BInfinity - B0) * std::exp(ht)*htDr; |
322 | |
|
323 | 0 | results.rho = |
324 | 0 | (IDr*std::pow(S/I, beta) |
325 | 0 | + (I-X)*std::pow(S/I, beta)*(betaDr*std::log(S/I) - beta/I*IDr)) |
326 | 0 | * (1 - phi_S_beta_I_I_rT_bT_v) |
327 | 0 | - (I - X) * std::pow(S/I, beta) |
328 | 0 | *( phi_H_S_beta_I_I_rT_bT_v*IDr |
329 | 0 | + phi_I_S_beta_I_I_rT_bT_v*IDr |
330 | 0 | + phi_gamma_S_beta_I_I_rT_bT_v*betaDr |
331 | 0 | + phi_rt(S, beta, I, I, rT, bT, variance)*tr |
332 | 0 | + phi_bt_S_beta_I_I_rT_bT_v*tr) |
333 | 0 | + S*( phi_H_S_1_I_I_rT_bT_v*IDr |
334 | 0 | + phi_I_S_1_I_I_rT_bT_v*IDr |
335 | 0 | + phi_rt(S, 1.0, I, I, rT, bT, variance)*tr |
336 | 0 | + phi_bt_S_1_I_I_rT_bT_v*tr) |
337 | 0 | - S*( phi_I_S_1_X_I_rT_bT_v*IDr |
338 | 0 | + phi_rt(S, 1.0, X, I, rT, bT, variance)*tr |
339 | 0 | + phi_bt_S_1_X_I_rT_bT_v*tr) |
340 | 0 | - X*( phi_H_S_0_I_I_rT_bT_v*IDr |
341 | 0 | + phi_I_S_0_I_I_rT_bT_v*IDr |
342 | 0 | + phi_rt(S, 0.0, I, I, rT, bT, variance)*tr |
343 | 0 | + phi_bt_S_0_I_I_rT_bT_v*tr) |
344 | 0 | + X*( phi_I_S_0_X_I_rT_bT_v*IDr |
345 | 0 | + phi_rt(S, 0.0, X, I, rT, bT, variance)*tr |
346 | 0 | + phi_bt_S_0_X_I_rT_bT_v*tr); |
347 | |
|
348 | 0 | const Real beta = (0.5 - bT/variance) + |
349 | 0 | std::sqrt(squared(bT/variance - 0.5) + 2.0 * rT/variance); |
350 | |
|
351 | 0 | const DayCounter vdc = process_->blackVolatility()->dayCounter(); |
352 | 0 | const Time tv = vdc.yearFraction(refDate, exerciseDate); |
353 | 0 | const Real varianceDv = 2*std::sqrt(variance*tv); |
354 | |
|
355 | 0 | const Real betaDv = bT/squared(variance)*varianceDv + |
356 | 0 | - 1/(2*std::sqrt(squared(bT/variance - 0.5) + 2.0 * rT/variance)) |
357 | 0 | *( 2*(bT/variance - 0.5)*bT*varianceDv/squared(variance) |
358 | 0 | +2*rT/squared(variance)*varianceDv ); |
359 | 0 | const Real BInfinityDv = -X/squared(beta-1.0)*betaDv; |
360 | 0 | const Real htDv = -1/std::sqrt(variance)*varianceDv*B0/(BInfinity-B0) |
361 | 0 | + (bT + 2*std::sqrt(variance))*B0/squared(BInfinity-B0)*BInfinityDv; |
362 | |
|
363 | 0 | const Real IDv = BInfinityDv*(1-std::exp(ht)) |
364 | 0 | - (BInfinity-B0)*std::exp(ht)*htDv; |
365 | |
|
366 | 0 | results.vega = |
367 | 0 | (IDv*std::pow(S/I, beta) |
368 | 0 | + (I-X)*std::pow(S/I, beta)*(betaDv*std::log(S/I) - beta/I*IDv)) |
369 | 0 | * (1 - phi_S_beta_I_I_rT_bT_v) |
370 | 0 | - (I - X) * std::pow(S/I, beta) |
371 | 0 | *( phi_H_S_beta_I_I_rT_bT_v*IDv |
372 | 0 | + phi_I_S_beta_I_I_rT_bT_v*IDv |
373 | 0 | + phi_gamma_S_beta_I_I_rT_bT_v*betaDv |
374 | 0 | + phi_v(S, beta, I, I, rT, bT, variance)*varianceDv) |
375 | 0 | + S*( phi_H_S_1_I_I_rT_bT_v*IDv |
376 | 0 | + phi_I_S_1_I_I_rT_bT_v*IDv |
377 | 0 | + phi_v(S, 1.0, I, I, rT, bT, variance)*varianceDv) |
378 | 0 | - S*( phi_I_S_1_X_I_rT_bT_v*IDv |
379 | 0 | + phi_v(S, 1.0, X, I, rT, bT, variance)*varianceDv) |
380 | 0 | - X*( phi_H_S_0_I_I_rT_bT_v*IDv |
381 | 0 | + phi_I_S_0_I_I_rT_bT_v*IDv |
382 | 0 | + phi_v(S, 0.0, I, I, rT, bT, variance)*varianceDv) |
383 | 0 | + X*( phi_I_S_0_X_I_rT_bT_v*IDv |
384 | 0 | + phi_v(S, 0.0, X, I, rT, bT, variance)*varianceDv); |
385 | |
|
386 | 0 | results.gamma = |
387 | 0 | (I - X) * std::pow(S/I, beta-2)*beta*(beta-1)/squared(I) |
388 | 0 | * (1 - phi_S_beta_I_I_rT_bT_v) |
389 | 0 | - 2*(I - X) * std::pow(S/I, beta-1)*beta/I |
390 | 0 | *phi_S_S_beta_I_I_rT_bT_v |
391 | 0 | - (I - X) * std::pow(S/I, beta) |
392 | 0 | * phi_SS(S, beta, I, I, rT, bT, variance) |
393 | |
|
394 | 0 | + 2*phi_S_S_1_I_I_rT_bT_v |
395 | 0 | + S*phi_SS(S, 1.0, I, I, rT, bT, variance) |
396 | |
|
397 | 0 | - 2*phi_S_S_1_X_I_rT_bT_v |
398 | 0 | - S*phi_SS(S, 1.0, X, I, rT, bT, variance) |
399 | |
|
400 | 0 | - X*phi_SS(S, 0.0, I, I, rT, bT, variance) |
401 | 0 | + X*phi_SS(S, 0.0, X, I, rT, bT, variance); |
402 | |
|
403 | 0 | const Volatility vol = std::sqrt(variance/tv); |
404 | |
|
405 | 0 | const Date tomorrow = refDate + Period(1, Days); |
406 | 0 | const Time dtq = qdc.yearFraction(refDate, exerciseDate) |
407 | 0 | - qdc.yearFraction(tomorrow, exerciseDate); |
408 | 0 | const Time dtr = rdc.yearFraction(refDate, exerciseDate) |
409 | 0 | - rdc.yearFraction(tomorrow, exerciseDate); |
410 | 0 | const Time dtv = vdc.yearFraction(refDate, exerciseDate) |
411 | 0 | - vdc.yearFraction(tomorrow, exerciseDate); |
412 | |
|
413 | 0 | results.thetaPerDay = -(0.5*results.vega*vol/tv*dtv |
414 | 0 | + results.rho*rT/(tr*tr)*dtr + results.dividendRho*(rT-bT)/(tq*tq)*dtq); |
415 | 0 | results.theta = 365*results.thetaPerDay; |
416 | |
|
417 | 0 | results.strikeSensitivity = results.value/X - S/X*results.delta; |
418 | 0 | results.additionalResults["strikeGamma"] = Real(results.gamma*squared(S/X)); |
419 | |
|
420 | 0 | results.additionalResults["exerciseType"] = std::string("American"); |
421 | 0 | } |
422 | | |
423 | | // check if European engine gives higher NPV |
424 | 0 | if (results.value < europeanResults.value) { |
425 | 0 | results = europeanResults; |
426 | 0 | } |
427 | |
|
428 | 0 | return results; |
429 | 0 | } |
430 | | |
431 | | |
432 | 0 | void BjerksundStenslandApproximationEngine::calculate() const { |
433 | |
|
434 | 0 | QL_REQUIRE(arguments_.exercise->type() == Exercise::American, |
435 | 0 | "not an American Option"); |
436 | | |
437 | 0 | ext::shared_ptr<AmericanExercise> ex = |
438 | 0 | ext::dynamic_pointer_cast<AmericanExercise>(arguments_.exercise); |
439 | 0 | QL_REQUIRE(ex, "non-American exercise given"); |
440 | 0 | QL_REQUIRE(!ex->payoffAtExpiry(), |
441 | 0 | "payoff at expiry not handled"); |
442 | | |
443 | 0 | ext::shared_ptr<PlainVanillaPayoff> payoff = |
444 | 0 | ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff); |
445 | 0 | QL_REQUIRE(payoff, "non-plain payoff given"); |
446 | | |
447 | 0 | Real variance = |
448 | 0 | process_->blackVolatility()->blackVariance(ex->lastDate(), |
449 | 0 | payoff->strike()); |
450 | 0 | DiscountFactor dividendDiscount = |
451 | 0 | process_->dividendYield()->discount(ex->lastDate()); |
452 | 0 | DiscountFactor riskFreeDiscount = |
453 | 0 | process_->riskFreeRate()->discount(ex->lastDate()); |
454 | 0 | Real spot = process_->stateVariable()->value(); |
455 | 0 | QL_REQUIRE(spot > 0.0, "negative or null underlying given"); |
456 | 0 | Real strike = payoff->strike(); |
457 | |
|
458 | 0 | if (payoff->optionType()==Option::Put) { |
459 | | // use put-call symmetry |
460 | 0 | std::swap(spot, strike); |
461 | 0 | std::swap(riskFreeDiscount, dividendDiscount); |
462 | 0 | payoff = ext::make_shared<PlainVanillaPayoff>( |
463 | 0 | Option::Call, strike); |
464 | 0 | } |
465 | |
|
466 | 0 | if (dividendDiscount > 1.0 && riskFreeDiscount > dividendDiscount) |
467 | 0 | QL_FAIL("double-boundary case r<q<0 for a call given"); |
468 | | |
469 | | |
470 | 0 | if (dividendDiscount >= 1.0 && dividendDiscount >= riskFreeDiscount) { |
471 | 0 | results_ = europeanCallResults( |
472 | 0 | spot, strike, riskFreeDiscount, dividendDiscount, variance); |
473 | 0 | } else { |
474 | | // early exercise can be optimal - use approximation |
475 | 0 | results_ = americanCallApproximation( |
476 | 0 | spot, strike, riskFreeDiscount, dividendDiscount, variance); |
477 | 0 | } |
478 | | |
479 | | // check if immediate exercise gives higher NPV |
480 | 0 | if (results_.value < (spot - strike)*(1+10*QL_EPSILON) ) { |
481 | 0 | results_ = immediateExercise(spot, strike); |
482 | 0 | } |
483 | |
|
484 | 0 | if (ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff) |
485 | 0 | ->optionType() == Option::Put) { |
486 | |
|
487 | 0 | std::swap(results_.delta, results_.strikeSensitivity); |
488 | |
|
489 | 0 | Real tmp = results_.gamma; |
490 | 0 | results_.gamma = |
491 | 0 | ext::any_cast<Real>(results_.additionalResults["strikeGamma"]); |
492 | 0 | results_.additionalResults["strikeGamma"] = tmp; |
493 | |
|
494 | 0 | std::swap(results_.rho, results_.dividendRho); |
495 | |
|
496 | 0 | Time tr = process_->riskFreeRate()->dayCounter().yearFraction( |
497 | 0 | process_->riskFreeRate()->referenceDate(), |
498 | 0 | arguments_.exercise->lastDate()); |
499 | 0 | Time tq = process_->dividendYield()->dayCounter().yearFraction( |
500 | 0 | process_->dividendYield()->referenceDate(), |
501 | 0 | arguments_.exercise->lastDate()); |
502 | |
|
503 | 0 | results_.rho *= tr/tq; |
504 | 0 | results_.dividendRho *= tq/tr; |
505 | 0 | } |
506 | 0 | } |
507 | | } |