Coverage Report

Created: 2026-10-31 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/pricingengines/barrier/analyticpartialtimebarrieroptionengine.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2014 Master IMAFA - Polytech'Nice Sophia - Université de Nice Sophia Antipolis
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/math/functional.hpp>
22
#include <ql/pricingengines/barrier/analyticpartialtimebarrieroptionengine.hpp>
23
#include <ql/math/distributions/bivariatenormaldistribution.hpp>
24
#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
25
#include <utility>
26
27
namespace QuantLib {
28
29
    AnalyticPartialTimeBarrierOptionEngine::AnalyticPartialTimeBarrierOptionEngine(
30
        ext::shared_ptr<GeneralizedBlackScholesProcess> process)
31
0
    : process_(std::move(process)) {
32
0
        registerWith(process_);
33
0
    }
34
35
    Real AnalyticPartialTimeBarrierOptionEngine::calculate(PartialTimeBarrierOption::arguments& arguments,
36
                                                          const ext::shared_ptr<PlainVanillaPayoff>& payoff,
37
0
                                                          const ext::shared_ptr<GeneralizedBlackScholesProcess>& process) const {
38
0
        Barrier::Type barrierType = arguments.barrierType;
39
0
        PartialBarrier::Range barrierRange = arguments.barrierRange;
40
0
        Rate r = process->riskFreeRate()->zeroRate(residualTime(), Continuous,
41
0
                                                  NoFrequency);
42
0
        Rate q = process->dividendYield()->zeroRate(residualTime(), Continuous,
43
0
                                                  NoFrequency);
44
0
        Real barrier = arguments.barrier;
45
0
        Real strike = payoff->strike();
46
        
47
0
        switch (barrierType) {
48
0
          case Barrier::DownOut:
49
0
            switch (barrierRange) {
50
0
              case PartialBarrier::Start:
51
0
                return CA(1, barrier, strike, r, q);
52
0
              case PartialBarrier::EndB1:
53
0
                return CoB1(barrier, strike, r, q);
54
0
              case PartialBarrier::EndB2:
55
0
                return CoB2(Barrier::DownOut, barrier, strike, r, q);
56
0
              default:
57
0
                QL_FAIL("invalid barrier range");
58
0
            }
59
0
            break;
60
61
0
          case Barrier::DownIn:
62
0
            switch (barrierRange) {
63
0
              case PartialBarrier::Start:
64
0
                return CIA(1, barrier, strike, r, q, payoff, process);
65
0
              case PartialBarrier::EndB1:
66
0
              case PartialBarrier::EndB2:
67
0
                QL_FAIL("Down-and-in partial-time end barrier is not implemented");
68
0
              default:
69
0
                QL_FAIL("invalid barrier range");
70
0
            }
71
0
            break;
72
73
0
          case Barrier::UpOut:
74
0
            switch (barrierRange) {
75
0
              case PartialBarrier::Start:
76
0
                return CA(-1, barrier, strike, r, q);
77
0
              case PartialBarrier::EndB1:
78
0
                return CoB1(barrier, strike, r, q);
79
0
              case PartialBarrier::EndB2:
80
0
                return CoB2(Barrier::UpOut, barrier, strike, r, q);
81
0
              default:
82
0
                QL_FAIL("invalid barrier range");
83
0
            }
84
0
            break;
85
86
0
            case Barrier::UpIn:
87
0
              switch (barrierRange) {
88
0
                case PartialBarrier::Start:
89
0
                  return CIA(-1, barrier, strike, r, q, payoff, process);
90
0
                case PartialBarrier::EndB1:
91
0
                case PartialBarrier::EndB2:
92
0
                  QL_FAIL("Up-and-in partial-time end barrier is not implemented");
93
0
                default:
94
0
                  QL_FAIL("invalid barrier range");
95
0
              }
96
0
              break;
97
0
            default:
98
0
              QL_FAIL("unknown barrier type");
99
0
          }
100
0
    }
101
102
0
    void AnalyticPartialTimeBarrierOptionEngine::calculate() const {
103
0
        ext::shared_ptr<PlainVanillaPayoff> payoff =
104
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
105
0
        QL_REQUIRE(payoff, "non-plain payoff given");
106
0
        QL_REQUIRE(payoff->strike()>0.0,
107
0
                   "strike must be positive");
108
109
0
        Real spot = process_->x0();
110
0
        QL_REQUIRE(spot > 0.0, "negative or null underlying given");
111
112
0
        auto getSymmetricBarrierType = [](Barrier::Type barrierType) -> Barrier::Type {
113
0
          if (barrierType == Barrier::UpIn) return Barrier::DownIn;
114
0
          if (barrierType == Barrier::DownIn) return Barrier::UpIn;
115
0
          if (barrierType == Barrier::UpOut) return Barrier::DownOut;
116
0
          return Barrier::UpOut;
117
0
        };
118
119
0
        auto tmp_arguments_ = arguments_;
120
0
        if (payoff->optionType() == Option::Put)
121
0
        {
122
0
          Real spotSq = spot * spot;
123
0
          Real callStrike = spotSq / payoff->strike();
124
0
          ext::shared_ptr<PlainVanillaPayoff> callPayoff =
125
0
            ext::make_shared<PlainVanillaPayoff>(Option::Call, callStrike);
126
0
          tmp_arguments_.barrierType = getSymmetricBarrierType(arguments_.barrierType);
127
0
          tmp_arguments_.barrier = spotSq / arguments_.barrier;
128
0
          tmp_arguments_.payoff = callPayoff;
129
0
          auto callProcess = ext::make_shared<GeneralizedBlackScholesProcess>(
130
0
              process_->stateVariable(),
131
0
              process_->riskFreeRate(),
132
0
              process_->dividendYield(),
133
0
              process_->blackVolatility()
134
0
            );
135
136
0
          results_.value = payoff->strike() / spot * calculate(tmp_arguments_, callPayoff, callProcess);
137
0
        } else
138
0
          results_.value = calculate(tmp_arguments_, payoff, process_);
139
0
    }
140
141
    Real AnalyticPartialTimeBarrierOptionEngine::CoB2(
142
                                      Barrier::Type barrierType, 
143
0
                                      Real barrier, Real strike, Rate r, Rate q) const {
144
0
        Real result = 0.0;
145
0
        Real b = r - q;
146
0
        Real T = residualTime();
147
0
        Real S = underlying();
148
0
        Real mu_ = mu(strike, b);
149
0
        Real g1_ = g1(barrier, strike, b);
150
0
        Real g2_ = g2(barrier, strike, b);
151
0
        Real g3_ = g3(barrier, strike, b);
152
0
        Real g4_ = g4(barrier, strike, b);
153
0
        Real e1_ = e1(barrier, strike, b);
154
0
        Real e2_ = e2(barrier, strike, b);
155
0
        Real e3_ = e3(barrier, strike, b);
156
0
        Real e4_ = e4(barrier, strike, b);
157
0
        Real rho_ = rho();
158
0
        Real HSMu = HS(S, barrier, 2 * mu_);
159
0
        Real HSMu1 = HS(S, barrier, 2 * (mu_ + 1));
160
0
        Real X1 = strike * std::exp(-r * T);
161
162
0
        if (strike < barrier){
163
0
            switch (barrierType) {
164
0
              case Barrier::DownOut:
165
0
                result = S * std::exp((b - r) * T);
166
0
                result *= (M(g1_, e1_, rho_) - HSMu1 * M(g3_, -e3_, -rho_));
167
0
                result -= X1 * (M(g2_, e2_, rho_)-HSMu*M(g4_, -e4_, -rho_));
168
0
                return result;
169
170
0
              case Barrier::UpOut:
171
0
                result = S * std::exp((b - r) * T);
172
0
                result *= (M(-g1_, -e1_, rho_) - HSMu1 * M(-g3_, e3_, -rho_));
173
0
                result -= X1 * (M(-g2_, -e2_, rho_) - HSMu * M(-g4_, e4_, -rho_));
174
0
                result -= S * std::exp((b - r) * T) * 
175
0
                          (M(-d1(strike, b), -e1_, rho_) - HSMu1 * 
176
0
                          M(e3_, -f1(barrier, strike, b),-rho_));
177
0
                result += X1 * (M(-d2(strike, b), -e2_, rho_) - HSMu * 
178
0
                          M(e4_, -f2(barrier, strike, b), -rho_));
179
0
                return result;
180
181
0
              default:
182
0
                QL_FAIL("invalid barrier type");
183
0
            }
184
0
        } else {
185
0
            QL_FAIL("case of strike>barrier is not implemented for OutEnd B2 type");
186
0
        }
187
0
    }
188
189
0
    Real AnalyticPartialTimeBarrierOptionEngine::CoB1(Real barrier, Real strike, Rate r, Rate q) const {
190
0
        Real result = 0.0;
191
0
        Rate b = r - q;
192
0
        Real T = residualTime();
193
0
        Real S = underlying();
194
0
        Real mu_ = mu(strike, b);
195
0
        Real g1_ = g1(barrier, strike, b);
196
0
        Real g2_ = g2(barrier, strike, b);
197
0
        Real g3_ = g3(barrier, strike, b);
198
0
        Real g4_ = g4(barrier, strike, b);
199
0
        Real e1_ = e1(barrier, strike, b);
200
0
        Real e2_ = e2(barrier, strike, b);
201
0
        Real e3_ = e3(barrier, strike, b);
202
0
        Real e4_ = e4(barrier, strike, b);
203
0
        Real rho_ = rho();
204
0
        Real HSMu = HS(S, barrier, 2 * mu_);
205
0
        Real HSMu1 = HS(S, barrier, 2 * (mu_ + 1));
206
0
        Real X1 = strike * std::exp(-r * T);
207
208
0
        if (strike > barrier) {
209
0
            result = S * std::exp((b - r) * T);
210
0
            result *= (M(d1(strike, b), e1_, rho_) - HSMu1 * M(f1(barrier, strike, b), -e3_, -rho_));
211
0
            result -= X1 * (M(d2(strike, b), e2_, rho_) - HSMu * M(f2(barrier, strike, b), -e4_, -rho_));
212
0
            return result;
213
0
        } else {
214
0
            Real S1 = S * std::exp((b - r) * T);
215
0
            result = S1;
216
0
            result *= (M(-g1_, -e1_, rho_) - HSMu1 * M(-g3_,e3_,-rho_));
217
0
            result -= X1 * (M(-g2_, -e2_, rho_) - HSMu * M(-g4_, e4_, -rho_));
218
0
            result -= S1 * (M(-d1(strike, b), -e1_, rho_) - HSMu1 * M(-f1(barrier, strike, b), e3_, -rho_));
219
0
            result += X1 * (M(-d2(strike, b), -e2_, rho_) - HSMu * M(-f2(barrier, strike, b), e4_, -rho_));
220
0
            result += S1 * (M(g1_, e1_, rho_) - HSMu1 * M(g3_, -e3_, -rho_));
221
0
            result -= X1 * (M(g2_, e2_, rho_) - HSMu * M(g4_, -e4_, -rho_));
222
0
            return result;
223
0
        }
224
0
    }
225
226
    // eta = -1: Up-and-In Call
227
    // eta =  1: Down-and-In Call
228
    Real AnalyticPartialTimeBarrierOptionEngine::CIA(
229
        Integer eta, Real barrier, Real strike, Rate r, Rate q,
230
        const ext::shared_ptr<PlainVanillaPayoff>& payoff,
231
0
        const ext::shared_ptr<GeneralizedBlackScholesProcess>& process) const {
232
0
        ext::shared_ptr<EuropeanExercise> exercise =
233
0
            ext::dynamic_pointer_cast<EuropeanExercise>(arguments_.exercise);
234
235
0
        VanillaOption europeanOption(payoff, exercise);
236
237
0
        europeanOption.setPricingEngine(
238
0
                        ext::make_shared<AnalyticEuropeanEngine>(process));
239
240
0
        return europeanOption.NPV() - CA(eta, barrier, strike, r, q);
241
0
    }
242
243
0
    Real AnalyticPartialTimeBarrierOptionEngine::CA(Integer eta, Real barrier, Real strike, Rate r, Rate q) const {
244
        //Partial-Time-Start- OUT  Call Option calculation
245
0
        Real b = r - q;
246
0
        Real rho_ = rho();
247
0
        Real T = residualTime();
248
0
        Real S = underlying();
249
0
        Real mu_ = mu(strike, b);
250
0
        Real e1_ = e1(barrier, strike, b);
251
0
        Real e2_ = e2(barrier, strike, b);
252
0
        Real e3_ = e3(barrier, strike, b);
253
0
        Real e4_ = e4(barrier, strike, b);
254
0
        Real HSMu = HS(S, barrier,2 * mu_);
255
0
        Real HSMu1 = HS(S, barrier, 2 * (mu_ + 1));
256
257
0
        Real result;
258
0
        result = S * std::exp((b - r) * T);
259
0
        result *= (M(d1(strike, b), eta * e1_, eta * rho_)-HSMu1 * 
260
0
                  M(f1(barrier, strike, b), eta * e3_, eta * rho_));
261
0
        result -= (strike * std::exp(-r * T) * 
262
0
                  (M(d2(strike, b),eta * e2_, eta * rho_) - HSMu *
263
0
                  M(f2(barrier, strike, b), eta * e4_, eta * rho_)));
264
0
        return result;
265
0
    }
266
267
0
    Real AnalyticPartialTimeBarrierOptionEngine::underlying() const {
268
0
        return process_->x0();
269
0
    }
270
271
0
    Time AnalyticPartialTimeBarrierOptionEngine::residualTime() const {
272
0
        return process_->time(arguments_.exercise->lastDate());
273
0
    }
274
275
0
    Time AnalyticPartialTimeBarrierOptionEngine::coverEventTime() const {
276
0
        return process_->time(arguments_.coverEventDate);
277
0
    }
278
279
0
    Volatility AnalyticPartialTimeBarrierOptionEngine::volatility(Time t, Real strike) const {
280
0
        return process_->blackVolatility()->blackVol(t, strike);
281
0
    }
282
283
0
    Real AnalyticPartialTimeBarrierOptionEngine::f1(Real barrier, Real strike, Rate b) const {
284
0
        Real S = underlying();
285
0
        Real T = residualTime();
286
0
        Real sigma = volatility(T, strike);
287
0
        return (std::log(S / strike) + 2 * std::log(barrier / S) + 
288
0
              (b + (squared(sigma) / 2))*T) / (sigma*std::sqrt(T));
289
0
    }
290
291
0
    Real AnalyticPartialTimeBarrierOptionEngine::f2(Real barrier, Real strike, Rate b) const {
292
0
        Time T = residualTime();
293
0
        return f1(barrier, strike, b) - volatility(T, strike) * std::sqrt(T);
294
0
    }
295
296
0
    Real AnalyticPartialTimeBarrierOptionEngine::M(Real a, Real b, Real rho) const {
297
0
        BivariateCumulativeNormalDistributionDr78 CmlNormDist(rho);
298
0
        return CmlNormDist(a,b);
299
0
    }
300
301
0
    Real AnalyticPartialTimeBarrierOptionEngine::rho() const {
302
0
        return std::sqrt(coverEventTime() / residualTime());
303
0
    }
304
305
0
    Rate AnalyticPartialTimeBarrierOptionEngine::mu(Real strike, Rate b) const {
306
0
        Volatility vol = volatility(coverEventTime(), strike);
307
0
        return (b - (vol * vol) / 2) / (vol * vol);
308
0
    }
309
310
0
    Real AnalyticPartialTimeBarrierOptionEngine::d1(Real strike, Rate b) const {
311
0
        Time T2 = residualTime();
312
0
        Volatility vol = volatility(T2, strike);
313
0
        return (std::log(underlying() / strike) + (b + vol * vol / 2) * T2) / (std::sqrt(T2) * vol);
314
0
    }
315
316
0
    Real AnalyticPartialTimeBarrierOptionEngine::d2(Real strike, Rate b) const {
317
0
        Time T2 = residualTime();
318
0
        Volatility vol = volatility(T2, strike);
319
0
        return d1(strike, b) - vol * std::sqrt(T2);
320
0
    }
321
322
0
    Real AnalyticPartialTimeBarrierOptionEngine::e1(Real barrier, Real strike, Rate b) const {
323
0
        Time T1 = coverEventTime();
324
0
        Volatility vol = volatility(T1, strike);
325
0
        return (std::log(underlying() / barrier) + (b + vol * vol / 2) * T1) / (std::sqrt(T1) * vol);
326
0
    }
327
328
0
    Real AnalyticPartialTimeBarrierOptionEngine::e2(Real barrier, Real strike, Rate b) const {
329
0
        Time T1 = coverEventTime();
330
0
        Volatility vol = volatility(T1, strike);
331
0
        return e1(barrier, strike, b) - vol * std::sqrt(T1);
332
0
    }
333
334
0
    Real AnalyticPartialTimeBarrierOptionEngine::e3(Real barrier, Real strike, Rate b) const {
335
0
        Time T1 = coverEventTime();
336
0
        Real vol = volatility(T1, strike);
337
0
        return e1(barrier, strike, b) + (2 * std::log(barrier / underlying()) / (vol * std::sqrt(T1)));
338
0
    }
339
340
0
    Real AnalyticPartialTimeBarrierOptionEngine::e4(Real barrier, Real strike, Rate b) const {
341
0
        Time t = coverEventTime();
342
0
        return e3(barrier, strike, b) - volatility(t, strike) * std::sqrt(t);
343
0
    }
344
345
0
    Real AnalyticPartialTimeBarrierOptionEngine::g1(Real barrier, Real strike, Rate b) const {
346
0
        Time T2 = residualTime();
347
0
        Volatility vol = volatility(T2, strike);
348
0
        return (std::log(underlying() / barrier) + (b + vol * vol / 2) * T2) / (std::sqrt(T2) * vol);
349
0
    }
350
351
0
    Real AnalyticPartialTimeBarrierOptionEngine::g2(Real barrier, Real strike, Rate b) const {
352
0
        Time T2 = residualTime();
353
0
        Volatility vol = volatility(T2, strike);
354
0
        return g1(barrier, strike, b) - vol * std::sqrt(T2);
355
0
    }
356
357
0
    Real AnalyticPartialTimeBarrierOptionEngine::g3(Real barrier, Real strike, Rate b) const {
358
0
        Time T2 = residualTime();
359
0
        Real vol = volatility(T2, strike);
360
0
        return g1(barrier, strike, b) + (2 * std::log(barrier / underlying()) /(vol * std::sqrt(T2)));
361
0
    }
362
363
0
    Real AnalyticPartialTimeBarrierOptionEngine::g4(Real barrier, Real strike, Rate b) const {
364
0
        Time T2 = residualTime();
365
0
        Real vol = volatility(T2, strike);
366
0
        return g3(barrier, strike, b) - vol * std::sqrt(T2);
367
0
    }
368
369
0
    Real AnalyticPartialTimeBarrierOptionEngine::HS(Real S, Real H, Real power) const {
370
0
        return std::pow((H / S), power);
371
0
    }
372
373
}
374