Coverage Report

Created: 2026-06-23 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/pricingengines/swaption/gaussian1dswaptionengine.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2013 Peter Caspers
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/pricingengines/swaption/gaussian1dswaptionengine.hpp>
21
#include <ql/math/interpolations/cubicinterpolation.hpp>
22
#include <ql/payoff.hpp>
23
24
namespace QuantLib {
25
26
0
    void Gaussian1dSwaptionEngine::calculate() const {
27
28
0
        QL_REQUIRE(arguments_.settlementMethod != Settlement::ParYieldCurve,
29
0
                   "cash settled (ParYieldCurve) swaptions not priced with "
30
0
                   "Gaussian1dSwaptionEngine");
31
32
0
        QL_REQUIRE(arguments_.nominal != Null<Real>(),
33
0
                   "non-constant nominals are not supported yet");
34
35
0
        Date settlement = model_->termStructure()->referenceDate();
36
37
0
        if (arguments_.exercise->dates().back() <=
38
0
            settlement) { // swaption is expired, possibly generated swap is not
39
                          // valued
40
0
            results_.value = 0.0;
41
0
            return;
42
0
        }
43
44
0
        int idx = static_cast<int>(arguments_.exercise->dates().size()) - 1;
45
0
        int minIdxAlive = static_cast<int>(
46
0
            std::upper_bound(arguments_.exercise->dates().begin(),
47
0
                             arguments_.exercise->dates().end(), settlement) -
48
0
            arguments_.exercise->dates().begin());
49
50
0
        auto swap = arguments_.swap;
51
0
        Option::Type type =
52
0
            arguments_.type == Swap::Payer ? Option::Call : Option::Put;
53
0
        const Schedule& fixedSchedule = swap->fixedSchedule();
54
0
        const Schedule& floatSchedule = swap->floatingSchedule();
55
56
0
        Array npv0(2 * integrationPoints_ + 1, 0.0),
57
0
            npv1(2 * integrationPoints_ + 1, 0.0);
58
0
        Array z = model_->yGrid(stddevs_, integrationPoints_);
59
0
        Array p(z.size(), 0.0);
60
61
        // for probability computation
62
0
        std::vector<Array> npvp0, npvp1;
63
0
        if (probabilities_ != None) {
64
0
            for (int i = 0; i < idx - minIdxAlive + 2; ++i) {
65
0
                Array npvTmp0(2 * integrationPoints_ + 1, 0.0);
66
0
                Array npvTmp1(2 * integrationPoints_ + 1, 0.0);
67
0
                npvp0.push_back(npvTmp0);
68
0
                npvp1.push_back(npvTmp1);
69
0
            }
70
0
        }
71
        // end probability computation
72
73
0
        Date expiry1 = Date(), expiry0;
74
0
        Time expiry1Time = Null<Real>(), expiry0Time;
75
76
0
        do {
77
78
0
            if (idx == minIdxAlive - 1)
79
0
                expiry0 = settlement;
80
0
            else
81
0
                expiry0 = arguments_.exercise->dates()[idx];
82
83
0
            expiry0Time = std::max(
84
0
                model_->termStructure()->timeFromReference(expiry0), 0.0);
85
86
0
            Size j1 =
87
0
                std::upper_bound(fixedSchedule.dates().begin(),
88
0
                                 fixedSchedule.dates().end(), expiry0 - 1) -
89
0
                fixedSchedule.dates().begin();
90
0
            Size k1 =
91
0
                std::upper_bound(floatSchedule.dates().begin(),
92
0
                                 floatSchedule.dates().end(), expiry0 - 1) -
93
0
                floatSchedule.dates().begin();
94
95
            // a lazy object is not thread safe, neither is the caching
96
            // in gsrprocess. therefore we trigger computations here such
97
            // that neither lazy object recalculation nor write access
98
            // during caching occurs in the parallized loop below.
99
            // this is known to work for the gsr and markov functional
100
            // model implementations of Gaussian1dModel
101
#ifdef _OPENMP
102
            if (expiry1Time != Null<Real>())
103
                model_->yGrid(stddevs_, integrationPoints_, expiry1Time,
104
                              expiry0Time, 0.0);
105
            if (expiry0 > settlement) {
106
                for (Size l = k1; l < arguments_.floatingCoupons.size(); l++) {
107
                    model_->forwardRate(arguments_.floatingFixingDates[l],
108
                                        expiry0, 0.0,
109
                                        arguments_.swap->iborIndex());
110
                    model_->zerobond(arguments_.floatingPayDates[l], expiry0,
111
                                     0.0, discountCurve_);
112
                }
113
                for (Size l = j1; l < arguments_.fixedCoupons.size(); l++) {
114
                    model_->zerobond(arguments_.fixedPayDates[l], expiry0, 0.0,
115
                                     discountCurve_);
116
                }
117
                model_->numeraire(expiry0Time, 0.0, discountCurve_);
118
            }
119
#endif
120
121
0
#pragma omp parallel for default(shared) firstprivate(p) if(expiry0>settlement)
122
0
            for (long k = 0; k < (expiry0 > settlement ? (long)npv0.size() : 1);
123
0
                 k++) {
124
125
0
                Real price = 0.0;
126
0
                if (expiry1Time != Null<Real>()) {
127
0
                    Array yg = model_->yGrid(stddevs_, integrationPoints_,
128
0
                                             expiry1Time, expiry0Time,
129
0
                                             expiry0 > settlement ? z[k] : 0.0);
130
0
                    CubicInterpolation payoff0(
131
0
                        z.begin(), z.end(), npv1.begin(),
132
0
                        CubicInterpolation::Spline, true,
133
0
                        CubicInterpolation::Lagrange, 0.0,
134
0
                        CubicInterpolation::Lagrange, 0.0);
135
0
                    for (Size i = 0; i < yg.size(); i++) {
136
0
                        p[i] = payoff0(yg[i], true);
137
0
                    }
138
0
                    CubicInterpolation payoff1(
139
0
                        z.begin(), z.end(), p.begin(),
140
0
                        CubicInterpolation::Spline, true,
141
0
                        CubicInterpolation::Lagrange, 0.0,
142
0
                        CubicInterpolation::Lagrange, 0.0);
143
0
                    for (Size i = 0; i < z.size() - 1; i++) {
144
0
                        price += Gaussian1dModel::gaussianShiftedPolynomialIntegral(
145
0
                            0.0, payoff1.cCoefficients()[i],
146
0
                            payoff1.bCoefficients()[i],
147
0
                            payoff1.aCoefficients()[i], p[i], z[i], z[i],
148
0
                            z[i + 1]);
149
0
                    }
150
0
                    if (extrapolatePayoff_) {
151
0
                        if (flatPayoffExtrapolation_) {
152
0
                            price += Gaussian1dModel::gaussianShiftedPolynomialIntegral(
153
0
                                0.0, 0.0, 0.0, 0.0, p[z.size() - 2],
154
0
                                z[z.size() - 2], z[z.size() - 1], 100.0);
155
0
                            price += Gaussian1dModel::gaussianShiftedPolynomialIntegral(
156
0
                                0.0, 0.0, 0.0, 0.0, p[0], z[0], -100.0, z[0]);
157
0
                        } else {
158
0
                            if (type == Option::Call)
159
0
                                price +=
160
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
161
0
                                        0.0,
162
0
                                        payoff1.cCoefficients()[z.size() - 2],
163
0
                                        payoff1.bCoefficients()[z.size() - 2],
164
0
                                        payoff1.aCoefficients()[z.size() - 2],
165
0
                                        p[z.size() - 2], z[z.size() - 2],
166
0
                                        z[z.size() - 1], 100.0);
167
0
                            if (type == Option::Put)
168
0
                                price +=
169
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
170
0
                                        0.0, payoff1.cCoefficients()[0],
171
0
                                        payoff1.bCoefficients()[0],
172
0
                                        payoff1.aCoefficients()[0], p[0], z[0],
173
0
                                        -100.0, z[0]);
174
0
                        }
175
0
                    }
176
0
                }
177
178
0
                npv0[k] = price;
179
180
                // for probability computation
181
0
                if (probabilities_ != None) {
182
0
                    for (Size m = 0; m < npvp0.size(); m++) {
183
0
                        Real price = 0.0;
184
0
                        if (expiry1Time != Null<Real>()) {
185
0
                            Array yg = model_->yGrid(
186
0
                                stddevs_, integrationPoints_, expiry1Time,
187
0
                                expiry0Time, expiry0 > settlement ? z[k] : 0.0);
188
0
                            CubicInterpolation payoff0(
189
0
                                z.begin(), z.end(), npvp1[m].begin(),
190
0
                                CubicInterpolation::Spline, true,
191
0
                                CubicInterpolation::Lagrange, 0.0,
192
0
                                CubicInterpolation::Lagrange, 0.0);
193
0
                            for (Size i = 0; i < yg.size(); i++) {
194
0
                                p[i] = payoff0(yg[i], true);
195
0
                            }
196
0
                            CubicInterpolation payoff1(
197
0
                                z.begin(), z.end(), p.begin(),
198
0
                                CubicInterpolation::Spline, true,
199
0
                                CubicInterpolation::Lagrange, 0.0,
200
0
                                CubicInterpolation::Lagrange, 0.0);
201
0
                            for (Size i = 0; i < z.size() - 1; i++) {
202
0
                                price +=
203
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
204
0
                                        0.0, payoff1.cCoefficients()[i],
205
0
                                        payoff1.bCoefficients()[i],
206
0
                                        payoff1.aCoefficients()[i], p[i], z[i],
207
0
                                        z[i], z[i + 1]);
208
0
                            }
209
0
                            if (extrapolatePayoff_) {
210
0
                                if (flatPayoffExtrapolation_) {
211
0
                                    price +=
212
0
                                        Gaussian1dModel::gaussianShiftedPolynomialIntegral(
213
0
                                                  0.0, 0.0, 0.0, 0.0,
214
0
                                                  p[z.size() - 2],
215
0
                                                  z[z.size() - 2],
216
0
                                                  z[z.size() - 1], 100.0);
217
0
                                    price +=
218
0
                                        Gaussian1dModel::gaussianShiftedPolynomialIntegral(
219
0
                                                  0.0, 0.0, 0.0, 0.0, p[0],
220
0
                                                  z[0], -100.0, z[0]);
221
0
                                } else {
222
0
                                    if (type == Option::Call)
223
0
                                        price +=
224
0
                                            Gaussian1dModel::gaussianShiftedPolynomialIntegral(
225
0
                                                      0.0,
226
0
                                                      payoff1.cCoefficients()
227
0
                                                          [z.size() - 2],
228
0
                                                      payoff1.bCoefficients()
229
0
                                                          [z.size() - 2],
230
0
                                                      payoff1.aCoefficients()
231
0
                                                          [z.size() - 2],
232
0
                                                      p[z.size() - 2],
233
0
                                                      z[z.size() - 2],
234
0
                                                      z[z.size() - 1], 100.0);
235
0
                                    if (type == Option::Put)
236
0
                                        price +=
237
0
                                            Gaussian1dModel::gaussianShiftedPolynomialIntegral(
238
0
                                                      0.0,
239
0
                                                      payoff1
240
0
                                                          .cCoefficients()[0],
241
0
                                                      payoff1
242
0
                                                          .bCoefficients()[0],
243
0
                                                      payoff1
244
0
                                                          .aCoefficients()[0],
245
0
                                                      p[0], z[0], -100.0, z[0]);
246
0
                                }
247
0
                            }
248
0
                        }
249
250
0
                        npvp0[m][k] = price;
251
0
                    }
252
0
                }
253
                // end probability computation
254
255
0
                if (expiry0 > settlement) {
256
0
                    Real floatingLegNpv = 0.0;
257
0
                    for (Size l = k1; l < arguments_.floatingCoupons.size();
258
0
                         l++) {
259
0
                        floatingLegNpv +=
260
0
                            arguments_.nominal *
261
0
                            arguments_.floatingAccrualTimes[l] *
262
0
                            (arguments_.floatingSpreads[l] +
263
0
                             model_->forwardRate(
264
0
                                 arguments_.floatingFixingDates[l], expiry0,
265
0
                                 z[k], arguments_.swap->iborIndex())) *
266
0
                            model_->zerobond(arguments_.floatingPayDates[l],
267
0
                                             expiry0, z[k], discountCurve_);
268
0
                    }
269
0
                    Real fixedLegNpv = 0.0;
270
0
                    for (Size l = j1; l < arguments_.fixedCoupons.size(); l++) {
271
0
                        fixedLegNpv +=
272
0
                            arguments_.fixedCoupons[l] *
273
0
                            model_->zerobond(arguments_.fixedPayDates[l],
274
0
                                             expiry0, z[k], discountCurve_);
275
0
                    }
276
0
                    Real exerciseValue =
277
0
                        (type == Option::Call ? 1.0 : -1.0) *
278
0
                        (floatingLegNpv - fixedLegNpv) /
279
0
                        model_->numeraire(expiry0Time, z[k], discountCurve_);
280
281
                    // for probability computation
282
0
                    if (probabilities_ != None) {
283
0
                        if (idx == static_cast<int>(
284
0
                                       arguments_.exercise->dates().size()) -
285
0
                                       1) // if true we are at the latest date,
286
                                          // so we init
287
                                          // the no call probability
288
0
                            npvp0.back()[k] =
289
0
                                probabilities_ == Naive
290
0
                                    ? Real(1.0)
291
0
                                    : 1.0 / (model_->zerobond(expiry0Time, 0.0,
292
0
                                                              0.0,
293
0
                                                              discountCurve_) *
294
0
                                             model_->numeraire(expiry0, z[k],
295
0
                                                               discountCurve_));
296
0
                        if (exerciseValue >= npv0[k]) {
297
0
                            npvp0[idx - minIdxAlive][k] =
298
0
                                probabilities_ == Naive
299
0
                                    ? Real(1.0)
300
0
                                    : 1.0 /
301
0
                                          (model_->zerobond(expiry0Time, 0.0,
302
0
                                                            0.0,
303
0
                                                            discountCurve_) *
304
0
                                           model_->numeraire(expiry0Time, z[k],
305
0
                                                             discountCurve_));
306
0
                            for (Size ii = idx - minIdxAlive + 1;
307
0
                                 ii < npvp0.size(); ii++)
308
0
                                npvp0[ii][k] = 0.0;
309
0
                        }
310
0
                    }
311
                    // end probability computation
312
313
0
                    npv0[k] = std::max(npv0[k], exerciseValue);
314
0
                }
315
0
            }
316
317
0
            npv1.swap(npv0);
318
319
            // for probability computation
320
0
            if (probabilities_ != None) {
321
0
                for (Size i = 0; i < npvp0.size(); i++)
322
0
                    npvp1[i].swap(npvp0[i]);
323
0
            }
324
            // end probability computation
325
326
0
            expiry1 = expiry0;
327
0
            expiry1Time = expiry0Time;
328
329
0
        } while (--idx >= minIdxAlive - 1);
330
331
0
        results_.value = npv1[0] * model_->numeraire(0.0, 0.0, discountCurve_);
332
333
        // for probability computation
334
0
        if (probabilities_ != None) {
335
0
            std::vector<Real> prob(npvp0.size());
336
0
            for (Size i = 0; i < npvp0.size(); i++) {
337
0
                prob[i] = npvp1[i][0] *
338
0
                          (probabilities_ == Naive
339
0
                               ? 1.0
340
0
                               : model_->numeraire(0.0, 0.0, discountCurve_));
341
0
            }
342
0
            results_.additionalResults["probabilities"] = prob;
343
0
        }
344
        // end probability computation
345
0
    }
346
}