Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/pricingengines/exotic/analyticholderextensibleoptionengine.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 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
 <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/exotic/analyticholderextensibleoptionengine.hpp>
22
#include <ql/math/distributions/bivariatenormaldistribution.hpp>
23
#include <utility>
24
25
using std::pow;
26
using std::log;
27
using std::exp;
28
using std::sqrt;
29
30
namespace QuantLib {
31
32
    AnalyticHolderExtensibleOptionEngine::AnalyticHolderExtensibleOptionEngine(
33
        ext::shared_ptr<GeneralizedBlackScholesProcess> process)
34
0
    : process_(std::move(process)) {
35
0
        registerWith(process_);
36
0
    }
37
38
0
    void AnalyticHolderExtensibleOptionEngine::calculate() const {
39
        //Spot
40
0
        Real S = process_->x0();
41
0
        Real r = riskFreeRate();
42
0
        Real b = r - dividendYield();
43
0
        Real X1 = strike();
44
0
        Real X2 = arguments_.secondStrike;
45
0
        Time T2 = secondExpiryTime();
46
0
        Time t1 = firstExpiryTime();
47
0
        Real A = arguments_.premium;
48
49
50
0
        Real z1 = this->z1();
51
52
0
        Real z2 = this->z2();
53
54
0
        Real rho = sqrt(t1 / T2);
55
56
57
0
        ext::shared_ptr<PlainVanillaPayoff> payoff =
58
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
59
60
        //QuantLib requires sigma * sqrt(T) rather than just sigma/volatility
61
0
        Real vol = volatility();
62
63
        //calculate dividend discount factor assuming continuous compounding (e^-rt)
64
0
        DiscountFactor growth = dividendDiscount(t1);
65
        //calculate payoff discount factor assuming continuous compounding
66
0
        DiscountFactor discount = riskFreeDiscount(t1);
67
0
        Real result = 0;
68
0
        constexpr double minusInf = -std::numeric_limits<double>::infinity();
69
70
0
        Real y1 = this->y1(payoff->optionType()),
71
0
             y2 = this->y2(payoff->optionType());
72
0
        if (payoff->optionType() == Option::Call) {
73
            //instantiate payoff function for a call
74
0
            ext::shared_ptr<PlainVanillaPayoff> vanillaCallPayoff =
75
0
                ext::make_shared<PlainVanillaPayoff>(Option::Call, X1);
76
0
            Real BSM = BlackScholesCalculator(vanillaCallPayoff, S, growth, vol*sqrt(t1), discount).value();
77
0
            result = BSM
78
0
                + S*exp((b - r)*T2)*M2(y1, y2, minusInf, z1, rho)
79
0
                - X2*exp(-r*T2)*M2(y1 - vol*sqrt(t1), y2 - vol*sqrt(t1), minusInf, z1 - vol*sqrt(T2), rho)
80
0
                - S*exp((b - r)*t1)*N2(y1, z2) + X1*exp(-r*t1)*N2(y1 - vol*sqrt(t1), z2 - vol*sqrt(t1))
81
0
                - A*exp(-r*t1)*N2(y1 - vol*sqrt(t1), y2 - vol*sqrt(t1));
82
0
        } else {
83
            //instantiate payoff function for a call
84
0
            ext::shared_ptr<PlainVanillaPayoff> vanillaPutPayoff =
85
0
                ext::make_shared<PlainVanillaPayoff>(Option::Put, X1);
86
0
            result = BlackScholesCalculator(vanillaPutPayoff, S, growth, vol*sqrt(t1), discount).value()
87
0
                - S*exp((b - r)*T2)*M2(y1, y2, minusInf, -z1, rho)
88
0
                + X2*exp(-r*T2)*M2(y1 - vol*sqrt(t1), y2 - vol*sqrt(t1), minusInf, -z1 + vol*sqrt(T2), rho)
89
0
                + S*exp((b - r)*t1)*N2(z2, y2) - X1*exp(-r*t1)*N2(z2 - vol*sqrt(t1), y2 - vol*sqrt(t1))
90
0
                - A*exp(-r*t1)*N2(y1 - vol*sqrt(t1), y2 - vol*sqrt(t1));
91
0
        }
92
0
        this->results_.value = result;
93
0
    }
94
95
0
    Real AnalyticHolderExtensibleOptionEngine::I1Call() const {
96
0
        Real Sv = process_->x0();
97
0
        Real A = arguments_.premium;
98
99
0
        if(A==0)
100
0
        {
101
0
            return 0;
102
0
        }
103
0
        else
104
0
        {
105
0
            BlackScholesCalculator bs = bsCalculator(Sv, Option::Call);
106
0
            Real ci = bs.value();
107
0
            Real dc = bs.delta();
108
109
0
            Real yi = ci - A;
110
            //da/ds = 0
111
0
            Real di = dc - 0;
112
0
            Real epsilon = 0.001;
113
114
            //Newton-Raphson process
115
0
            while (std::fabs(yi) > epsilon){
116
0
                Sv = Sv - yi / di;
117
118
0
                bs = bsCalculator(Sv, Option::Call);
119
0
                ci = bs.value();
120
0
                dc = bs.delta();
121
122
0
                yi = ci - A;
123
0
                di = dc - 0;
124
0
            }
125
0
            return Sv;
126
0
        }
127
0
    }
128
129
0
    Real AnalyticHolderExtensibleOptionEngine::I2Call() const {
130
0
        Real Sv = process_->x0();
131
0
        Real X1 = strike();
132
0
        Real X2 = arguments_.secondStrike;
133
0
        Real A = arguments_.premium;
134
0
        Time T2 = secondExpiryTime();
135
0
        Time t1 = firstExpiryTime();
136
0
        Real r=riskFreeRate();
137
138
0
        Real val=X1-X2*std::exp(-r*(T2-t1));
139
0
        if(A< val){
140
0
            return std::numeric_limits<Real>::infinity();
141
0
        } else {
142
0
            BlackScholesCalculator bs = bsCalculator(Sv, Option::Call);
143
0
            Real ci = bs.value();
144
0
            Real dc = bs.delta();
145
146
0
            Real yi = ci - A - Sv + X1;
147
            //da/ds = 1
148
0
            Real di = dc - 1;
149
0
            Real epsilon = 0.001;
150
151
            //Newton-Raphson process
152
0
            while (std::fabs(yi) > epsilon){
153
0
                Sv = Sv - yi / di;
154
155
0
                bs = bsCalculator(Sv, Option::Call);
156
0
                ci = bs.value();
157
0
                dc = bs.delta();
158
159
0
                yi = ci - A - Sv + X1;
160
0
                di = dc - 1;
161
0
            }
162
0
            return Sv;
163
0
        }
164
0
    }
165
166
0
    Real AnalyticHolderExtensibleOptionEngine::I1Put() const {
167
0
        Real Sv = process_->x0();
168
        //Srtike
169
0
        Real X1 = strike();
170
        //Premium
171
0
        Real A = arguments_.premium;
172
173
0
        BlackScholesCalculator bs = bsCalculator(Sv, Option::Put);
174
0
        Real pi = bs.value();
175
0
        Real dc = bs.delta();
176
177
0
        Real yi = pi - A + Sv - X1;
178
        //da/ds = 1
179
0
        Real di = dc - 1;
180
0
        Real epsilon = 0.001;
181
182
        //Newton-Raphson prosess
183
0
        while (std::fabs(yi) > epsilon){
184
0
            Sv = Sv - yi / di;
185
186
0
            bs = bsCalculator(Sv, Option::Put);
187
0
            pi = bs.value();
188
0
            dc = bs.delta();
189
190
0
            yi = pi - A + Sv - X1;
191
0
            di = dc - 1;
192
0
        }
193
0
        return Sv;
194
0
    }
195
196
0
    Real AnalyticHolderExtensibleOptionEngine::I2Put() const {
197
0
        Real Sv = process_->x0();
198
0
        Real A = arguments_.premium;
199
0
        if(A==0){
200
0
            return std::numeric_limits<Real>::infinity();
201
0
        }
202
0
        else{
203
0
            BlackScholesCalculator bs = bsCalculator(Sv, Option::Put);
204
0
            Real pi = bs.value();
205
0
            Real dc = bs.delta();
206
207
0
            Real yi = pi - A;
208
            //da/ds = 0
209
0
            Real di = dc - 0;
210
0
            Real epsilon = 0.001;
211
212
            //Newton-Raphson prosess
213
0
            while (std::fabs(yi) > epsilon){
214
0
                Sv = Sv - yi / di;
215
216
0
                bs = bsCalculator(Sv, Option::Put);
217
0
                pi = bs.value();
218
0
                dc = bs.delta();
219
220
0
                yi = pi - A;
221
0
                di = dc - 0;
222
0
            }
223
0
            return Sv;
224
0
        }
225
0
    }
226
227
228
    BlackScholesCalculator AnalyticHolderExtensibleOptionEngine::bsCalculator(
229
0
                                    Real spot, Option::Type optionType) const {
230
        //Real spot = process_->x0();
231
0
        Real vol;
232
0
        DiscountFactor growth;
233
0
        DiscountFactor discount;
234
0
        Real X2 = arguments_.secondStrike;
235
0
        Time T2 = secondExpiryTime();
236
0
        Time t1 = firstExpiryTime();
237
0
        Time t = T2 - t1;
238
239
        //payoff
240
0
        ext::shared_ptr<PlainVanillaPayoff > vanillaPayoff =
241
0
            ext::make_shared<PlainVanillaPayoff>(optionType, X2);
242
243
        //QuantLib requires sigma * sqrt(T) rather than just sigma/volatility
244
0
        vol = volatility() * std::sqrt(t);
245
        //calculate dividend discount factor assuming continuous compounding (e^-rt)
246
0
        growth = dividendDiscount(t);
247
        //calculate payoff discount factor assuming continuous compounding
248
0
        discount = riskFreeDiscount(t);
249
250
0
        BlackScholesCalculator bs(vanillaPayoff, spot, growth, vol, discount);
251
0
        return bs;
252
0
    }
253
254
0
    Real AnalyticHolderExtensibleOptionEngine::M2(Real a, Real b, Real c, Real d, Real rho) const {
255
0
        BivariateCumulativeNormalDistributionDr78 CmlNormDist(rho);
256
0
        return CmlNormDist(b, d) - CmlNormDist(a, d) - CmlNormDist(b, c) + CmlNormDist(a,c);
257
0
    }
258
259
0
    Real AnalyticHolderExtensibleOptionEngine::N2(Real a, Real b) const {
260
0
        CumulativeNormalDistribution  NormDist;
261
0
        return NormDist(b) - NormDist(a);
262
0
    }
263
264
0
    Real AnalyticHolderExtensibleOptionEngine::strike() const {
265
0
        ext::shared_ptr<PlainVanillaPayoff> payoff =
266
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
267
0
        QL_REQUIRE(payoff, "non-plain payoff given");
268
0
        return payoff->strike();
269
0
    }
270
271
0
    Time AnalyticHolderExtensibleOptionEngine::firstExpiryTime() const {
272
0
        return process_->time(arguments_.exercise->lastDate());
273
0
    }
274
275
0
    Time AnalyticHolderExtensibleOptionEngine::secondExpiryTime() const {
276
0
        return process_->time(arguments_.secondExpiryDate);
277
0
    }
278
279
0
    Volatility AnalyticHolderExtensibleOptionEngine::volatility() const {
280
0
        return process_->blackVolatility()->blackVol(firstExpiryTime(), strike());
281
0
    }
282
0
    Rate AnalyticHolderExtensibleOptionEngine::riskFreeRate() const {
283
0
        return process_->riskFreeRate()->zeroRate(firstExpiryTime(), Continuous,
284
0
            NoFrequency);
285
0
    }
286
0
    Rate AnalyticHolderExtensibleOptionEngine::dividendYield() const {
287
0
        return process_->dividendYield()->zeroRate(firstExpiryTime(),
288
0
            Continuous, NoFrequency);
289
0
    }
290
291
0
    DiscountFactor AnalyticHolderExtensibleOptionEngine::dividendDiscount(Time t) const {
292
0
        return process_->dividendYield()->discount(t);
293
0
    }
294
295
0
    DiscountFactor AnalyticHolderExtensibleOptionEngine::riskFreeDiscount(Time t) const {
296
0
        return process_->riskFreeRate()->discount(t);
297
0
    }
298
299
0
    Real AnalyticHolderExtensibleOptionEngine::y1(Option::Type type) const {
300
0
        Real S = process_->x0();
301
0
        Real I2 = (type == Option::Call) ? I2Call() : I2Put();
302
303
0
        Real b = riskFreeRate() - dividendYield();
304
0
        Real vol = volatility();
305
0
        Time t1 = firstExpiryTime();
306
307
0
        return (log(S / I2) + (b + pow(vol, 2) / 2)*t1) / (vol*sqrt(t1));
308
0
    }
309
310
0
    Real AnalyticHolderExtensibleOptionEngine::y2(Option::Type type) const {
311
0
        Real S = process_->x0();
312
0
        Real I1 = (type == Option::Call) ? I1Call() : I1Put();
313
314
0
        Real b = riskFreeRate() - dividendYield();
315
0
        Real vol = volatility();
316
0
        Time t1 = firstExpiryTime();
317
318
0
        return (log(S / I1) + (b + pow(vol, 2) / 2)*t1) / (vol*sqrt(t1));
319
0
    }
320
321
0
    Real AnalyticHolderExtensibleOptionEngine::z1() const {
322
0
        Real S = process_->x0();
323
0
        Real X2 = arguments_.secondStrike;
324
0
        Real b = riskFreeRate() - dividendYield();
325
0
        Real vol = volatility();
326
0
        Time T2 = secondExpiryTime();
327
328
0
        return (log(S / X2) + (b + pow(vol, 2) / 2)*T2) / (vol*sqrt(T2));
329
0
    }
330
331
0
    Real AnalyticHolderExtensibleOptionEngine::z2() const {
332
0
        Real S = process_->x0();
333
0
        Real X1 = strike();
334
335
0
        Real b = riskFreeRate() - dividendYield();
336
0
        Real vol = volatility();
337
0
        Time t1 = firstExpiryTime();
338
339
0
        return (log(S / X1) + (b + pow(vol, 2) / 2)*t1) / (vol*sqrt(t1));
340
0
    }
341
342
}