Coverage Report

Created: 2026-09-28 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/pricingengines/barrier/analyticbarrierengine.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
5
 Copyright (C) 2002, 2003 Ferdinando Ametrano
6
 Copyright (C) 2002, 2003 Sadruddin Rejeb
7
 Copyright (C) 2003 Neil Firth
8
 Copyright (C) 2007 StatPro Italia srl
9
10
 This file is part of QuantLib, a free-software/open-source library
11
 for financial quantitative analysts and developers - http://quantlib.org/
12
13
 QuantLib is free software: you can redistribute it and/or modify it
14
 under the terms of the QuantLib license.  You should have received a
15
 copy of the license along with this program; if not, please email
16
 <quantlib-dev@lists.sf.net>. The license is also available online at
17
 <https://www.quantlib.org/license.shtml>.
18
19
 This program is distributed in the hope that it will be useful, but WITHOUT
20
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21
 FOR A PARTICULAR PURPOSE.  See the license for more details.
22
*/
23
24
#include <ql/exercise.hpp>
25
#include <ql/pricingengines/barrier/analyticbarrierengine.hpp>
26
#include <utility>
27
28
namespace QuantLib {
29
30
    AnalyticBarrierEngine::AnalyticBarrierEngine(
31
        ext::shared_ptr<GeneralizedBlackScholesProcess> process)
32
405
    : process_(std::move(process)) {
33
405
        registerWith(process_);
34
405
    }
35
36
405
    void AnalyticBarrierEngine::calculate() const {
37
38
405
        ext::shared_ptr<PlainVanillaPayoff> payoff =
39
405
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
40
405
        QL_REQUIRE(payoff, "non-plain payoff given");
41
405
        QL_REQUIRE(payoff->strike()>0.0,
42
405
                   "strike must be positive");
43
44
405
        QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
45
405
                   "only european style option are supported");
46
47
405
        Real strike = payoff->strike();
48
405
        Real spot = process_->x0();
49
405
        QL_REQUIRE(spot > 0.0, "negative or null underlying given");
50
405
        QL_REQUIRE(!triggered(spot), "barrier touched");
51
52
332
        Barrier::Type barrierType = arguments_.barrierType;
53
54
332
        switch (payoff->optionType()) {
55
135
          case Option::Call:
56
135
            switch (barrierType) {
57
59
              case Barrier::DownIn:
58
59
                if (strike >= barrier())
59
36
                    results_.value = C(1,1) + E(1);
60
23
                else
61
23
                    results_.value = A(1) - B(1) + D(1,1) + E(1);
62
59
                break;
63
41
              case Barrier::UpIn:
64
41
                if (strike >= barrier())
65
18
                    results_.value = A(1) + E(-1);
66
23
                else
67
23
                    results_.value = B(1) - C(-1,1) + D(-1,1) + E(-1);
68
41
                break;
69
20
              case Barrier::DownOut:
70
20
                if (strike >= barrier())
71
18
                    results_.value = A(1) - C(1,1) + F(1);
72
2
                else
73
2
                    results_.value = B(1) - D(1,1) + F(1);
74
20
                break;
75
15
              case Barrier::UpOut:
76
15
                if (strike >= barrier())
77
5
                    results_.value = F(-1);
78
10
                else
79
10
                    results_.value = A(1) - B(1) + C(-1,1) - D(-1,1) + F(-1);
80
15
                break;
81
135
            }
82
135
            break;
83
197
          case Option::Put:
84
197
            switch (barrierType) {
85
105
              case Barrier::DownIn:
86
105
                if (strike >= barrier())
87
70
                    results_.value = B(-1) - C(1,-1) + D(1,-1) + E(1);
88
35
                else
89
35
                    results_.value = A(-1) + E(1);
90
105
                break;
91
34
              case Barrier::UpIn:
92
34
                if (strike >= barrier())
93
19
                    results_.value = A(-1) - B(-1) + D(-1,-1) + E(-1);
94
15
                else
95
15
                    results_.value = C(-1,-1) + E(-1);
96
34
                break;
97
31
              case Barrier::DownOut:
98
31
                if (strike >= barrier())
99
23
                    results_.value = A(-1) - B(-1) + C(1,-1) - D(1,-1) + F(1);
100
8
                else
101
8
                    results_.value = F(1);
102
31
                break;
103
27
              case Barrier::UpOut:
104
27
                if (strike >= barrier())
105
11
                    results_.value = B(-1) - D(-1,-1) + F(-1);
106
16
                else
107
16
                    results_.value = A(-1) - C(-1,-1) + F(-1);
108
27
                break;
109
197
            }
110
197
            break;
111
197
          default:
112
0
            QL_FAIL("unknown type");
113
332
        }
114
332
    }
115
116
117
2.30k
    Real AnalyticBarrierEngine::underlying() const {
118
2.30k
        return process_->x0();
119
2.30k
    }
120
121
6.23k
    Real AnalyticBarrierEngine::strike() const {
122
6.23k
        ext::shared_ptr<PlainVanillaPayoff> payoff =
123
6.23k
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
124
6.23k
        QL_REQUIRE(payoff, "non-plain payoff given");
125
6.23k
        return payoff->strike();
126
6.23k
    }
127
128
1.78k
    Volatility AnalyticBarrierEngine::volatility() const {
129
1.78k
        return process_->blackVolatility()->blackVol(
130
1.78k
                    arguments_.exercise->lastDate(),
131
1.78k
                    strike());
132
1.78k
    }
133
134
3.34k
    Real AnalyticBarrierEngine::stdDeviation() const {
135
3.34k
        return std::sqrt(process_->blackVolatility()->blackVariance(
136
3.34k
                        arguments_.exercise->lastDate(),
137
3.34k
                        strike()));
138
3.34k
    }
139
140
1.95k
    Real AnalyticBarrierEngine::barrier() const {
141
1.95k
        return arguments_.barrier;
142
1.95k
    }
143
144
569
    Real AnalyticBarrierEngine::rebate() const {
145
569
        return arguments_.rebate;
146
569
    }
147
148
1.78k
    Rate AnalyticBarrierEngine::riskFreeRate() const {
149
1.78k
        return process_->riskFreeRate()->zeroRate(
150
1.78k
                    arguments_.exercise->lastDate(),
151
1.78k
                    process_->riskFreeRate()->dayCounter(),
152
1.78k
                    Continuous, NoFrequency);
153
1.78k
    }
154
155
915
    DiscountFactor AnalyticBarrierEngine::riskFreeDiscount() const {
156
915
        return process_->riskFreeRate()->discount(
157
915
                    arguments_.exercise->lastDate());
158
915
    }
159
160
1.72k
    Rate AnalyticBarrierEngine::dividendYield() const {
161
1.72k
        return process_->dividendYield()->zeroRate(
162
1.72k
                    arguments_.exercise->lastDate(),
163
1.72k
                    process_->dividendYield()->dayCounter(),
164
1.72k
                    Continuous, NoFrequency);
165
1.72k
    }
166
167
735
    DiscountFactor AnalyticBarrierEngine::dividendDiscount() const {
168
735
        return process_->dividendYield()->discount(
169
735
                    arguments_.exercise->lastDate());
170
735
    }
171
172
1.72k
    Rate AnalyticBarrierEngine::mu() const {
173
1.72k
        Volatility vol = volatility();
174
1.72k
        return (riskFreeRate() - dividendYield())/(vol * vol) - 0.5;
175
1.72k
    }
176
177
1.09k
    Real AnalyticBarrierEngine::muSigma() const {
178
1.09k
        return (1 + mu()) * stdDeviation();
179
1.09k
    }
180
181
162
    Real AnalyticBarrierEngine::A(Real phi) const {
182
162
        Real x1 =
183
162
            std::log(underlying()/strike())/stdDeviation() + muSigma();
184
162
        Real N1 = f_(phi*x1);
185
162
        Real N2 = f_(phi*(x1-stdDeviation()));
186
187
162
        return phi*(underlying() * dividendDiscount() * N1
188
162
                      - strike() * riskFreeDiscount() * N2);
189
162
    }
190
191
181
    Real AnalyticBarrierEngine::B(Real phi) const {
192
181
        Real x2 =
193
181
            std::log(underlying()/barrier())/stdDeviation() + muSigma();
194
181
        Real N1 = f_(phi*x2);
195
181
        Real N2 = f_(phi*(x2-stdDeviation()));
196
181
        return phi*(underlying() * dividendDiscount() * N1
197
181
                      - strike() * riskFreeDiscount() * N2);
198
181
    }
199
200
211
    Real AnalyticBarrierEngine::C(Real eta, Real phi) const {
201
211
        Real HS = barrier()/underlying();
202
211
        Real powHS0 = std::pow(HS, 2 * mu());
203
211
        Real powHS1 = powHS0 * HS * HS;
204
211
        Real y1 = std::log(barrier()*HS/strike())/stdDeviation() + muSigma();
205
211
        Real N1 = f_(eta*y1);
206
211
        Real N2 = f_(eta*(y1-stdDeviation()));
207
        // when N1 or N2 are zero, the corresponding powHS might
208
        // be infinity, resulting in a NaN for their products.  The limit should be 0.
209
211
        return phi*(underlying() * dividendDiscount() * (N1 == 0.0 ? Real(0.0) : Real(powHS1 * N1))
210
211
                      - strike() * riskFreeDiscount() * (N2 == 0.0 ? Real(0.0) : Real(powHS0 * N2)));
211
211
    }
212
213
181
    Real AnalyticBarrierEngine::D(Real eta, Real phi) const {
214
181
        Real HS = barrier()/underlying();
215
181
        Real powHS0 = std::pow(HS, 2 * mu());
216
181
        Real powHS1 = powHS0 * HS * HS;
217
181
        Real y2 = std::log(barrier()/underlying())/stdDeviation() + muSigma();
218
181
        Real N1 = f_(eta*y2);
219
181
        Real N2 = f_(eta*(y2-stdDeviation()));
220
        // when N1 or N2 are zero, the corresponding powHS might
221
        // be infinity, resulting in a NaN for their products.  The limit should be 0.
222
181
        return phi*(underlying() * dividendDiscount() * (N1 == 0.0 ? Real(0.0) : Real(powHS1 * N1))
223
181
                      - strike() * riskFreeDiscount() * (N2 == 0.0 ? Real(0.0) : Real(powHS0 * N2)));
224
181
    }
225
226
239
    Real AnalyticBarrierEngine::E(Real eta) const {
227
239
        if (rebate() > 0) {
228
180
            Real powHS0 = std::pow(barrier()/underlying(), 2 * mu());
229
180
            Real x2 =
230
180
                std::log(underlying()/barrier())/stdDeviation() + muSigma();
231
180
            Real y2 =
232
180
                std::log(barrier()/underlying())/stdDeviation() + muSigma();
233
180
            Real N1 = f_(eta*(x2 - stdDeviation()));
234
180
            Real N2 = f_(eta*(y2 - stdDeviation()));
235
            // when N2 is zero, powHS0 might be infinity, resulting in
236
            // a NaN for their product.  The limit should be 0.
237
180
            return rebate() * riskFreeDiscount() * (N1 - (N2 == 0.0 ? Real(0.0) : Real(powHS0 * N2)));
238
180
        } else {
239
59
            return 0.0;
240
59
        }
241
239
    }
242
243
93
    Real AnalyticBarrierEngine::F(Real eta) const {
244
93
        if (rebate() > 0) {
245
57
            Rate m = mu();
246
57
            Volatility vol = volatility();
247
57
            Real lambda = std::sqrt(m*m + 2.0*riskFreeRate()/(vol * vol));
248
57
            Real HS = barrier()/underlying();
249
57
            Real powHSplus = std::pow(HS, m + lambda);
250
57
            Real powHSminus = std::pow(HS, m - lambda);
251
252
57
            Real sigmaSqrtT = stdDeviation();
253
57
            Real z = std::log(barrier()/underlying())/sigmaSqrtT
254
57
                + lambda * sigmaSqrtT;
255
256
57
            Real N1 = f_(eta * z);
257
57
            Real N2 = f_(eta * (z - 2.0 * lambda * sigmaSqrtT));
258
            // when N1 or N2 are zero, the corresponding powHS might
259
            // be infinity, resulting in a NaN for their product.  The limit should be 0.
260
57
            return rebate() * ((N1 == 0.0 ? Real(0.0) : Real(powHSplus * N1)) + (N2 == 0.0 ? Real(0.0) : Real(powHSminus * N2)));
261
57
        } else {
262
36
            return 0.0;
263
36
        }
264
93
    }
265
266
}