Coverage Report

Created: 2025-12-08 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/pricingengines/capfloor/gaussian1dcapfloorengine.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/capfloor/gaussian1dcapfloorengine.hpp>
21
#include <ql/math/interpolations/cubicinterpolation.hpp>
22
#include <ql/payoff.hpp>
23
24
namespace QuantLib {
25
26
0
    void Gaussian1dCapFloorEngine::calculate() const {
27
28
0
        for (Real spread : arguments_.spreads)
29
0
            QL_REQUIRE(spread == 0.0, "Non zero spreads (" << spread << ") are not allowed.");
30
31
0
        Size optionlets = arguments_.startDates.size();
32
0
        std::vector<Real> values(optionlets, 0.0);
33
0
        std::vector<Real> forwards(optionlets, 0.0);
34
0
        Real value = 0.0;
35
36
0
        Date settlement = model_->termStructure()->referenceDate();
37
38
0
        CapFloor::Type type = arguments_.type;
39
40
0
        Array z = model_->yGrid(stddevs_, integrationPoints_);
41
0
        Array p(z.size());
42
43
0
        for (Size i = 0; i < optionlets; ++i) {
44
45
0
            Date valueDate = arguments_.startDates[i];
46
0
            Date paymentDate = arguments_.endDates[i];
47
0
            ext::shared_ptr<IborIndex> iborIndex =
48
0
                ext::dynamic_pointer_cast<IborIndex>(arguments_.indexes[i]);
49
            // if we do not find an ibor index with associated forwarding curve
50
            // we fall back on the model curve
51
52
0
            if (paymentDate > settlement) {
53
54
0
                Real f = arguments_.nominals[i] * arguments_.gearings[i];
55
0
                Date fixingDate = arguments_.fixingDates[i];
56
0
                Time fixingTime =
57
0
                    model_->termStructure()->timeFromReference(fixingDate);
58
59
0
                Real strike;
60
61
0
                if (type == CapFloor::Cap || type == CapFloor::Collar) {
62
0
                    strike = arguments_.capRates[i];
63
0
                    if (fixingDate <= settlement) {
64
0
                        values[i] =
65
0
                            std::max(arguments_.forwards[i] - strike, 0.0) * f *
66
0
                            arguments_.accrualTimes[i];
67
0
                    } else {
68
69
                        // todo add openmp support later on (as in gaussian1dswaptionengine)
70
71
0
                        for (Size j = 0; j < z.size(); j++) {
72
0
                            Real floatingLegNpv;
73
0
                            if (iborIndex != nullptr)
74
0
                                floatingLegNpv =
75
0
                                    arguments_.accrualTimes[i] *
76
0
                                    model_->forwardRate(fixingDate, fixingDate,
77
0
                                                        z[j], iborIndex) *
78
0
                                    model_->zerobond(paymentDate, fixingDate,
79
0
                                                     z[j], discountCurve_);
80
0
                            else
81
0
                                floatingLegNpv =
82
0
                                    (model_->zerobond(valueDate, fixingDate,
83
0
                                                      z[j]) -
84
0
                                     model_->zerobond(paymentDate, fixingDate,
85
0
                                                      z[j]));
86
0
                            Real fixedLegNpv =
87
0
                                arguments_.capRates[i] *
88
0
                                arguments_.accrualTimes[i] *
89
0
                                model_->zerobond(paymentDate, fixingDate, z[j]);
90
0
                            p[j] =
91
0
                                std::max((floatingLegNpv - fixedLegNpv), 0.0) /
92
0
                                model_->numeraire(fixingTime, z[j],
93
0
                                                  discountCurve_);
94
0
                        }
95
0
                        CubicInterpolation payoff(
96
0
                            z.begin(), z.end(), p.begin(),
97
0
                            CubicInterpolation::Spline, true,
98
0
                            CubicInterpolation::Lagrange, 0.0,
99
0
                            CubicInterpolation::Lagrange, 0.0);
100
0
                        Real price = 0.0;
101
0
                        for (Size j = 0; j < z.size() - 1; j++) {
102
0
                            price += Gaussian1dModel::gaussianShiftedPolynomialIntegral(
103
0
                                0.0, payoff.cCoefficients()[j],
104
0
                                payoff.bCoefficients()[j],
105
0
                                payoff.aCoefficients()[j], p[j], z[j], z[j],
106
0
                                z[j + 1]);
107
0
                        }
108
0
                        if (extrapolatePayoff_) {
109
0
                            if (flatPayoffExtrapolation_) {
110
0
                                price +=
111
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
112
0
                                        0.0, 0.0, 0.0, 0.0, p[z.size() - 2],
113
0
                                        z[z.size() - 2], z[z.size() - 1],
114
0
                                        100.0);
115
0
                                price +=
116
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
117
0
                                        0.0, 0.0, 0.0, 0.0, p[0], z[0], -100.0,
118
0
                                        z[0]);
119
0
                            } else {
120
0
                                price +=
121
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
122
0
                                        0.0,
123
0
                                        payoff.cCoefficients()[z.size() - 2],
124
0
                                        payoff.bCoefficients()[z.size() - 2],
125
0
                                        payoff.aCoefficients()[z.size() - 2],
126
0
                                        p[z.size() - 2], z[z.size() - 2],
127
0
                                        z[z.size() - 1], 100.0);
128
0
                            }
129
0
                        }
130
0
                        values[i] =
131
0
                            price *
132
0
                            model_->numeraire(0.0, 0.0, discountCurve_) * f;
133
0
                    }
134
0
                }
135
0
                if (type == CapFloor::Floor || type == CapFloor::Collar) {
136
0
                    strike = arguments_.floorRates[i];
137
0
                    Real floorlet;
138
0
                    if (fixingDate <= settlement) {
139
0
                        floorlet =
140
0
                            std::max(-(arguments_.forwards[i] - strike), 0.0) *
141
0
                            f * arguments_.accrualTimes[i];
142
0
                    } else {
143
0
                        for (Size j = 0; j < z.size(); j++) {
144
0
                            Real floatingLegNpv;
145
0
                            if (iborIndex != nullptr)
146
0
                                floatingLegNpv =
147
0
                                    arguments_.accrualTimes[i] *
148
0
                                    model_->forwardRate(fixingDate, fixingDate,
149
0
                                                        z[j], iborIndex) *
150
0
                                    model_->zerobond(paymentDate, fixingDate,
151
0
                                                     z[j], discountCurve_);
152
0
                            else
153
0
                                floatingLegNpv =
154
0
                                    (model_->zerobond(valueDate, fixingDate,
155
0
                                                      z[j]) -
156
0
                                     model_->zerobond(paymentDate, fixingDate,
157
0
                                                      z[j]));
158
0
                            Real fixedLegNpv =
159
0
                                arguments_.floorRates[i] *
160
0
                                arguments_.accrualTimes[i] *
161
0
                                model_->zerobond(paymentDate, fixingDate, z[j]);
162
0
                            p[j] =
163
0
                                std::max(-(floatingLegNpv - fixedLegNpv), 0.0) /
164
0
                                model_->numeraire(fixingTime, z[j],
165
0
                                                  discountCurve_);
166
0
                        }
167
0
                        CubicInterpolation payoff(
168
0
                            z.begin(), z.end(), p.begin(),
169
0
                            CubicInterpolation::Spline, true,
170
0
                            CubicInterpolation::Lagrange, 0.0,
171
0
                            CubicInterpolation::Lagrange, 0.0);
172
0
                        Real price = 0.0;
173
0
                        for (Size j = 0; j < z.size() - 1; j++) {
174
0
                            price += Gaussian1dModel::gaussianShiftedPolynomialIntegral(
175
0
                                0.0, payoff.cCoefficients()[j],
176
0
                                payoff.bCoefficients()[j],
177
0
                                payoff.aCoefficients()[j], p[j], z[j], z[j],
178
0
                                z[j + 1]);
179
0
                        }
180
0
                        if (extrapolatePayoff_) {
181
0
                            if (flatPayoffExtrapolation_) {
182
0
                                price +=
183
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
184
0
                                        0.0, 0.0, 0.0, 0.0, p[z.size() - 2],
185
0
                                        z[z.size() - 2], z[z.size() - 1],
186
0
                                        100.0);
187
0
                                price +=
188
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
189
0
                                        0.0, 0.0, 0.0, 0.0, p[0], z[0], -100.0,
190
0
                                        z[0]);
191
0
                            } else {
192
0
                                price +=
193
0
                                    Gaussian1dModel::gaussianShiftedPolynomialIntegral(
194
0
                                        0.0, payoff.cCoefficients()[0],
195
0
                                        payoff.bCoefficients()[0],
196
0
                                        payoff.aCoefficients()[0], p[0], z[0],
197
0
                                        -100.0, z[0]);
198
0
                            }
199
0
                        }
200
0
                        floorlet = price *
201
0
                                   model_->numeraire(0.0, 0.0, discountCurve_) *
202
0
                                   f;
203
0
                    }
204
0
                    if (type == CapFloor::Floor) {
205
0
                        values[i] = floorlet;
206
0
                    } else {
207
                        // a collar is long a cap and short a floor
208
0
                        values[i] -= floorlet;
209
0
                    }
210
0
                }
211
212
0
                value += values[i];
213
0
            }
214
0
        }
215
216
0
        results_.value = value;
217
218
0
        results_.additionalResults["optionletsPrice"] = values;
219
0
        results_.additionalResults["optionletsAtmForward"] = forwards;
220
0
    }
221
222
}