Coverage Report

Created: 2025-12-08 06:13

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
0
    : process_(std::move(process)) {
33
0
        registerWith(process_);
34
0
    }
35
36
0
    void AnalyticBarrierEngine::calculate() const {
37
38
0
        ext::shared_ptr<PlainVanillaPayoff> payoff =
39
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
40
0
        QL_REQUIRE(payoff, "non-plain payoff given");
41
0
        QL_REQUIRE(payoff->strike()>0.0,
42
0
                   "strike must be positive");
43
44
0
        QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
45
0
                   "only european style option are supported");
46
47
0
        Real strike = payoff->strike();
48
0
        Real spot = process_->x0();
49
0
        QL_REQUIRE(spot > 0.0, "negative or null underlying given");
50
0
        QL_REQUIRE(!triggered(spot), "barrier touched");
51
52
0
        Barrier::Type barrierType = arguments_.barrierType;
53
54
0
        switch (payoff->optionType()) {
55
0
          case Option::Call:
56
0
            switch (barrierType) {
57
0
              case Barrier::DownIn:
58
0
                if (strike >= barrier())
59
0
                    results_.value = C(1,1) + E(1);
60
0
                else
61
0
                    results_.value = A(1) - B(1) + D(1,1) + E(1);
62
0
                break;
63
0
              case Barrier::UpIn:
64
0
                if (strike >= barrier())
65
0
                    results_.value = A(1) + E(-1);
66
0
                else
67
0
                    results_.value = B(1) - C(-1,1) + D(-1,1) + E(-1);
68
0
                break;
69
0
              case Barrier::DownOut:
70
0
                if (strike >= barrier())
71
0
                    results_.value = A(1) - C(1,1) + F(1);
72
0
                else
73
0
                    results_.value = B(1) - D(1,1) + F(1);
74
0
                break;
75
0
              case Barrier::UpOut:
76
0
                if (strike >= barrier())
77
0
                    results_.value = F(-1);
78
0
                else
79
0
                    results_.value = A(1) - B(1) + C(-1,1) - D(-1,1) + F(-1);
80
0
                break;
81
0
            }
82
0
            break;
83
0
          case Option::Put:
84
0
            switch (barrierType) {
85
0
              case Barrier::DownIn:
86
0
                if (strike >= barrier())
87
0
                    results_.value = B(-1) - C(1,-1) + D(1,-1) + E(1);
88
0
                else
89
0
                    results_.value = A(-1) + E(1);
90
0
                break;
91
0
              case Barrier::UpIn:
92
0
                if (strike >= barrier())
93
0
                    results_.value = A(-1) - B(-1) + D(-1,-1) + E(-1);
94
0
                else
95
0
                    results_.value = C(-1,-1) + E(-1);
96
0
                break;
97
0
              case Barrier::DownOut:
98
0
                if (strike >= barrier())
99
0
                    results_.value = A(-1) - B(-1) + C(1,-1) - D(1,-1) + F(1);
100
0
                else
101
0
                    results_.value = F(1);
102
0
                break;
103
0
              case Barrier::UpOut:
104
0
                if (strike >= barrier())
105
0
                    results_.value = B(-1) - D(-1,-1) + F(-1);
106
0
                else
107
0
                    results_.value = A(-1) - C(-1,-1) + F(-1);
108
0
                break;
109
0
            }
110
0
            break;
111
0
          default:
112
0
            QL_FAIL("unknown type");
113
0
        }
114
0
    }
115
116
117
0
    Real AnalyticBarrierEngine::underlying() const {
118
0
        return process_->x0();
119
0
    }
120
121
0
    Real AnalyticBarrierEngine::strike() const {
122
0
        ext::shared_ptr<PlainVanillaPayoff> payoff =
123
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
124
0
        QL_REQUIRE(payoff, "non-plain payoff given");
125
0
        return payoff->strike();
126
0
    }
127
128
0
    Volatility AnalyticBarrierEngine::volatility() const {
129
0
        return process_->blackVolatility()->blackVol(
130
0
                    arguments_.exercise->lastDate(),
131
0
                    strike());
132
0
    }
133
134
0
    Real AnalyticBarrierEngine::stdDeviation() const {
135
0
        return std::sqrt(process_->blackVolatility()->blackVariance(
136
0
                        arguments_.exercise->lastDate(),
137
0
                        strike()));
138
0
    }
139
140
0
    Real AnalyticBarrierEngine::barrier() const {
141
0
        return arguments_.barrier;
142
0
    }
143
144
0
    Real AnalyticBarrierEngine::rebate() const {
145
0
        return arguments_.rebate;
146
0
    }
147
148
0
    Rate AnalyticBarrierEngine::riskFreeRate() const {
149
0
        return process_->riskFreeRate()->zeroRate(
150
0
                    arguments_.exercise->lastDate(),
151
0
                    process_->riskFreeRate()->dayCounter(),
152
0
                    Continuous, NoFrequency);
153
0
    }
154
155
0
    DiscountFactor AnalyticBarrierEngine::riskFreeDiscount() const {
156
0
        return process_->riskFreeRate()->discount(
157
0
                    arguments_.exercise->lastDate());
158
0
    }
159
160
0
    Rate AnalyticBarrierEngine::dividendYield() const {
161
0
        return process_->dividendYield()->zeroRate(
162
0
                    arguments_.exercise->lastDate(),
163
0
                    process_->dividendYield()->dayCounter(),
164
0
                    Continuous, NoFrequency);
165
0
    }
166
167
0
    DiscountFactor AnalyticBarrierEngine::dividendDiscount() const {
168
0
        return process_->dividendYield()->discount(
169
0
                    arguments_.exercise->lastDate());
170
0
    }
171
172
0
    Rate AnalyticBarrierEngine::mu() const {
173
0
        Volatility vol = volatility();
174
0
        return (riskFreeRate() - dividendYield())/(vol * vol) - 0.5;
175
0
    }
176
177
0
    Real AnalyticBarrierEngine::muSigma() const {
178
0
        return (1 + mu()) * stdDeviation();
179
0
    }
180
181
0
    Real AnalyticBarrierEngine::A(Real phi) const {
182
0
        Real x1 =
183
0
            std::log(underlying()/strike())/stdDeviation() + muSigma();
184
0
        Real N1 = f_(phi*x1);
185
0
        Real N2 = f_(phi*(x1-stdDeviation()));
186
187
0
        return phi*(underlying() * dividendDiscount() * N1
188
0
                      - strike() * riskFreeDiscount() * N2);
189
0
    }
190
191
0
    Real AnalyticBarrierEngine::B(Real phi) const {
192
0
        Real x2 =
193
0
            std::log(underlying()/barrier())/stdDeviation() + muSigma();
194
0
        Real N1 = f_(phi*x2);
195
0
        Real N2 = f_(phi*(x2-stdDeviation()));
196
0
        return phi*(underlying() * dividendDiscount() * N1
197
0
                      - strike() * riskFreeDiscount() * N2);
198
0
    }
199
200
0
    Real AnalyticBarrierEngine::C(Real eta, Real phi) const {
201
0
        Real HS = barrier()/underlying();
202
0
        Real powHS0 = std::pow(HS, 2 * mu());
203
0
        Real powHS1 = powHS0 * HS * HS;
204
0
        Real y1 = std::log(barrier()*HS/strike())/stdDeviation() + muSigma();
205
0
        Real N1 = f_(eta*y1);
206
0
        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
0
        return phi*(underlying() * dividendDiscount() * (N1 == 0.0 ? Real(0.0) : Real(powHS1 * N1))
210
0
                      - strike() * riskFreeDiscount() * (N2 == 0.0 ? Real(0.0) : Real(powHS0 * N2)));
211
0
    }
212
213
0
    Real AnalyticBarrierEngine::D(Real eta, Real phi) const {
214
0
        Real HS = barrier()/underlying();
215
0
        Real powHS0 = std::pow(HS, 2 * mu());
216
0
        Real powHS1 = powHS0 * HS * HS;
217
0
        Real y2 = std::log(barrier()/underlying())/stdDeviation() + muSigma();
218
0
        Real N1 = f_(eta*y2);
219
0
        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
0
        return phi*(underlying() * dividendDiscount() * (N1 == 0.0 ? Real(0.0) : Real(powHS1 * N1))
223
0
                      - strike() * riskFreeDiscount() * (N2 == 0.0 ? Real(0.0) : Real(powHS0 * N2)));
224
0
    }
225
226
0
    Real AnalyticBarrierEngine::E(Real eta) const {
227
0
        if (rebate() > 0) {
228
0
            Real powHS0 = std::pow(barrier()/underlying(), 2 * mu());
229
0
            Real x2 =
230
0
                std::log(underlying()/barrier())/stdDeviation() + muSigma();
231
0
            Real y2 =
232
0
                std::log(barrier()/underlying())/stdDeviation() + muSigma();
233
0
            Real N1 = f_(eta*(x2 - stdDeviation()));
234
0
            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
0
            return rebate() * riskFreeDiscount() * (N1 - (N2 == 0.0 ? Real(0.0) : Real(powHS0 * N2)));
238
0
        } else {
239
0
            return 0.0;
240
0
        }
241
0
    }
242
243
0
    Real AnalyticBarrierEngine::F(Real eta) const {
244
0
        if (rebate() > 0) {
245
0
            Rate m = mu();
246
0
            Volatility vol = volatility();
247
0
            Real lambda = std::sqrt(m*m + 2.0*riskFreeRate()/(vol * vol));
248
0
            Real HS = barrier()/underlying();
249
0
            Real powHSplus = std::pow(HS, m + lambda);
250
0
            Real powHSminus = std::pow(HS, m - lambda);
251
252
0
            Real sigmaSqrtT = stdDeviation();
253
0
            Real z = std::log(barrier()/underlying())/sigmaSqrtT
254
0
                + lambda * sigmaSqrtT;
255
256
0
            Real N1 = f_(eta * z);
257
0
            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
0
            return rebate() * ((N1 == 0.0 ? Real(0.0) : Real(powHSplus * N1)) + (N2 == 0.0 ? Real(0.0) : Real(powHSminus * N2)));
261
0
        } else {
262
0
            return 0.0;
263
0
        }
264
0
    }
265
266
}