Coverage Report

Created: 2026-08-14 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/termstructures/inflation/inflationhelpers.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2007, 2009 Chris Kenyon
5
 Copyright (C) 2007 StatPro Italia srl
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
 <https://www.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
#include <ql/cashflows/inflationcouponpricer.hpp>
22
#include <ql/indexes/inflationindex.hpp>
23
#include <ql/pricingengines/swap/discountingswapengine.hpp>
24
#include <ql/shared_ptr.hpp>
25
#include <ql/termstructures/inflation/inflationhelpers.hpp>
26
#include <ql/termstructures/yield/flatforward.hpp>
27
#include <ql/utilities/null_deleter.hpp>
28
#include <utility>
29
30
namespace QuantLib {
31
32
    QL_DEPRECATED_DISABLE_WARNING
33
34
    ZeroCouponInflationSwapHelper::ZeroCouponInflationSwapHelper(
35
        const Handle<Quote>& quote,
36
        const Period& swapObsLag,
37
        const Date& maturity,
38
        Calendar calendar,
39
        BusinessDayConvention paymentConvention,
40
        const DayCounter& dayCounter,
41
        const ext::shared_ptr<ZeroInflationIndex>& zii,
42
        CPI::InterpolationType observationInterpolation,
43
        Pillar::Choice pillar,
44
        Date customPillarDate)
45
0
    : ZeroCouponInflationSwapHelper(
46
0
        quote, swapObsLag, Date(), maturity, std::move(calendar), paymentConvention,
47
0
        dayCounter, zii, observationInterpolation,
48
0
        pillar, customPillarDate) {}
49
50
    ZeroCouponInflationSwapHelper::ZeroCouponInflationSwapHelper(
51
        const Handle<Quote>& quote,
52
        const Period& swapObsLag,
53
        const Date& startDate,
54
        const Date& endDate,
55
        Calendar calendar,
56
        BusinessDayConvention paymentConvention,
57
        DayCounter  dayCounter,
58
        const ext::shared_ptr<ZeroInflationIndex>& zii,
59
        CPI::InterpolationType observationInterpolation,
60
        Pillar::Choice pillar,
61
        Date customPillarDate)
62
0
    : RelativeDateBootstrapHelper<ZeroInflationTermStructure>(quote, startDate == Date()),
63
0
      swapObsLag_(swapObsLag), startDate_(startDate), maturity_(endDate),
64
0
      calendar_(std::move(calendar)), paymentConvention_(paymentConvention),
65
0
      dayCounter_(std::move(dayCounter)), observationInterpolation_(observationInterpolation),
66
0
      pillarChoice_(pillar) {
67
0
        zii_ = zii->clone(termStructureHandle_);
68
        // We want to be notified of changes of fixings, but we don't
69
        // want notifications from termStructureHandle_ (they would
70
        // interfere with bootstrapping.)
71
0
        zii_->unregisterWith(termStructureHandle_);
72
73
0
        auto fixingPeriod = inflationPeriod(maturity_ - swapObsLag_, zii_->frequency());
74
75
0
        if (detail::CPI::isInterpolated(observationInterpolation_)) {
76
0
            earliestDate_ = fixingPeriod.first;
77
0
            latestDate_ = fixingPeriod.second + 1;
78
79
0
            switch (pillarChoice_) {
80
0
              case Pillar::MaturityDate:
81
0
                pillarDate_ = latestDate_;
82
0
                break;
83
0
              case Pillar::LastRelevantDate:
84
                // Assign the pillar to the node with the dominant
85
                // interpolation weight.  We use startDate_ (the swap
86
                // effective date) rather than maturity_ so that all
87
                // helpers sharing the same effective date make the same
88
                // LEFT/RIGHT decision.  Using maturity_ would cause
89
                // collisions when consecutive helpers mature in months
90
                // of different length (e.g. February vs March) and the
91
                // day-of-month is near the mid-month threshold.
92
0
                {
93
0
                    Date weightDate = startDate_ != Date() ? startDate_ : maturity_;
94
0
                    auto weightPeriod = inflationPeriod(weightDate, zii_->frequency());
95
0
                    Real dp = (weightPeriod.second + 1) - weightPeriod.first;
96
0
                    Real dt = weightDate - weightPeriod.first;
97
0
                    if (dt / dp <= 0.5)
98
0
                        pillarDate_ = fixingPeriod.first;
99
0
                }
100
0
                break;
101
0
              case Pillar::CustomDate:
102
0
                pillarDate_ = customPillarDate;
103
0
                QL_REQUIRE(pillarDate_ >= earliestDate_,
104
0
                           "pillar date (" << pillarDate_ << ") must be later "
105
0
                           "than or equal to the instrument's earliest date ("
106
0
                           << earliestDate_ << ")");
107
0
                QL_REQUIRE(pillarDate_ <= latestDate_,
108
0
                           "pillar date (" << pillarDate_ << ") must be before "
109
0
                           "or equal to the instrument's latest relevant date ("
110
0
                           << latestDate_ << ")");
111
0
                break;
112
0
              default:
113
0
                QL_FAIL("unknown Pillar::Choice(" << Integer(pillarChoice_) << ")");
114
0
            }
115
0
        } else {
116
            // Not interpolated: only the left node matters, so
117
            // earliestDate_ == latestDate_ and pillarChoice_ is
118
            // irrelevant (there is only one possible pillar).
119
0
            earliestDate_ = fixingPeriod.first;
120
0
            latestDate_ = fixingPeriod.first;
121
0
        }
122
123
        // check that the observation lag of the swap
124
        // is compatible with the availability lag of the index AND
125
        // it's interpolation (assuming the start day is spot)
126
0
        if (detail::CPI::isInterpolated(observationInterpolation_)) {
127
0
            Period pShift(zii_->frequency());
128
0
            QL_REQUIRE(swapObsLag_ - pShift >= zii_->availabilityLag(),
129
0
                       "inconsistency between swap observation lag "
130
0
                           << swapObsLag_ << ", index period " << pShift << " and index availability "
131
0
                           << zii_->availabilityLag() << ": need (obsLag-index period) >= availLag");
132
0
        }
133
134
0
        registerWith(zii_);
135
0
        ZeroCouponInflationSwapHelper::initializeDates();
136
0
    }
137
138
    QL_DEPRECATED_ENABLE_WARNING
139
140
141
0
    Real ZeroCouponInflationSwapHelper::impliedQuote() const {
142
0
        zciis_->deepUpdate();
143
0
        return zciis_->fairRate();
144
0
    }
145
146
0
    void ZeroCouponInflationSwapHelper::initializeDates() {
147
0
        zciis_ = ext::make_shared<ZeroCouponInflationSwap>(
148
0
            Swap::Payer, 1.0, updateDates_ ? evaluationDate_ : startDate_, maturity_, calendar_,
149
0
            paymentConvention_, dayCounter_, 0.0, zii_, swapObsLag_,
150
0
            observationInterpolation_);
151
152
        // The instrument takes a standard discounting swap engine.
153
        // The inflation-related work is done by the coupons.
154
155
        // Any nominal term structure will give the same result;
156
        // when calculating the fair rate, the equal discount factors
157
        // for the payments on the two legs will cancel out.
158
159
0
        auto null_nominal_curve =
160
0
            Handle<YieldTermStructure>(ext::make_shared<FlatForward>(0, NullCalendar(), 0.0, dayCounter_));
161
162
0
        zciis_->setPricingEngine(
163
0
            ext::make_shared<DiscountingSwapEngine>(null_nominal_curve));
164
0
    }
165
166
0
    void ZeroCouponInflationSwapHelper::setTermStructure(ZeroInflationTermStructure* z) {
167
        // do not set the relinkable handle as an observer -
168
        // force recalculation when needed
169
0
        bool observer = false;
170
171
0
        ext::shared_ptr<ZeroInflationTermStructure> temp(z, null_deleter());
172
0
        termStructureHandle_.linkTo(std::move(temp), observer);
173
174
0
        RelativeDateBootstrapHelper<ZeroInflationTermStructure>::setTermStructure(z);
175
0
    }
176
177
178
    YearOnYearInflationSwapHelper::YearOnYearInflationSwapHelper(
179
        const Handle<Quote>& quote,
180
        const Period& swapObsLag,
181
        const Date& maturity,
182
        Calendar calendar,
183
        BusinessDayConvention paymentConvention,
184
        DayCounter dayCounter,
185
        const ext::shared_ptr<YoYInflationIndex>& yii,
186
        CPI::InterpolationType interpolation,
187
        Handle<YieldTermStructure> nominalTermStructure,
188
        Pillar::Choice pillar,
189
        Date customPillarDate)
190
0
    : YearOnYearInflationSwapHelper(
191
0
        quote, swapObsLag, Date(), maturity, std::move(calendar), paymentConvention,
192
0
        std::move(dayCounter), yii, interpolation, std::move(nominalTermStructure),
193
0
        pillar, customPillarDate) {}
194
195
    YearOnYearInflationSwapHelper::YearOnYearInflationSwapHelper(
196
        const Handle<Quote>& quote,
197
        const Period& swapObsLag,
198
        const Date& startDate,
199
        const Date& endDate,
200
        Calendar calendar,
201
        BusinessDayConvention paymentConvention,
202
        DayCounter dayCounter,
203
        const ext::shared_ptr<YoYInflationIndex>& yii,
204
        CPI::InterpolationType interpolation,
205
        Handle<YieldTermStructure> nominalTermStructure,
206
        Pillar::Choice pillar,
207
        Date customPillarDate)
208
0
    : RelativeDateBootstrapHelper<YoYInflationTermStructure>(quote, startDate == Date()),
209
0
      swapObsLag_(swapObsLag), startDate_(startDate), maturity_(endDate),
210
0
      calendar_(std::move(calendar)), paymentConvention_(paymentConvention),
211
0
      dayCounter_(std::move(dayCounter)), interpolation_(interpolation),
212
0
      pillarChoice_(pillar),
213
0
      nominalTermStructure_(std::move(nominalTermStructure)) {
214
0
        yii_ = yii->clone(termStructureHandle_);
215
        // We want to be notified of changes of fixings, but we don't
216
        // want notifications from termStructureHandle_ (they would
217
        // interfere with bootstrapping.)
218
0
        yii_->unregisterWith(termStructureHandle_);
219
220
0
        auto fixingPeriod = inflationPeriod(maturity_ - swapObsLag_, yii_->frequency());
221
222
0
        if (detail::CPI::isInterpolated(interpolation_, yii_)) {
223
0
            earliestDate_ = fixingPeriod.first;
224
0
            latestDate_ = fixingPeriod.second + 1;
225
226
0
            switch (pillarChoice_) {
227
0
              case Pillar::MaturityDate:
228
0
                pillarDate_ = latestDate_;
229
0
                break;
230
0
              case Pillar::LastRelevantDate:
231
0
                {
232
0
                    Date weightDate = startDate_ != Date() ? startDate_ : maturity_;
233
0
                    auto weightPeriod = inflationPeriod(weightDate, yii_->frequency());
234
0
                    Real dp = (weightPeriod.second + 1) - weightPeriod.first;
235
0
                    Real dt = weightDate - weightPeriod.first;
236
0
                    if (dt / dp <= 0.5)
237
0
                        pillarDate_ = fixingPeriod.first;
238
0
                }
239
0
                break;
240
0
              case Pillar::CustomDate:
241
0
                pillarDate_ = customPillarDate;
242
0
                QL_REQUIRE(pillarDate_ >= earliestDate_,
243
0
                           "pillar date (" << pillarDate_ << ") must be later "
244
0
                           "than or equal to the instrument's earliest date ("
245
0
                           << earliestDate_ << ")");
246
0
                QL_REQUIRE(pillarDate_ <= latestDate_,
247
0
                           "pillar date (" << pillarDate_ << ") must be before "
248
0
                           "or equal to the instrument's latest relevant date ("
249
0
                           << latestDate_ << ")");
250
0
                break;
251
0
              default:
252
0
                QL_FAIL("unknown Pillar::Choice(" << Integer(pillarChoice_) << ")");
253
0
            }
254
0
        } else {
255
            // Not interpolated: only the left node matters, so
256
            // earliestDate_ == latestDate_ and pillarChoice_ is
257
            // irrelevant (there is only one possible pillar).
258
0
            earliestDate_ = fixingPeriod.first;
259
0
            latestDate_ = fixingPeriod.first;
260
0
        }
261
262
        // check that the observation lag of the swap
263
        // is compatible with the availability lag of the index AND
264
        // its interpolation (assuming the start day is spot)
265
0
        if (detail::CPI::isInterpolated(interpolation_, yii_)) {
266
0
            Period pShift(yii_->frequency());
267
0
            QL_REQUIRE(swapObsLag_ - pShift >= yii_->availabilityLag(),
268
0
                       "inconsistency between swap observation lag "
269
0
                       << swapObsLag_ << ", index period " << pShift << " and index availability "
270
0
                       << yii_->availabilityLag() << ": need (obsLag-index period) >= availLag");
271
0
        }
272
273
0
        registerWith(yii_);
274
0
        registerWith(nominalTermStructure_);
275
0
        YearOnYearInflationSwapHelper::initializeDates();
276
0
    }
277
278
0
    Real YearOnYearInflationSwapHelper::impliedQuote() const {
279
0
        yyiis_->deepUpdate();
280
0
        return yyiis_->fairRate();
281
0
    }
282
283
0
    void YearOnYearInflationSwapHelper::initializeDates() {
284
        // always works because tenor is always 1 year so
285
        // no problem with different days-in-month
286
0
        Schedule fixedSchedule = MakeSchedule()
287
0
                                     .from(updateDates_ ? evaluationDate_ : startDate_)
288
0
                                     .to(maturity_)
289
0
                                     .withTenor(1 * Years)
290
0
                                     .withConvention(Unadjusted)
291
0
                                     .withCalendar(calendar_) // fixed leg gets cal from sched
292
0
                                     .backwards();
293
0
        const Schedule& yoySchedule = fixedSchedule;
294
295
0
        yyiis_ = ext::make_shared<YearOnYearInflationSwap>(
296
0
            Swap::Payer, 1.0, fixedSchedule, 0.0, dayCounter_,
297
0
            yoySchedule, yii_, swapObsLag_, interpolation_,
298
0
            0.0, dayCounter_, calendar_, paymentConvention_);
299
300
        // The instrument takes a standard discounting swap engine.
301
        // The inflation-related work is done by the coupons.
302
0
        yyiis_->setPricingEngine(
303
0
            ext::make_shared<DiscountingSwapEngine>(nominalTermStructure_));
304
0
    }
305
306
0
    void YearOnYearInflationSwapHelper::setTermStructure(YoYInflationTermStructure* y) {
307
        // do not set the relinkable handle as an observer -
308
        // force recalculation when needed
309
0
        bool observer = false;
310
311
0
        ext::shared_ptr<YoYInflationTermStructure> temp(y, null_deleter());
312
0
        termStructureHandle_.linkTo(std::move(temp), observer);
313
314
0
        RelativeDateBootstrapHelper<YoYInflationTermStructure>::setTermStructure(y);
315
0
    }
316
317
}