Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/pricingengines/barrier/analyticbinarybarrierengine.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2014 Thema Consulting SA
5
6
 This file is part of QuantLib, a free-software/open-source library
7
 for financial quantitative analysts and developers - http://quantlib.org/
8
9
 QuantLib is free software: you can redistribute it and/or modify it
10
 under the terms of the QuantLib license.  You should have received a
11
 copy of the license along with this program; if not, please email
12
 <quantlib-dev@lists.sf.net>. The license is also available online at
13
 <http://quantlib.org/license.shtml>.
14
15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19
20
#include <ql/exercise.hpp>
21
#include <ql/instruments/vanillaoption.hpp>
22
#include <ql/math/distributions/normaldistribution.hpp>
23
#include <ql/pricingengines/barrier/analyticbinarybarrierengine.hpp>
24
#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
25
#include <utility>
26
27
namespace QuantLib {
28
29
    // calc helper object 
30
    class AnalyticBinaryBarrierEngine_helper
31
    {
32
    
33
    public:
34
        AnalyticBinaryBarrierEngine_helper(
35
             const ext::shared_ptr<GeneralizedBlackScholesProcess>& process,
36
             const ext::shared_ptr<StrikedTypePayoff> &payoff,
37
             const ext::shared_ptr<AmericanExercise> &exercise,
38
             const BarrierOption::arguments &arguments):
39
0
        process_(process),
40
0
        payoff_(payoff),
41
0
        exercise_(exercise),
42
0
        arguments_(arguments)
43
0
        {
44
0
        }
45
46
        Real payoffAtExpiry(Real spot, Real variance, Real discount);
47
    private:
48
        const ext::shared_ptr<GeneralizedBlackScholesProcess>& process_;
49
        const ext::shared_ptr<StrikedTypePayoff> &payoff_;
50
        const ext::shared_ptr<AmericanExercise> &exercise_;
51
        const BarrierOption::arguments &arguments_;
52
    };
53
54
55
    AnalyticBinaryBarrierEngine::AnalyticBinaryBarrierEngine(
56
        ext::shared_ptr<GeneralizedBlackScholesProcess> process)
57
0
    : process_(std::move(process)) {
58
0
        registerWith(process_);
59
0
    }
60
61
0
    void AnalyticBinaryBarrierEngine::calculate() const {
62
63
0
        ext::shared_ptr<AmericanExercise> ex =
64
0
            ext::dynamic_pointer_cast<AmericanExercise>(arguments_.exercise);
65
0
        QL_REQUIRE(ex, "non-American exercise given");
66
0
        QL_REQUIRE(ex->payoffAtExpiry(), "payoff must be at expiry");
67
0
        QL_REQUIRE(ex->dates()[0] <=
68
0
                   process_->blackVolatility()->referenceDate(),
69
0
                   "American option with window exercise not handled yet");
70
71
0
        ext::shared_ptr<StrikedTypePayoff> payoff =
72
0
            ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
73
0
        QL_REQUIRE(payoff, "non-striked payoff given");
74
75
0
        Real spot = process_->stateVariable()->value();
76
0
        QL_REQUIRE(spot > 0.0, "negative or null underlying given");
77
78
0
        Real variance =
79
0
            process_->blackVolatility()->blackVariance(ex->lastDate(),
80
0
                                                       payoff->strike());
81
0
        Real barrier = arguments_.barrier;
82
0
        QL_REQUIRE(barrier>0.0,
83
0
                   "positive barrier value required");
84
0
        Barrier::Type barrierType = arguments_.barrierType;
85
86
        // KO degenerate cases
87
0
        if ( (barrierType == Barrier::DownOut && spot <= barrier) ||
88
0
             (barrierType == Barrier::UpOut && spot >= barrier))
89
0
        {
90
            // knocked out, no value
91
0
            results_.value = 0;
92
0
            results_.delta = 0;
93
0
            results_.gamma = 0;
94
0
            results_.vega = 0;
95
0
            results_.theta = 0;
96
0
            results_.rho = 0;
97
0
            results_.dividendRho = 0;
98
0
            return;
99
0
        }
100
101
        // KI degenerate cases
102
0
        if ((barrierType == Barrier::DownIn && spot <= barrier) ||
103
0
           (barrierType == Barrier::UpIn && spot >= barrier)) {
104
            // knocked in - is a digital european
105
0
            ext::shared_ptr<Exercise> exercise(new EuropeanExercise(
106
0
                                             arguments_.exercise->lastDate()));
107
108
0
            ext::shared_ptr<PricingEngine> engine(
109
0
                                       new AnalyticEuropeanEngine(process_));
110
111
0
            VanillaOption opt(payoff, exercise);
112
0
            opt.setPricingEngine(engine);
113
0
            results_.value = opt.NPV();
114
0
            results_.delta = opt.delta();
115
0
            results_.gamma = opt.gamma();
116
0
            results_.vega = opt.vega();
117
0
            results_.theta = opt.theta();
118
0
            results_.rho = opt.rho();
119
0
            results_.dividendRho = opt.dividendRho();
120
0
            return;
121
0
        }
122
123
0
        Rate riskFreeDiscount =
124
0
            process_->riskFreeRate()->discount(ex->lastDate());
125
126
0
        AnalyticBinaryBarrierEngine_helper helper(process_,
127
0
           payoff, ex, arguments_);
128
0
        results_.value = helper.payoffAtExpiry(spot, variance, riskFreeDiscount);
129
0
    }
130
131
    Real AnalyticBinaryBarrierEngine_helper::payoffAtExpiry(
132
         Real spot, Real variance, Real discount)
133
0
    {
134
0
        Rate dividendDiscount =
135
0
            process_->dividendYield()->discount(exercise_->lastDate());
136
137
0
        QL_REQUIRE(spot>0.0,
138
0
                   "positive spot value required");
139
140
0
        QL_REQUIRE(discount>0.0,
141
0
                   "positive discount required");
142
143
0
        QL_REQUIRE(dividendDiscount>0.0,
144
0
                   "positive dividend discount required");
145
146
0
        QL_REQUIRE(variance>=0.0,
147
0
                   "negative variance not allowed");
148
149
0
        Option::Type type   = payoff_->optionType();
150
0
        Real strike = payoff_->strike();
151
0
        Real barrier = arguments_.barrier;
152
0
        QL_REQUIRE(barrier>0.0,
153
0
                   "positive barrier value required");
154
0
        Barrier::Type barrierType = arguments_.barrierType;
155
156
0
        Real stdDev = std::sqrt(variance);
157
0
        Real mu = std::log(dividendDiscount/discount)/variance - 0.5;
158
0
        Real K = 0;
159
160
        // binary cash-or-nothing payoff?
161
0
        ext::shared_ptr<CashOrNothingPayoff> coo =
162
0
            ext::dynamic_pointer_cast<CashOrNothingPayoff>(payoff_);
163
0
        if (coo != nullptr) {
164
0
            K = coo->cashPayoff();
165
0
        }
166
167
        // binary asset-or-nothing payoff?
168
0
        ext::shared_ptr<AssetOrNothingPayoff> aoo =
169
0
            ext::dynamic_pointer_cast<AssetOrNothingPayoff>(payoff_);
170
0
        if (aoo != nullptr) {
171
0
            mu += 1.0; 
172
0
            K = spot * dividendDiscount / discount; // forward
173
0
        }
174
175
0
        Real log_S_X = std::log(spot/strike);
176
0
        Real log_S_H = std::log(spot/barrier);
177
0
        Real log_H_S = std::log(barrier/spot);
178
0
        Real log_H2_SX = std::log(barrier*barrier/(spot*strike));
179
0
        Real H_S_2mu = std::pow(barrier/spot, 2*mu);
180
181
0
        Real eta = (barrierType == Barrier::DownIn ||
182
0
                    barrierType == Barrier::DownOut ? 1.0 : -1.0);
183
0
        Real phi = (type == Option::Call ? 1.0 : -1.0);
184
185
0
        Real x1, x2, y1, y2;
186
0
        Real cum_x1, cum_x2, cum_y1, cum_y2;
187
0
        if (variance>=QL_EPSILON) {
188
189
            // we calculate using mu*stddev instead of (mu+1)*stddev
190
            // because cash-or-nothing don't need it. asset-or-nothing
191
            // mu is really mu+1
192
0
            x1 = phi*(log_S_X/stdDev + mu*stdDev);
193
0
            x2 = phi*(log_S_H/stdDev + mu*stdDev);
194
0
            y1 = eta*(log_H2_SX/stdDev + mu*stdDev);
195
0
            y2 = eta*(log_H_S/stdDev + mu*stdDev);
196
197
0
            CumulativeNormalDistribution f;
198
0
            cum_x1 = f(x1);
199
0
            cum_x2 = f(x2);
200
0
            cum_y1 = f(y1);
201
0
            cum_y2 = f(y2);
202
0
        } else {
203
0
            if (log_S_X>0)
204
0
                cum_x1= 1.0;
205
0
            else
206
0
                cum_x1= 0.0;
207
0
            if (log_S_H>0)
208
0
                cum_x2= 1.0;
209
0
            else
210
0
                cum_x2= 0.0;
211
0
            if (log_H2_SX>0)
212
0
                cum_y1= 1.0;
213
0
            else
214
0
                cum_y1= 0.0;
215
0
            if (log_H_S>0)
216
0
                cum_y2= 1.0;
217
0
            else
218
0
                cum_y2= 0.0;
219
0
        }
220
221
0
        Real alpha = 0;
222
223
0
        switch (barrierType) {
224
0
            case Barrier::DownIn:
225
0
               if (type == Option::Call) {
226
                  // down-in and call
227
0
                  if (strike >= barrier) {
228
                     // B3 (eta=1, phi=1)
229
0
                     alpha = H_S_2mu * cum_y1;  
230
0
                  } else {
231
                     // B1-B2+B4 (eta=1, phi=1)
232
0
                     alpha = cum_x1 - cum_x2 + H_S_2mu * cum_y2; 
233
0
                  }
234
0
               }
235
0
               else {
236
                  // down-in and put 
237
0
                  if (strike >= barrier) {
238
                     // B2-B3+B4 (eta=1, phi=-1)
239
0
                     alpha = cum_x2 + H_S_2mu*(-cum_y1 + cum_y2);
240
0
                  } else {
241
                     // B1 (eta=1, phi=-1)
242
0
                     alpha = cum_x1;
243
0
                  }
244
0
               }
245
0
               break;
246
247
0
            case Barrier::UpIn:
248
0
               if (type == Option::Call) {
249
                  // up-in and call
250
0
                  if (strike >= barrier) {
251
                     // B1 (eta=-1, phi=1)
252
0
                     alpha = cum_x1;  
253
0
                  } else {
254
                     // B2-B3+B4 (eta=-1, phi=1)
255
0
                     alpha = cum_x2 + H_S_2mu * (-cum_y1 + cum_y2);
256
0
                  }
257
0
               }
258
0
               else {
259
                  // up-in and put 
260
0
                  if (strike >= barrier) {
261
                     // B1-B2+B4 (eta=-1, phi=-1)
262
0
                     alpha = cum_x1 - cum_x2 + H_S_2mu * cum_y2;
263
0
                  } else {
264
                     // B3 (eta=-1, phi=-1)
265
0
                     alpha = H_S_2mu * cum_y1;  
266
0
                  }
267
0
               }
268
0
               break;
269
270
0
            case Barrier::DownOut:
271
0
               if (type == Option::Call) {
272
                  // down-out and call
273
0
                  if (strike >= barrier) {
274
                     // B1-B3 (eta=1, phi=1)
275
0
                     alpha = cum_x1 - H_S_2mu * cum_y1; 
276
0
                  } else {
277
                     // B2-B4 (eta=1, phi=1)
278
0
                     alpha = cum_x2 - H_S_2mu * cum_y2; 
279
0
                  }
280
0
               }
281
0
               else {
282
                  // down-out and put 
283
0
                  if (strike >= barrier) {
284
                     // B1-B2+B3-B4 (eta=1, phi=-1)
285
0
                     alpha = cum_x1 - cum_x2 + H_S_2mu * (cum_y1-cum_y2);
286
0
                  } else {
287
                     // always 0
288
0
                     alpha = 0;  
289
0
                  }
290
0
               }
291
0
               break;
292
0
            case Barrier::UpOut:
293
0
               if (type == Option::Call) {
294
                  // up-out and call
295
0
                  if (strike >= barrier) {
296
                     // always 0
297
0
                     alpha = 0;  
298
0
                  } else {
299
                     // B1-B2+B3-B4 (eta=-1, phi=1)
300
0
                     alpha = cum_x1 - cum_x2 + H_S_2mu * (cum_y1-cum_y2);
301
0
                  }
302
0
               }
303
0
               else {
304
                  // up-out and put 
305
0
                  if (strike >= barrier) {
306
                     // B2-B4 (eta=-1, phi=-1)
307
0
                     alpha = cum_x2 - H_S_2mu * cum_y2;
308
0
                  } else {
309
                     // B1-B3 (eta=-1, phi=-1)
310
0
                     alpha = cum_x1 - H_S_2mu * cum_y1;
311
0
                  }
312
0
               }
313
0
               break;
314
0
            default:
315
0
                QL_FAIL("invalid barrier type");
316
0
        }
317
318
0
        return discount * K * alpha;
319
0
    }
320
321
322
323
}
324