Coverage Report

Created: 2025-08-28 06:30

/src/quantlib/ql/termstructures/inflation/seasonality.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) 2008 Piero Del Boca
5
 Copyright (C) 2009 Chris Kenyon
6
 Copyright (C) 2015 Bernd Lewerenz
7
8
This file is part of QuantLib, a free-software/open-source library
9
for financial quantitative analysts and developers - http://quantlib.org/
10
11
QuantLib is free software: you can redistribute it and/or modify it
12
under the terms of the QuantLib license.  You should have received a
13
copy of the license along with this program; if not, please email
14
<quantlib-dev@lists.sf.net>. The license is also available online at
15
<http://quantlib.org/license.shtml>.
16
17
This program is distributed in the hope that it will be useful, but WITHOUT
18
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
FOR A PARTICULAR PURPOSE.  See the license for more details.
20
*/
21
22
23
#include <ql/termstructures/inflation/seasonality.hpp>
24
#include <ql/termstructures/inflationtermstructure.hpp>
25
#include <ql/errors.hpp>
26
27
namespace QuantLib {
28
29
0
    bool Seasonality::isConsistent(const InflationTermStructure&) const {
30
0
        return true;
31
0
    }
32
33
34
    //Multiplicative Seasonality on price = on CPI/RPI/HICP/etc
35
36
    void MultiplicativePriceSeasonality::validate() const
37
0
    {
38
        // NOLINTBEGIN(clang-analyzer-optin.cplusplus.VirtualCall)
39
0
        switch (this->frequency()) {
40
0
            case Semiannual:        //2
41
0
            case EveryFourthMonth:  //3
42
0
            case Quarterly:         //4
43
0
            case Bimonthly:         //6
44
0
            case Monthly:           //12
45
0
            case Biweekly:          // etc.
46
0
            case Weekly:
47
0
            case Daily:
48
0
                QL_REQUIRE(!this->seasonalityFactors().empty(), "no seasonality factors given");
49
0
                QL_REQUIRE( (this->seasonalityFactors().size() %
50
0
                             this->frequency()) == 0,
51
0
                           "For frequency " << this->frequency()
52
0
                           << " require multiple of " << ((int)this->frequency()) << " factors "
53
0
                           << this->seasonalityFactors().size() << " were given.");
54
0
            break;
55
0
            default:
56
0
                QL_FAIL("bad frequency specified: " << this->frequency()
57
0
                        << ", only semi-annual through daily permitted.");
58
0
            break;
59
0
        }
60
        // NOLINTEND(clang-analyzer-optin.cplusplus.VirtualCall)
61
0
    }
62
63
64
    bool MultiplicativePriceSeasonality::isConsistent(const InflationTermStructure& iTS) const
65
0
    {
66
        // If multi-year is the specification consistent with the term structure start date?
67
        // We do NOT test daily seasonality because this will, in general, never be consistent
68
        // given weekends, holidays, leap years, etc.
69
0
        if(this->frequency() == Daily) return true;
70
0
        if(Size(this->frequency()) == seasonalityFactors().size()) return true;
71
72
        // how many years do you need to test?
73
0
        Size nTest = seasonalityFactors().size() / this->frequency();
74
        // ... relative to the start of the inflation curve
75
0
        std::pair<Date,Date> lim = inflationPeriod(iTS.baseDate(), iTS.frequency());
76
0
        Date curveBaseDate = lim.second;
77
0
        Real factorBase = this->seasonalityFactor(curveBaseDate);
78
79
0
        Real eps = 0.00001;
80
0
        for (Size i = 1; i < nTest; i++) {
81
0
            Real factorAt = this->seasonalityFactor(curveBaseDate+Period(i,Years));
82
0
            QL_REQUIRE(std::fabs(factorAt-factorBase)<eps,"seasonality is inconsistent with inflation term structure, factors "
83
0
                       << factorBase << " and later factor " << factorAt << ", " << i << " years later from inflation curve "
84
0
                       <<" with base date at " << curveBaseDate);
85
0
        }
86
87
0
        return true;
88
0
    }
89
90
91
    MultiplicativePriceSeasonality::MultiplicativePriceSeasonality(const Date& seasonalityBaseDate, const Frequency frequency,
92
                                                                   const std::vector<Rate>& seasonalityFactors)
93
0
    {
94
0
        MultiplicativePriceSeasonality::set(seasonalityBaseDate, frequency, seasonalityFactors);
95
0
    }
96
97
    void MultiplicativePriceSeasonality::set(const Date& seasonalityBaseDate, const Frequency frequency,
98
                                             const std::vector<Rate>& seasonalityFactors)
99
0
    {
100
0
        frequency_ = frequency;
101
0
        seasonalityFactors_ = std::vector<Rate>(seasonalityFactors.size());
102
0
        for(Size i=0; i<seasonalityFactors.size(); i++) {
103
0
            seasonalityFactors_[i] = seasonalityFactors[i];
104
0
        }
105
0
        seasonalityBaseDate_ = seasonalityBaseDate;
106
        // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall)
107
0
        validate();
108
0
    }
109
110
0
    Date MultiplicativePriceSeasonality::seasonalityBaseDate() const {
111
0
        return seasonalityBaseDate_;
112
0
    }
113
114
0
    Frequency MultiplicativePriceSeasonality::frequency() const {
115
0
        return frequency_;
116
0
    }
117
118
0
    std::vector<Rate> MultiplicativePriceSeasonality::seasonalityFactors() const {
119
0
        return seasonalityFactors_;
120
0
    }
121
122
123
    Rate MultiplicativePriceSeasonality::correctZeroRate(const Date &d,
124
                                                         const Rate r,
125
0
                                                         const InflationTermStructure& iTS) const {
126
        // Mimic the logic in ZeroInflationIndex::forecastFixing for choosing the
127
        // curveBaseDate and effective fixing date. This means that we should retrieve
128
        // the input seasonality adjustments when we look at I_{SA}(t) / I_{NSA}(t).
129
0
        Date curveBaseDate = iTS.baseDate();
130
0
        Date effectiveFixingDate = inflationPeriod(d, iTS.frequency()).first;
131
        
132
0
        return seasonalityCorrection(r, effectiveFixingDate, iTS.dayCounter(), curveBaseDate, true);
133
0
    }
134
135
136
    Rate MultiplicativePriceSeasonality::correctYoYRate(const Date &d,
137
                                                        const Rate r,
138
0
                                                        const InflationTermStructure& iTS) const {
139
0
        std::pair<Date,Date> lim = inflationPeriod(iTS.baseDate(), iTS.frequency());
140
0
        Date curveBaseDate = lim.second;
141
0
        return seasonalityCorrection(r, d, iTS.dayCounter(), curveBaseDate, false);
142
0
    }
143
144
145
0
    Real MultiplicativePriceSeasonality::seasonalityFactor(const Date &to) const {
146
147
0
        Date from = seasonalityBaseDate();
148
0
        Frequency factorFrequency = frequency();
149
0
        Size nFactors = seasonalityFactors().size();
150
0
        Period factorPeriod(factorFrequency);
151
0
        Size which = 0;
152
0
        if (from==to) {
153
0
            which = 0;
154
0
        } else {
155
            // days, weeks, months, years are the only time unit possibilities
156
0
            Integer diffDays = std::abs(to - from);  // in days
157
0
            Integer dir = 1;
158
0
            if(from > to)dir = -1;
159
0
            Integer diff;
160
0
            if (factorPeriod.units() == Days) {
161
0
                diff = dir*diffDays;
162
0
            } else if (factorPeriod.units() == Weeks) {
163
0
                diff = dir * (diffDays / 7);
164
0
            } else if (factorPeriod.units() == Months) {
165
0
                std::pair<Date,Date> lim = inflationPeriod(to, factorFrequency);
166
0
                diff = diffDays / (31*factorPeriod.length());
167
0
                Date go = from + dir*diff*factorPeriod;
168
0
                while ( !(lim.first <= go && go <= lim.second) ) {
169
0
                    go += dir*factorPeriod;
170
0
                    diff++;
171
0
                }
172
0
                diff=dir*diff;
173
0
            } else if (factorPeriod.units() == Years) {
174
0
                QL_FAIL("seasonality period time unit is not allowed to be : " << factorPeriod.units());
175
0
            } else {
176
0
                QL_FAIL("Unknown time unit: " << factorPeriod.units());
177
0
            }
178
            // now adjust to the available number of factors, direction dependent
179
180
0
            if (dir==1) {
181
0
                which = diff % nFactors;
182
0
            } else {
183
0
                which = (nFactors - (-diff % nFactors)) % nFactors;
184
0
            }
185
0
        }
186
187
0
        return seasonalityFactors()[which];
188
0
    }
189
190
191
    Rate MultiplicativePriceSeasonality::seasonalityCorrection(Rate rate,
192
                                                               const Date& atDate,
193
                                                               const DayCounter& dc,
194
                                                               const Date& curveBaseDate,
195
0
                                                               const bool isZeroRate) const {
196
        // need _two_ corrections in order to get: seasonality = factor[atDate-seasonalityBase] / factor[reference-seasonalityBase]
197
        // i.e. for ZERO inflation rates you have the true fixing at the curve base so this factor must be normalized to one
198
        //      for YoY inflation rates your reference point is the year before
199
200
0
        Real factorAt = this->seasonalityFactor(atDate);
201
202
        //Getting seasonality correction for either ZC or YoY
203
0
        Rate f;
204
0
        if (isZeroRate) {
205
0
            Rate factorBase = this->seasonalityFactor(curveBaseDate);
206
0
            Real seasonalityAt = factorAt / factorBase;
207
0
            std::pair<Date,Date> p = inflationPeriod(atDate,frequency());
208
0
            Time timeFromCurveBase = dc.yearFraction(curveBaseDate, p.first);
209
0
            f = std::pow(seasonalityAt, 1/timeFromCurveBase);
210
0
        }
211
0
        else {
212
0
            Rate factor1Ybefore = this->seasonalityFactor(atDate - Period(1,Years));
213
0
            f = factorAt / factor1Ybefore;
214
0
        }
215
216
0
        return (rate + 1)*f - 1;
217
0
    }
218
219
220
0
    Real KerkhofSeasonality::seasonalityFactor(const Date &to) const {
221
222
0
        Integer dir = 1;
223
0
        Date from = seasonalityBaseDate();
224
0
        Size fromMonth = from.month();
225
0
        Size toMonth = to.month();
226
227
0
        Period factorPeriod(frequency());
228
229
0
        if (toMonth < fromMonth)
230
0
        {
231
0
            Size dummy = fromMonth;
232
0
            fromMonth = toMonth;
233
0
            toMonth = dummy;
234
0
            dir = 0; // We calculate invers Factor in loop
235
0
        }
236
237
0
        QL_REQUIRE(seasonalityFactors().size() == 12 &&
238
0
                   factorPeriod.units() == Months,
239
0
                   "12 monthly seasonal factors needed for Kerkhof Seasonality:"
240
0
                   << " got " << seasonalityFactors().size());
241
242
0
        Real seasonalCorrection = 1.0;
243
0
        for (Size i = fromMonth ; i<toMonth; i++)
244
0
        {
245
0
            seasonalCorrection *= seasonalityFactors()[i];
246
247
0
        }
248
249
0
        if (dir == 0) // invers Factor required
250
0
        {
251
0
            seasonalCorrection = 1/seasonalCorrection;
252
0
        }
253
254
0
        return seasonalCorrection;
255
0
    }
256
257
    Rate KerkhofSeasonality::seasonalityCorrection(Rate rate,
258
                                                   const Date& atDate,
259
                                                   const DayCounter& dc,
260
                                                   const Date& curveBaseDate,
261
0
                                                   const bool isZeroRate) const {
262
263
0
        Real indexFactor = this->seasonalityFactor(atDate);
264
265
        // Getting seasonality correction
266
0
        Rate f;
267
0
        if (isZeroRate) {
268
0
            std::pair<Date,Date> lim = inflationPeriod(curveBaseDate, Monthly);
269
0
            Time timeFromCurveBase = dc.yearFraction(lim.first, atDate);
270
0
            f = std::pow(indexFactor, 1/timeFromCurveBase);
271
0
        }
272
0
        else {
273
0
            QL_FAIL("Seasonal Kerkhof model is not defined on YoY rates");
274
0
        }
275
276
0
        return (rate + 1)*f - 1;
277
0
    }
278
279
}