Coverage Report

Created: 2025-08-28 06:30

/src/quantlib/ql/cashflows/cpicoupon.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) 2009 Chris Kenyon
5
 Copyright (C) 2022 Quaternion Risk Management Ltd
6
7
 This file is part of QuantLib, a free-software/open-source library
8
 for financial quantitative analysts and developers - http://quantlib.org/
9
10
 QuantLib is free software: you can redistribute it and/or modify it
11
 under the terms of the QuantLib license.  You should have received a
12
 copy of the license along with this program; if not, please email
13
 <quantlib-dev@lists.sf.net>. The license is also available online at
14
 <http://quantlib.org/license.shtml>.
15
16
 This program is distributed in the hope that it will be useful, but WITHOUT
17
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 FOR A PARTICULAR PURPOSE.  See the license for more details.
19
 */
20
21
22
#include <ql/cashflows/cashflowvectors.hpp>
23
#include <ql/cashflows/cpicoupon.hpp>
24
#include <ql/cashflows/cpicouponpricer.hpp>
25
#include <ql/cashflows/inflationcoupon.hpp>
26
#include <ql/time/daycounters/thirty360.hpp>
27
#include <utility>
28
29
30
namespace QuantLib {
31
32
    CPICoupon::CPICoupon(Real baseCPI,
33
                         const Date& paymentDate,
34
                         Real nominal,
35
                         const Date& startDate,
36
                         const Date& endDate,
37
                         const ext::shared_ptr<ZeroInflationIndex>& index,
38
                         const Period& observationLag,
39
                         CPI::InterpolationType observationInterpolation,
40
                         const DayCounter& dayCounter,
41
                         Real fixedRate,
42
                         const Date& refPeriodStart,
43
                         const Date& refPeriodEnd,
44
                         const Date& exCouponDate)
45
0
    : CPICoupon(baseCPI, Date(), paymentDate, nominal, startDate, endDate,
46
0
                index, observationLag, observationInterpolation, dayCounter,
47
0
                fixedRate, refPeriodStart, refPeriodEnd, exCouponDate) {}
Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&)
Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&)
48
49
    CPICoupon::CPICoupon(const Date& baseDate,
50
                         const Date& paymentDate,
51
                         Real nominal,
52
                         const Date& startDate,
53
                         const Date& endDate,
54
                         const ext::shared_ptr<ZeroInflationIndex>& index,
55
                         const Period& observationLag,
56
                         CPI::InterpolationType observationInterpolation,
57
                         const DayCounter& dayCounter,
58
                         Real fixedRate,
59
                         const Date& refPeriodStart,
60
                         const Date& refPeriodEnd,
61
                         const Date& exCouponDate)
62
0
    : CPICoupon(Null<Real>(), baseDate, paymentDate, nominal, startDate, endDate,
63
0
                index, observationLag, observationInterpolation, dayCounter,
64
0
                fixedRate, refPeriodStart, refPeriodEnd, exCouponDate) {}
Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&)
Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&)
65
66
    CPICoupon::CPICoupon(Real baseCPI,
67
                         const Date& baseDate,
68
                         const Date& paymentDate,
69
                         Real nominal,
70
                         const Date& startDate,
71
                         const Date& endDate,
72
                         const ext::shared_ptr<ZeroInflationIndex>& index,
73
                         const Period& observationLag,
74
                         CPI::InterpolationType observationInterpolation,
75
                         const DayCounter& dayCounter,
76
                         Real fixedRate,
77
                         const Date& refPeriodStart,
78
                         const Date& refPeriodEnd,
79
                         const Date& exCouponDate)
80
0
    : InflationCoupon(paymentDate, nominal, startDate, endDate, 0,
81
0
                      index, observationLag, dayCounter,
82
0
                      refPeriodStart, refPeriodEnd, exCouponDate),
83
0
      baseCPI_(baseCPI), fixedRate_(fixedRate),
84
0
      observationInterpolation_(observationInterpolation), baseDate_(baseDate) {
85
86
0
        QL_REQUIRE(index_, "no index provided");
87
0
        QL_REQUIRE(baseCPI_ != Null<Rate>() || baseDate != Date(),
88
0
                   "baseCPI and baseDate can not be both null, provide a valid baseCPI or baseDate");
89
0
        QL_REQUIRE(baseCPI_ == Null<Rate>() || std::fabs(baseCPI_) > 1e-16,
90
0
                   "|baseCPI_| < 1e-16, future divide-by-zero problem");
91
0
    }
Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&)
Unexecuted instantiation: QuantLib::CPICoupon::CPICoupon(double, QuantLib::Date const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::DayCounter const&, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Date const&)
92
93
0
    void CPICoupon::accept(AcyclicVisitor& v) {
94
0
        auto* v1 = dynamic_cast<Visitor<CPICoupon>*>(&v);
95
0
        if (v1 != nullptr)
96
0
            v1->visit(*this);
97
0
        else
98
0
            InflationCoupon::accept(v);
99
0
    }
100
101
0
    Real CPICoupon::accruedAmount(const Date& d) const {
102
0
        if (d <= accrualStartDate_ || d > paymentDate_) {
103
0
            return 0.0;
104
0
        } else {
105
0
            auto pricer = ext::dynamic_pointer_cast<CPICouponPricer>(pricer_);
106
0
            QL_REQUIRE(pricer, "pricer not set or of wrong type");
107
0
            pricer->initialize(*this);
108
0
            return nominal() * pricer->accruedRate(d) * accruedPeriod(d);
109
0
        }
110
0
    }
111
112
0
    Rate CPICoupon::indexRatio(Date d) const {
113
114
0
        Rate I0 = baseCPI();
115
116
0
        if (I0 == Null<Rate>()) {
117
0
            I0 = CPI::laggedFixing(cpiIndex(),
118
0
                                   baseDate() + observationLag(),
119
0
                                   observationLag(),
120
0
                                   observationInterpolation());
121
0
        }
122
123
0
        Rate I1 = CPI::laggedFixing(cpiIndex(),
124
0
                                    d,
125
0
                                    observationLag(),
126
0
                                    observationInterpolation());
127
128
0
        return I1 / I0;
129
0
    }
130
131
    bool CPICoupon::checkPricerImpl(
132
0
            const ext::shared_ptr<InflationCouponPricer>&pricer) const {
133
0
        return static_cast<bool>(
134
0
                        ext::dynamic_pointer_cast<CPICouponPricer>(pricer));
135
0
    }
136
137
138
139
    CPICashFlow::CPICashFlow(Real notional,
140
                             const ext::shared_ptr<ZeroInflationIndex>& index,
141
                             const Date& baseDate,
142
                             Real baseFixing,
143
                             const Date& observationDate,
144
                             const Period& observationLag,
145
                             CPI::InterpolationType interpolation,
146
                             const Date& paymentDate,
147
                             bool growthOnly)
148
0
    : IndexedCashFlow(notional, index, baseDate, observationDate - observationLag, paymentDate, growthOnly),
149
0
      baseFixing_(baseFixing), observationDate_(observationDate), observationLag_(observationLag),
150
0
      interpolation_(interpolation), frequency_(index ? index->frequency() : NoFrequency) {
151
0
        QL_REQUIRE(index, "no index provided");
152
0
        QL_REQUIRE(
153
0
            baseFixing_ != Null<Rate>() || baseDate != Date(),
154
0
            "baseCPI and baseDate can not be both null, provide a valid baseCPI or baseDate");
155
0
        QL_REQUIRE(baseFixing_ == Null<Rate>() || std::fabs(baseFixing_) > 1e-16,
156
0
                   "|baseCPI_| < 1e-16, future divide-by-zero problem");
157
0
    }
Unexecuted instantiation: QuantLib::CPICashFlow::CPICashFlow(double, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::Date const&, bool)
Unexecuted instantiation: QuantLib::CPICashFlow::CPICashFlow(double, boost::shared_ptr<QuantLib::ZeroInflationIndex> const&, QuantLib::Date const&, double, QuantLib::Date const&, QuantLib::Period const&, QuantLib::CPI::InterpolationType, QuantLib::Date const&, bool)
158
159
0
    Date CPICashFlow::baseDate() const {
160
0
        Date base = IndexedCashFlow::baseDate();
161
0
        if (base != Date()) {
162
0
            return base;
163
0
        } else {
164
0
            QL_FAIL("no base date specified");
165
0
        }
166
0
    }
167
168
0
    Real CPICashFlow::baseFixing() const {
169
0
        if (baseFixing_ != Null<Rate>())
170
0
            return baseFixing_;
171
0
        else
172
0
            return CPI::laggedFixing(cpiIndex(), baseDate(), 0 * Months, interpolation_);
173
0
    }
174
175
0
    Real CPICashFlow::indexFixing() const {
176
0
        return CPI::laggedFixing(cpiIndex(), observationDate_, observationLag_, interpolation_);
177
0
    }
178
179
    CPILeg::CPILeg(Schedule schedule,
180
                   ext::shared_ptr<ZeroInflationIndex> index,
181
                   const Real baseCPI,
182
                   const Period& observationLag)
183
0
    : schedule_(std::move(schedule)), index_(std::move(index)), baseCPI_(baseCPI),
184
0
      observationLag_(observationLag), paymentDayCounter_(Thirty360(Thirty360::BondBasis)),
185
0
      paymentCalendar_(schedule_.calendar()) {}
186
187
188
0
    CPILeg& CPILeg::withObservationInterpolation(CPI::InterpolationType interp) {
189
0
        observationInterpolation_ = interp;
190
0
        return *this;
191
0
    }
192
193
194
0
    CPILeg& CPILeg::withFixedRates(Real fixedRate) {
195
0
        fixedRates_ = std::vector<Real>(1,fixedRate);
196
0
        return *this;
197
0
    }
198
199
0
    CPILeg& CPILeg::withFixedRates(const std::vector<Real>& fixedRates) {
200
0
        fixedRates_ =   fixedRates;
201
0
        return *this;
202
0
    }
203
204
0
    CPILeg& CPILeg::withNotionals(Real notional) {
205
0
        notionals_ = std::vector<Real>(1,notional);
206
0
        return *this;
207
0
    }
208
209
0
    CPILeg& CPILeg::withNotionals(const std::vector<Real>& notionals) {
210
0
        notionals_ = notionals;
211
0
        return *this;
212
0
    }
213
214
0
    CPILeg& CPILeg::withSubtractInflationNominal(bool growthOnly) {
215
0
        subtractInflationNominal_ = growthOnly;
216
0
        return *this;
217
0
    }
218
219
0
    CPILeg& CPILeg::withPaymentDayCounter(const DayCounter& dayCounter) {
220
0
        paymentDayCounter_ = dayCounter;
221
0
        return *this;
222
0
    }
223
224
0
    CPILeg& CPILeg::withPaymentAdjustment(BusinessDayConvention convention) {
225
0
        paymentAdjustment_ = convention;
226
0
        return *this;
227
0
    }
228
229
0
    CPILeg& CPILeg::withPaymentCalendar(const Calendar& cal) {
230
0
        paymentCalendar_ = cal;
231
0
        return *this;
232
0
    }
233
234
0
    CPILeg& CPILeg::withCaps(Rate cap) {
235
0
        caps_ = std::vector<Rate>(1,cap);
236
0
        return *this;
237
0
    }
238
239
0
    CPILeg& CPILeg::withCaps(const std::vector<Rate>& caps) {
240
0
        caps_ = caps;
241
0
        return *this;
242
0
    }
243
244
0
    CPILeg& CPILeg::withFloors(Rate floor) {
245
0
        floors_ = std::vector<Rate>(1,floor);
246
0
        return *this;
247
0
    }
248
249
0
    CPILeg& CPILeg::withFloors(const std::vector<Rate>& floors) {
250
0
        floors_ = floors;
251
0
        return *this;
252
0
    }
253
254
    CPILeg& CPILeg::withExCouponPeriod(
255
                        const Period& period,
256
                        const Calendar& cal,
257
                        BusinessDayConvention convention,
258
0
                        bool endOfMonth) {
259
0
        exCouponPeriod_ = period;
260
0
        exCouponCalendar_ = cal;
261
0
        exCouponAdjustment_ = convention;
262
0
        exCouponEndOfMonth_ = endOfMonth;
263
0
        return *this;
264
0
    }
265
266
0
    CPILeg& CPILeg::withBaseDate(const Date& baseDate) {
267
0
        baseDate_ = baseDate;
268
0
        return *this;
269
0
    }
270
271
272
0
    CPILeg::operator Leg() const {
273
274
0
        QL_REQUIRE(!notionals_.empty(), "no notional given");
275
0
        Size n = schedule_.size()-1;
276
0
        Leg leg;
277
0
        leg.reserve(n+1);   // +1 for notional, we always have some sort ...
278
279
0
        Date baseDate = baseDate_;
280
        // BaseDate and baseCPI are not given, use the first date as startDate and the baseFixingg
281
        // should be at startDate - observationLag
282
283
0
        if (n>0) {
284
0
            QL_REQUIRE(!fixedRates_.empty(), "no fixedRates given");
285
286
0
            if (baseDate_ == Date() && baseCPI_ == Null<Real>()) {
287
0
                baseDate = schedule_.date(0) - observationLag_;
288
0
            }
289
290
0
            Date refStart, start, refEnd, end;
291
292
0
            for (Size i=0; i<n; ++i) {
293
0
                refStart = start = schedule_.date(i);
294
0
                refEnd   =   end = schedule_.date(i+1);
295
0
                Date paymentDate = paymentCalendar_.adjust(end, paymentAdjustment_);
296
297
0
                Date exCouponDate;
298
0
                if (exCouponPeriod_ != Period())
299
0
                {
300
0
                    exCouponDate = exCouponCalendar_.advance(paymentDate,
301
0
                                                                -exCouponPeriod_,
302
0
                                                                exCouponAdjustment_,
303
0
                                                                exCouponEndOfMonth_);
304
0
                }
305
306
0
                if (i==0   && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) {
307
0
                    BusinessDayConvention bdc = schedule_.businessDayConvention();
308
0
                    refStart = schedule_.calendar().adjust(end - schedule_.tenor(), bdc);
309
0
                }
310
0
                if (i==n-1 && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) {
311
0
                    BusinessDayConvention bdc = schedule_.businessDayConvention();
312
0
                    refEnd = schedule_.calendar().adjust(start + schedule_.tenor(), bdc);
313
0
                }
314
0
                if (detail::get(fixedRates_, i, 1.0) == 0.0) { // fixed coupon
315
                    // this looks like an optimization but I'm not sure it's worth it?
316
0
                    leg.push_back(ext::make_shared<FixedRateCoupon>
317
0
                                   (paymentDate, detail::get(notionals_, i, 0.0),
318
0
                                    detail::effectiveFixedRate({},caps_,floors_,i),
319
0
                                    paymentDayCounter_, start, end, refStart, refEnd, exCouponDate));
320
0
                } else { // zero inflation coupon
321
0
                    if (detail::noOption(caps_, floors_, i)) { // just swaplet
322
0
                        leg.push_back(ext::make_shared<CPICoupon>
323
0
                                    (baseCPI_,    // all have same base for ratio
324
0
                                     baseDate,
325
0
                                     paymentDate,
326
0
                                     detail::get(notionals_, i, 0.0),
327
0
                                     start, end,
328
0
                                     index_, observationLag_,
329
0
                                     observationInterpolation_,
330
0
                                     paymentDayCounter_,
331
0
                                     detail::get(fixedRates_, i, 0.0),
332
0
                                     refStart, refEnd, exCouponDate));
333
0
                    } else  {     // cap/floorlet
334
0
                        QL_FAIL("caps/floors on CPI coupons not implemented.");
335
0
                    }
336
0
                }
337
0
            }
338
0
        }
339
340
        // in CPI legs you always have a notional flow of some sort
341
0
        Date paymentDate = paymentCalendar_.adjust(schedule_.date(n), paymentAdjustment_);
342
0
        leg.push_back(ext::make_shared<CPICashFlow>
343
0
                          (detail::get(notionals_, n, 0.0), index_,
344
0
                           baseDate, baseCPI_,
345
0
                           schedule_.date(n), observationLag_, observationInterpolation_,
346
0
                           paymentDate, subtractInflationNominal_));
347
348
        // no caps and floors here, so this is enough
349
0
        setCouponPricer(leg, ext::make_shared<CPICouponPricer>());
350
351
0
        return leg;
352
0
    }
353
354
}