Coverage Report

Created: 2026-06-08 06:47

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