Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/pricingengines/barrier/analyticdoublebarrierbinaryengine.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) 2015 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/pricingengines/barrier/analyticdoublebarrierbinaryengine.hpp>
22
#include <utility>
23
24
using std::fabs;
25
26
namespace QuantLib {
27
28
   // number of iterations ...
29
   static Real PI= 3.14159265358979323846264338327950;
30
31
    // calc helper object 
32
    class AnalyticDoubleBarrierBinaryEngine_helper
33
    {
34
    
35
    public:
36
        AnalyticDoubleBarrierBinaryEngine_helper(
37
             const ext::shared_ptr<GeneralizedBlackScholesProcess>& process,
38
             const ext::shared_ptr<CashOrNothingPayoff> &payoff,
39
             const DoubleBarrierOption::arguments &arguments):
40
0
        process_(process),
41
0
        payoff_(payoff),
42
0
        arguments_(arguments)
43
0
        {
44
0
        }
45
46
        Real payoffAtExpiry(Real spot, Real variance,
47
                            DoubleBarrier::Type barrierType,
48
                            Size maxIteration = 100,
49
                            Real requiredConvergence = 1e-8);
50
        Real payoffKIKO(Real spot, Real variance,
51
                        DoubleBarrier::Type barrierType,
52
                        Size maxIteration = 1000,
53
                        Real requiredConvergence = 1e-8);
54
55
    private:
56
57
        const ext::shared_ptr<GeneralizedBlackScholesProcess>& process_;
58
        const ext::shared_ptr<CashOrNothingPayoff> &payoff_;
59
        const DoubleBarrierOption::arguments &arguments_;
60
    };
61
62
63
    // helper object methods
64
    Real AnalyticDoubleBarrierBinaryEngine_helper::payoffAtExpiry(
65
         Real spot, Real variance, DoubleBarrier::Type barrierType,
66
         Size maxIteration, Real requiredConvergence)
67
0
    {
68
0
        QL_REQUIRE(spot>0.0,
69
0
                   "positive spot value required");
70
71
0
        QL_REQUIRE(variance>=0.0,
72
0
                   "negative variance not allowed");
73
74
0
        Time residualTime = process_->time(arguments_.exercise->lastDate());
75
0
        QL_REQUIRE(residualTime>0.0,
76
0
                   "expiration time must be > 0");
77
78
        // Option::Type type   = payoff_->optionType(); // this is not used ?
79
0
        Real cash = payoff_->cashPayoff();
80
0
        Real barrier_lo = arguments_.barrier_lo;
81
0
        Real barrier_hi = arguments_.barrier_hi;
82
83
0
        Real sigmaq = variance/residualTime;
84
0
        Real r = process_->riskFreeRate()->zeroRate(residualTime, Continuous,
85
0
                                             NoFrequency);
86
0
        Real q = process_->dividendYield()->zeroRate(residualTime,
87
0
                                                   Continuous, NoFrequency);
88
0
        Real b = r - q;
89
90
0
        Real alpha = -0.5 * ( 2*b/sigmaq - 1);
91
0
        Real beta = -0.25 * std::pow(( 2*b/sigmaq - 1), 2) - 2 * r/sigmaq;
92
0
        Real Z = std::log(barrier_hi / barrier_lo);
93
0
        Real factor = ((2*PI*cash)/std::pow(Z,2)); // common factor
94
0
        Real lo_alpha = std::pow(spot/barrier_lo, alpha); 
95
0
        Real hi_alpha = std::pow(spot/barrier_hi, alpha); 
96
97
0
        Real tot = 0, term = 0;
98
0
        for (Size i = 1 ; i < maxIteration ; ++i)
99
0
        {
100
0
           Real term1 = (lo_alpha-std::pow(-1.0, (int)i)*hi_alpha) /
101
0
                              (std::pow(alpha,2)+std::pow(i*PI/Z, 2));
102
0
           Real term2 = std::sin(i*PI/Z * std::log(spot/barrier_lo));
103
0
           Real term3 = std::exp(-0.5*(std::pow(i*PI/Z,2)-beta)*variance);
104
0
           term = factor * i * term1 * term2 * term3;
105
0
           tot += term;
106
0
        }
107
108
        // Check if convergence is sufficiently fast (for extreme parameters with big alpha the convergence can be very
109
        // poor, see for example Hui "One-touch double barrier binary option value")
110
0
        QL_REQUIRE(std::fabs(term) < requiredConvergence, "serie did not converge sufficiently fast");
111
112
0
        if (barrierType == DoubleBarrier::KnockOut)
113
0
           return std::max(tot, 0.0); // KO
114
0
        else {
115
0
           Rate discount = process_->riskFreeRate()->discount(
116
0
                                             arguments_.exercise->lastDate());
117
0
           QL_REQUIRE(discount>0.0,
118
0
                        "positive discount required");
119
0
           return std::max(cash * discount - tot, 0.0); // KI
120
0
        }
121
0
    }
122
123
    // helper object methods
124
    Real AnalyticDoubleBarrierBinaryEngine_helper::payoffKIKO(
125
         Real spot, Real variance, DoubleBarrier::Type barrierType,
126
         Size maxIteration, Real requiredConvergence)
127
0
    {
128
0
        QL_REQUIRE(spot>0.0,
129
0
                   "positive spot value required");
130
131
0
        QL_REQUIRE(variance>=0.0,
132
0
                   "negative variance not allowed");
133
134
0
        Time residualTime = process_->time(arguments_.exercise->lastDate());
135
0
        QL_REQUIRE(residualTime>0.0,
136
0
                   "expiration time must be > 0");
137
138
0
        Real cash = payoff_->cashPayoff();
139
0
        Real barrier_lo = arguments_.barrier_lo;
140
0
        Real barrier_hi = arguments_.barrier_hi;
141
0
        if (barrierType == DoubleBarrier::KOKI)
142
0
           std::swap(barrier_lo, barrier_hi);
143
144
0
        Real sigmaq = variance/residualTime;
145
0
        Real r = process_->riskFreeRate()->zeroRate(residualTime, Continuous,
146
0
                                             NoFrequency);
147
0
        Real q = process_->dividendYield()->zeroRate(residualTime,
148
0
                                                   Continuous, NoFrequency);
149
0
        Real b = r - q;
150
151
0
        Real alpha = -0.5 * ( 2*b/sigmaq - 1);
152
0
        Real beta = -0.25 * std::pow(( 2*b/sigmaq - 1), 2) - 2 * r/sigmaq;
153
0
        Real Z = std::log(barrier_hi / barrier_lo);
154
0
        Real log_S_L = std::log(spot / barrier_lo);
155
156
0
        Real tot = 0, term = 0;
157
0
        for (Size i = 1 ; i < maxIteration ; ++i)
158
0
        {
159
0
            Real factor = std::pow(i*PI/Z,2)-beta;
160
0
            Real term1 = (beta - std::pow(i*PI/Z,2) * std::exp(-0.5*factor*variance)) / factor;
161
0
            Real term2 = std::sin(i * PI/Z * log_S_L);
162
0
            term = (2.0/(i*PI)) * term1 * term2;
163
0
            tot += term;
164
0
        }
165
0
        tot += 1 - log_S_L / Z;
166
0
        tot *= cash*std::pow(spot/barrier_lo, alpha);
167
168
        // Check if convergence is sufficiently fast
169
0
        QL_REQUIRE(fabs(term) < requiredConvergence, "serie did not converge sufficiently fast");
170
171
0
        return std::max(tot, 0.0);
172
0
    }
173
174
    AnalyticDoubleBarrierBinaryEngine::AnalyticDoubleBarrierBinaryEngine(
175
        ext::shared_ptr<GeneralizedBlackScholesProcess> process)
176
0
    : process_(std::move(process)) {
177
0
        registerWith(process_);
178
0
    }
179
180
0
    void AnalyticDoubleBarrierBinaryEngine::calculate() const {
181
182
0
        if (arguments_.barrierType == DoubleBarrier::KIKO ||
183
0
            arguments_.barrierType == DoubleBarrier::KOKI) {
184
0
            ext::shared_ptr<AmericanExercise> ex =
185
0
                ext::dynamic_pointer_cast<AmericanExercise>(
186
0
                                                   arguments_.exercise);
187
0
            QL_REQUIRE(ex, "KIKO/KOKI options must have American exercise");
188
0
            QL_REQUIRE(ex->dates()[0] <=
189
0
                       process_->blackVolatility()->referenceDate(),
190
0
                       "American option with window exercise not handled yet");
191
0
        } else {
192
0
            ext::shared_ptr<EuropeanExercise> ex =
193
0
                ext::dynamic_pointer_cast<EuropeanExercise>(
194
0
                                                   arguments_.exercise);
195
0
            QL_REQUIRE(ex, "non-European exercise given");
196
0
        }
197
0
        ext::shared_ptr<CashOrNothingPayoff> payoff =
198
0
            ext::dynamic_pointer_cast<CashOrNothingPayoff>(arguments_.payoff);
199
0
        QL_REQUIRE(payoff, "a cash-or-nothing payoff must be given");
200
201
0
        Real spot = process_->stateVariable()->value();
202
0
        QL_REQUIRE(spot > 0.0, "negative or null underlying given");
203
204
0
        Real variance =
205
0
            process_->blackVolatility()->blackVariance(
206
0
                                             arguments_.exercise->lastDate(),
207
0
                                             payoff->strike());
208
0
        Real barrier_lo = arguments_.barrier_lo;
209
0
        Real barrier_hi = arguments_.barrier_hi;
210
0
        DoubleBarrier::Type barrierType = arguments_.barrierType;
211
0
        QL_REQUIRE(barrier_lo>0.0,
212
0
                   "positive low barrier value required");
213
0
        QL_REQUIRE(barrier_hi>0.0,
214
0
                   "positive high barrier value required");
215
0
        QL_REQUIRE(barrier_lo < barrier_hi,
216
0
                   "barrier_lo must be < barrier_hi");
217
0
        QL_REQUIRE(barrierType == DoubleBarrier::KnockIn ||
218
0
                   barrierType == DoubleBarrier::KnockOut ||
219
0
                   barrierType == DoubleBarrier::KIKO ||
220
0
                   barrierType == DoubleBarrier::KOKI,
221
0
                   "Unsupported barrier type");
222
223
        // degenerate cases
224
0
        switch (barrierType) {
225
0
          case DoubleBarrier::KnockOut:
226
0
            if (spot <= barrier_lo || spot >= barrier_hi) {
227
                // knocked out, no value
228
0
                results_.value = 0;
229
0
                results_.delta = 0;
230
0
                results_.gamma = 0;
231
0
                results_.vega = 0;
232
0
                results_.rho = 0;
233
0
                return;
234
0
            }
235
0
            break;
236
237
0
          case DoubleBarrier::KnockIn:
238
0
            if (spot <= barrier_lo || spot >= barrier_hi) {
239
                // knocked in - pays
240
0
                results_.value = payoff->cashPayoff();
241
0
                results_.delta = 0;
242
0
                results_.gamma = 0;
243
0
                results_.vega = 0;
244
0
                results_.rho = 0;
245
0
                return;
246
0
            }
247
0
            break;
248
249
0
          case DoubleBarrier::KIKO:
250
0
            if (spot >= barrier_hi) {
251
                // knocked out, no value
252
0
                results_.value = 0;
253
0
                results_.delta = 0;
254
0
                results_.gamma = 0;
255
0
                results_.vega = 0;
256
0
                results_.rho = 0;
257
0
                return;
258
0
            } else if (spot <= barrier_lo) {
259
                // knocked in, pays
260
0
                results_.value = payoff->cashPayoff();
261
0
                results_.delta = 0;
262
0
                results_.gamma = 0;
263
0
                results_.vega = 0;
264
0
                results_.rho = 0;
265
0
                return;
266
0
            }
267
0
            break;
268
269
0
          case DoubleBarrier::KOKI:
270
0
            if (spot <= barrier_lo) {
271
                // knocked out, no value
272
0
                results_.value = 0;
273
0
                results_.delta = 0;
274
0
                results_.gamma = 0;
275
0
                results_.vega = 0;
276
0
                results_.rho = 0;
277
0
                return;
278
0
            } else if (spot >= barrier_hi) {
279
                // knocked in, pays
280
0
                results_.value = payoff->cashPayoff();
281
0
                results_.delta = 0;
282
0
                results_.gamma = 0;
283
0
                results_.vega = 0;
284
0
                results_.rho = 0;
285
0
                return;
286
0
            }
287
0
            break;
288
0
        }
289
290
0
        AnalyticDoubleBarrierBinaryEngine_helper helper(process_,
291
0
           payoff, arguments_);
292
0
        switch (barrierType)
293
0
        {
294
0
          case DoubleBarrier::KnockOut:
295
0
          case DoubleBarrier::KnockIn:
296
0
            results_.value = helper.payoffAtExpiry(spot, variance, barrierType);
297
0
            break;
298
299
0
          case DoubleBarrier::KIKO:
300
0
          case DoubleBarrier::KOKI:
301
0
            results_.value = helper.payoffKIKO(spot, variance, barrierType);
302
0
            break;
303
0
        }
304
0
    }
305
306
}
307