Coverage Report

Created: 2026-01-25 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}