Coverage Report

Created: 2026-03-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/pricingengines/barrier/analyticdoublebarrierengine.cpp
Line
Count
Source
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
 <https://www.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/analyticdoublebarrierengine.hpp>
22
#include <ql/pricingengines/blackcalculator.hpp>
23
#include <utility>
24
25
namespace QuantLib {
26
27
    AnalyticDoubleBarrierEngine::AnalyticDoubleBarrierEngine(
28
        ext::shared_ptr<GeneralizedBlackScholesProcess> process, int series)
29
0
    : process_(std::move(process)), series_(series) {
30
0
        registerWith(process_);
31
0
    }
32
33
0
    void AnalyticDoubleBarrierEngine::calculate() const {
34
35
0
        QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
36
0
                   "this engine handles only european options");
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
42
0
        Real strike = payoff->strike();
43
0
        QL_REQUIRE(strike>0.0,
44
0
                   "strike must be positive");
45
46
0
        Real spot = underlying();
47
0
        QL_REQUIRE(spot > 0.0, "negative or null underlying given");
48
0
        QL_REQUIRE(!triggered(spot), "barrier(s) already touched");
49
50
0
        DoubleBarrier::Type barrierType = arguments_.barrierType;
51
52
0
        if (triggered(spot)) {
53
0
           if (barrierType == DoubleBarrier::KnockIn)
54
0
               results_.value = vanillaEquivalent();  // knocked in
55
0
           else
56
0
               results_.value = 0.0;  // knocked out
57
0
        } else {
58
0
           switch (payoff->optionType()) {
59
0
             case Option::Call:
60
0
               switch (barrierType) {
61
0
                 case DoubleBarrier::KnockIn:
62
0
                   results_.value = callKI();
63
0
                   break;
64
0
                 case DoubleBarrier::KnockOut:
65
0
                   results_.value = callKO();
66
0
                   break;
67
0
                 case DoubleBarrier::KIKO:
68
0
                 case DoubleBarrier::KOKI:
69
0
                   QL_FAIL("unsupported double-barrier type: "
70
0
                           << barrierType);
71
0
                 default:
72
0
                   QL_FAIL("unknown double-barrier type: "
73
0
                           << barrierType);
74
0
               }
75
0
               break;
76
0
             case Option::Put:
77
0
               switch (barrierType) {
78
0
                 case DoubleBarrier::KnockIn:
79
0
                   results_.value = putKI();
80
0
                   break;
81
0
                 case DoubleBarrier::KnockOut:
82
0
                   results_.value = putKO();
83
0
                   break;
84
0
                 case DoubleBarrier::KIKO:
85
0
                 case DoubleBarrier::KOKI:
86
0
                   QL_FAIL("unsupported double-barrier type: "
87
0
                           << barrierType);
88
0
                 default:
89
0
                   QL_FAIL("unknown double-barrier type: "
90
0
                           << barrierType);
91
0
               }
92
0
               break;
93
0
             default:
94
0
               QL_FAIL("unknown type");
95
0
           }
96
0
        }
97
0
    }
98
99
100
0
    Real AnalyticDoubleBarrierEngine::underlying() const {
101
0
        return process_->x0();
102
0
    }
103
104
0
    Real AnalyticDoubleBarrierEngine::strike() const {
105
0
        ext::shared_ptr<PlainVanillaPayoff> payoff =
106
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
107
0
        QL_REQUIRE(payoff, "non-plain payoff given");
108
0
        return payoff->strike();
109
0
    }
110
111
0
    Time AnalyticDoubleBarrierEngine::residualTime() const {
112
0
        return process_->time(arguments_.exercise->lastDate());
113
0
    }
114
115
0
    Volatility AnalyticDoubleBarrierEngine::volatility() const {
116
0
        return process_->blackVolatility()->blackVol(residualTime(), strike());
117
0
    }
118
119
0
    Real AnalyticDoubleBarrierEngine::volatilitySquared() const {
120
0
        return volatility() * volatility();
121
0
    }
122
123
0
    Real AnalyticDoubleBarrierEngine::stdDeviation() const {
124
0
        return volatility() * std::sqrt(residualTime());
125
0
    }
126
127
0
    Real AnalyticDoubleBarrierEngine::barrierLo() const {
128
0
        return arguments_.barrier_lo;
129
0
    }
130
131
0
    Real AnalyticDoubleBarrierEngine::barrierHi() const {
132
0
        return arguments_.barrier_hi;
133
0
    }
134
135
0
    Rate AnalyticDoubleBarrierEngine::riskFreeRate() const {
136
0
        return process_->riskFreeRate()->zeroRate(residualTime(), Continuous,
137
0
                                                  NoFrequency);
138
0
    }
139
140
0
    DiscountFactor AnalyticDoubleBarrierEngine::riskFreeDiscount() const {
141
0
        return process_->riskFreeRate()->discount(residualTime());
142
0
    }
143
144
0
    Rate AnalyticDoubleBarrierEngine::dividendYield() const {
145
0
        return process_->dividendYield()->zeroRate(residualTime(),
146
0
                                                   Continuous, NoFrequency);
147
0
    }
148
149
0
    DiscountFactor AnalyticDoubleBarrierEngine::dividendDiscount() const {
150
0
        return process_->dividendYield()->discount(residualTime());
151
0
    }
152
153
0
    Rate AnalyticDoubleBarrierEngine::costOfCarry() const {
154
0
        return riskFreeRate() - dividendYield();
155
0
    }
156
157
0
    Real AnalyticDoubleBarrierEngine::vanillaEquivalent() const {
158
        // Call KI equates to vanilla - callKO
159
0
        ext::shared_ptr<StrikedTypePayoff> payoff =
160
0
            ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
161
0
        Real forwardPrice = underlying() * dividendDiscount() / riskFreeDiscount();
162
0
        BlackCalculator black(payoff, forwardPrice, stdDeviation(), riskFreeDiscount());
163
0
        Real vanilla = black.value();
164
0
        if (vanilla < 0.0)
165
0
           vanilla = 0.0;
166
0
        return vanilla;
167
0
    }
168
169
0
    Real AnalyticDoubleBarrierEngine::callKO() const {
170
       // N.B. for flat barriers mu3=mu1 and mu2=0
171
0
       Real mu1 = 2 * costOfCarry() / volatilitySquared() + 1;
172
0
       Real bsigma = (costOfCarry() + volatilitySquared() / 2.0) * residualTime() / stdDeviation();
173
174
0
       Real acc1 = 0;
175
0
       Real acc2 = 0;
176
0
       for (int n = -series_ ; n <= series_ ; ++n) {
177
0
          Real L2n = std::pow(barrierLo(), 2 * n);
178
0
          Real U2n = std::pow(barrierHi(), 2 * n);
179
0
          Real d1 = std::log( underlying()* U2n / (strike() * L2n) ) / stdDeviation() + bsigma;
180
0
          Real d2 = std::log( underlying()* U2n / (barrierHi() * L2n) ) / stdDeviation() + bsigma;
181
0
          Real d3 = std::log( std::pow(barrierLo(), 2 * n + 2) / (strike() * underlying() * U2n) ) / stdDeviation() + bsigma;
182
0
          Real d4 = std::log( std::pow(barrierLo(), 2 * n + 2) / (barrierHi() * underlying() * U2n) ) / stdDeviation() + bsigma;
183
184
0
          acc1 += std::pow( std::pow(barrierHi(), n) / std::pow(barrierLo(), n), mu1 ) * 
185
0
                  (f_(d1) - f_(d2)) -
186
0
                  std::pow( std::pow(barrierLo(), n+1) / (std::pow(barrierHi(), n) * underlying()), mu1 ) * 
187
0
                  (f_(d3) - f_(d4));
188
189
0
          acc2 += std::pow( std::pow(barrierHi(), n) / std::pow(barrierLo(), n), mu1-2) * 
190
0
                  (f_(d1 - stdDeviation()) - f_(d2 - stdDeviation())) -
191
0
                  std::pow( std::pow(barrierLo(), n+1) / (std::pow(barrierHi(), n) * underlying()), mu1-2 ) * 
192
0
                  (f_(d3-stdDeviation()) - f_(d4-stdDeviation()));
193
0
       }
194
195
0
       Real rend = std::exp(-dividendYield() * residualTime());
196
0
       Real kov = underlying() * rend * acc1 - strike() * riskFreeDiscount() * acc2;
197
0
       return std::max(0.0, kov);
198
0
    }
199
    
200
0
    Real AnalyticDoubleBarrierEngine::callKI() const {
201
        // Call KI equates to vanilla - callKO
202
0
        return std::max(0.0, vanillaEquivalent() - callKO());
203
0
    }
204
205
0
    Real AnalyticDoubleBarrierEngine::putKO() const {
206
0
       Real mu1 = 2 * costOfCarry() / volatilitySquared() + 1;
207
0
       Real bsigma = (costOfCarry() + volatilitySquared() / 2.0) * residualTime() / stdDeviation();
208
209
0
       Real acc1 = 0;
210
0
       Real acc2 = 0;
211
0
       for (int n = -series_ ; n <= series_ ; ++n) {
212
0
          Real L2n = std::pow(barrierLo(), 2 * n);
213
0
          Real U2n = std::pow(barrierHi(), 2 * n);
214
0
          Real y1 = std::log( underlying()* U2n / (std::pow(barrierLo(), 2 * n + 1)) ) / stdDeviation() + bsigma;
215
0
          Real y2 = std::log( underlying()* U2n / (strike() * L2n) ) / stdDeviation() + bsigma;
216
0
          Real y3 = std::log( std::pow(barrierLo(), 2 * n + 2) / (barrierLo() * underlying() * U2n) ) / stdDeviation() + bsigma;
217
0
          Real y4 = std::log( std::pow(barrierLo(), 2 * n + 2) / (strike() * underlying() * U2n) ) / stdDeviation() + bsigma;
218
219
0
          acc1 += std::pow( std::pow(barrierHi(), n) / std::pow(barrierLo(), n), mu1-2) * 
220
0
                  (f_(y1 - stdDeviation()) - f_(y2 - stdDeviation())) -
221
0
                  std::pow( std::pow(barrierLo(), n+1) / (std::pow(barrierHi(), n) * underlying()), mu1-2 ) * 
222
0
                  (f_(y3-stdDeviation()) - f_(y4-stdDeviation()));
223
224
0
          acc2 += std::pow( std::pow(barrierHi(), n) / std::pow(barrierLo(), n), mu1 ) * 
225
0
                  (f_(y1) - f_(y2)) -
226
0
                  std::pow( std::pow(barrierLo(), n+1) / (std::pow(barrierHi(), n) * underlying()), mu1 ) * 
227
0
                  (f_(y3) - f_(y4));
228
229
0
       }
230
231
0
       Real rend = std::exp(-dividendYield() * residualTime());
232
0
       Real kov = strike() * riskFreeDiscount() * acc1 - underlying() * rend  * acc2;
233
0
       return std::max(0.0, kov);
234
0
    }
235
    
236
0
    Real AnalyticDoubleBarrierEngine::putKI() const {
237
        // Put KI equates to vanilla - putKO
238
0
        return std::max(0.0, vanillaEquivalent() - putKO());
239
0
    }
240
241
    
242
}
243