Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
inflationcurve.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2022 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include "toplevelfixture.hpp"
20#include <boost/test/unit_test.hpp>
21
22#include <ql/termstructures/inflationtermstructure.hpp>
24
25#include <ql/indexes/inflation/euhicp.hpp>
26#include <ql/termstructures/inflation/inflationhelpers.hpp>
27#include <ql/termstructures/yield/flatforward.hpp>
28#include <ql/time/calendars/nullcalendar.hpp>
29#include <ql/time/daycounters/actual365fixed.hpp>
31
32using namespace boost::unit_test_framework;
33using namespace QuantLib;
34using namespace std;
35
36namespace {
37
38struct CommonData {
39
40 Date today;
41 Real tolerance;
42 DayCounter dayCounter;
43 std::vector<Period> zeroCouponPillars;
44 std::vector<Rate> zeroCouponQuotes;
45 std::map<Date, Rate> cpiFixings;
46 Period obsLag;
47
48 CommonData()
49 : today(15, Aug, 2022), tolerance(1e-6), dayCounter(Actual365Fixed()),
50 zeroCouponPillars({1 * Years, 2 * Years, 3 * Years, 5 * Years}), zeroCouponQuotes({0.06, 0.04, 0.03, 0.02}),
51 cpiFixings({{Date(1, May, 2022), 98.}, {Date(1, June, 2022), 100.}, {Date(1, July, 2022), 104.}}),
52 obsLag(2, Months){};
53};
54
55void addFixings(const std::map<Date, Rate> fixings, ZeroInflationIndex& index) {
56 index.clearFixings();
57 for (const auto& fixing : fixings) {
58 index.addFixing(fixing.first, fixing.second, true);
59 }
60};
61
62QuantLib::ext::shared_ptr<Seasonality> buildSeasonalityCurve() {
63 std::vector<double> factors{0.99, 1.01, 0.98, 1.02, 0.97, 1.03, 0.96, 1.04, 0.95, 1.05, 0.94, 1.06};
64 Date seasonalityBaseDate(1, Jan, 2022);
65 return QuantLib::ext::make_shared<MultiplicativePriceSeasonality>(seasonalityBaseDate, Monthly, factors);
66}
67
68QuantLib::ext::shared_ptr<ZeroInflationCurve>
69buildZeroInflationCurve(CommonData& cd, bool useLastKnownFixing, const QuantLib::ext::shared_ptr<ZeroInflationIndex>& index,
70 const bool isInterpolated, const QuantLib::ext::shared_ptr<Seasonality>& seasonality = nullptr) {
71 Date today = Settings::instance().evaluationDate();
72 QuantLib::ext::shared_ptr<SimpleQuote> flatZero = QuantLib::ext::make_shared<SimpleQuote>(0.01);
73 DayCounter dc = cd.dayCounter;
74 Calendar fixingCalendar = NullCalendar();
75 BusinessDayConvention bdc = ModifiedFollowing;
76
77 QuantLib::ext::shared_ptr<YieldTermStructure> discountTS =
78 QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), Handle<Quote>(flatZero), dc);
79
80 std::vector<QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper>> helpers;
81 for (size_t i = 0; i < cd.zeroCouponQuotes.size(); ++i) {
82 Date maturity = today + cd.zeroCouponPillars[i];
83 Rate quote = cd.zeroCouponQuotes[i];
84 QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper> instrument =
85 QuantLib::ext::make_shared<ZeroCouponInflationSwapHelper>(
86 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(quote)), cd.obsLag, maturity, fixingCalendar, bdc, dc,
87 index, isInterpolated ? CPI::Linear : CPI::Flat, Handle<YieldTermStructure>(discountTS), today);
88 helpers.push_back(instrument);
89 }
90 Rate baseRate = QuantExt::ZeroInflation::guessCurveBaseRate(useLastKnownFixing, today, today, cd.zeroCouponPillars[0],
91 cd.dayCounter, cd.obsLag, cd.zeroCouponQuotes[0],
92 cd.obsLag, cd.dayCounter, index, isInterpolated);
93 QuantLib::ext::shared_ptr<ZeroInflationCurve> curve = QuantLib::ext::make_shared<QuantExt::PiecewiseZeroInflationCurve<Linear>>(
94 today, fixingCalendar, dc, cd.obsLag, index->frequency(), baseRate, helpers, 1e-10, index, useLastKnownFixing);
95 if (seasonality) {
96 curve->setSeasonality(seasonality);
97 }
98 return curve;
99};
100
101} // namespace
102
103BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
104
105BOOST_AUTO_TEST_SUITE(InflationCurveTest)
106
107BOOST_AUTO_TEST_CASE(testZeroInflationCurveNonInterpolatedLastMonthFixingUnknown) {
108
109 CommonData cd;
110 Settings::instance().evaluationDate() = cd.today;
111 bool isInterpolated = false;
112 bool useLastKnownFixingDateAsBaseDate = false;
113 // Build Curve and Index
114 QuantLib::ext::shared_ptr<ZeroInflationIndex> curveBuildIndex = QuantLib::ext::make_shared<EUHICPXT>(false);
115 addFixings(cd.cpiFixings, *curveBuildIndex);
116 auto curve = buildZeroInflationCurve(cd, useLastKnownFixingDateAsBaseDate, curveBuildIndex, isInterpolated);
117
118 BOOST_CHECK_NO_THROW(curve->zeroRate(1.0));
119
120 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
121
122 std::vector<Date> expectedPillarDates{Date(1, June, 2022), Date(1, June, 2023), Date(1, June, 2024),
123 Date(1, June, 2025), Date(1, June, 2027)};
124
125 std::vector<Real> expectedZeroRates{0.06, 0.06, 0.04, 0.03, 0.02};
126
127 std::vector<Real> expectedCPIs{100., 106., 108.171622850024, 109.281549591561, 110.414070537467};
128
129 BOOST_CHECK_EQUAL(curve->baseDate(), expectedPillarDates.front());
130
131 BOOST_CHECK_EQUAL(curve->dates().size(), expectedPillarDates.size());
132
133 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
134 BOOST_CHECK_EQUAL(curve->dates().at(i), expectedPillarDates.at(i));
135 BOOST_CHECK_CLOSE(curve->zeroRate(curve->dates().at(i), 0 * Days), expectedZeroRates.at(i), cd.tolerance);
136 }
137
138 // Check index fixing forecasts
139
140 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
141 auto forwardCPI = index->fixing(expectedPillarDates.at(i));
142 BOOST_CHECK_CLOSE(forwardCPI, expectedCPIs.at(i), cd.tolerance);
143 }
144}
145
146BOOST_AUTO_TEST_CASE(testZeroInflationCurveNonInterpolatedLastMonthFixing) {
147
148 CommonData cd;
149 Settings::instance().evaluationDate() = cd.today;
150 bool isInterpolated = false;
151 bool useLastKnownFixingDateAsBaseDate = true;
152 // Build Curve and Index
153 QuantLib::ext::shared_ptr<ZeroInflationIndex> curveBuildIndex = QuantLib::ext::make_shared<EUHICPXT>(false);
154 addFixings(cd.cpiFixings, *curveBuildIndex);
155 auto curve = buildZeroInflationCurve(cd, useLastKnownFixingDateAsBaseDate, curveBuildIndex, isInterpolated);
156
157 BOOST_CHECK_NO_THROW(curve->zeroRate(1.0));
158
159 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
160
161 std::vector<Date> expectedPillarDates{Date(1, July, 2022), Date(1, June, 2023), Date(1, June, 2024),
162 Date(1, June, 2025), Date(1, June, 2027)};
163
164 std::vector<Real> expectedZeroRates{0.02097086546, 0.02097086546, 0.02068868041, 0.01710609424437, 0.01223686945};
165
166 std::vector<Real> expectedCPIs{104, 106., 108.171622850024, 109.281549591561, 110.414070537467};
167
168 BOOST_CHECK_EQUAL(curve->baseDate(), expectedPillarDates.front());
169
170 BOOST_CHECK_EQUAL(curve->dates().size(), expectedPillarDates.size());
171
172 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
173 BOOST_CHECK_EQUAL(curve->dates().at(i), expectedPillarDates.at(i));
174 BOOST_CHECK_CLOSE(curve->zeroRate(curve->dates().at(i), 0 * Days), expectedZeroRates.at(i), cd.tolerance);
175 }
176
177 // Check index fixing forecasts
178
179 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
180 auto forwardCPI = index->fixing(expectedPillarDates.at(i));
181 BOOST_CHECK_CLOSE(forwardCPI, expectedCPIs.at(i), cd.tolerance);
182 }
183}
184
185BOOST_AUTO_TEST_CASE(testZeroInflationCurveInterpolatedLastMonthFixing) {
186
187 CommonData cd;
188 Settings::instance().evaluationDate() = cd.today;
189 bool isInterpolated = true;
190 bool useLastKnownFixingDateAsBaseDate = true;
191 // Build Curve and Index
192 QuantLib::ext::shared_ptr<ZeroInflationIndex> curveBuildIndex = QuantLib::ext::make_shared<EUHICPXT>(false);
193 addFixings(cd.cpiFixings, *curveBuildIndex);
194 auto curve = buildZeroInflationCurve(cd, useLastKnownFixingDateAsBaseDate, curveBuildIndex, isInterpolated);
195
196 BOOST_CHECK_NO_THROW(curve->zeroRate(1.0));
197
198 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
199
200 std::vector<Date> expectedPillarDates{Date(1, July, 2022), Date(1, July, 2023), Date(1, July, 2024),
201 Date(1, July, 2025), Date(1, July, 2027)};
202
203 std::vector<Real> expectedZeroRates{0.03945267289772, 0.03945267289772, 0.02921461897637, 0.02277721089513,
204 0.01564691567};
205
206 std::vector<Date> fixingDates{Date(15, June, 2022), Date(15, June, 2023), Date(15, June, 2024),
207 Date(15, June, 2025), Date(15, June, 2027)};
208
209 // Base CPI is 100 + (104-100)*14/31, and then for the forward cpi it is baseCPI * (1+r)^T
210 std::vector<Real> expectedCPIs{101.806451613, 107.914838710, 110.125690876, 111.255667907, 112.408647296};
211
212 BOOST_CHECK_EQUAL(curve->baseDate(), expectedPillarDates.front());
213
214 BOOST_CHECK_EQUAL(curve->dates().size(), expectedPillarDates.size());
215
216 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
217 BOOST_CHECK_EQUAL(curve->dates().at(i), expectedPillarDates.at(i));
218 BOOST_CHECK_CLOSE(curve->zeroRate(curve->dates().at(i), 0 * Days), expectedZeroRates.at(i), 1e-6);
219 }
220
221 // Check index fixing forecasts
222
223 for (size_t i = 0; i < fixingDates.size(); ++i) {
224 Date fixDate1 = inflationPeriod(fixingDates.at(i), index->frequency()).first;
225 Date fixDate2 = inflationPeriod(fixingDates.at(i), index->frequency()).second + 1 * Days;
226 Rate cpi1 = index->fixing(fixDate1);
227 Rate cpi2 = index->fixing(fixDate2);
228 auto forwardCPI = cpi1 + (cpi2 - cpi1) * 14 / 31;
229 BOOST_CHECK_CLOSE(forwardCPI, expectedCPIs.at(i), cd.tolerance);
230 }
231}
232
233BOOST_AUTO_TEST_CASE(testZeroInflationCurveNonInterpolatedLastMonthFixingUnknownWithSeasonality) {
234
235 CommonData cd;
236 Settings::instance().evaluationDate() = cd.today;
237 bool isInterpolated = false;
238 bool useLastKnownFixingDateAsBaseDate = false;
239 // Build Curve and Index
240 QuantLib::ext::shared_ptr<ZeroInflationIndex> curveBuildIndex = QuantLib::ext::make_shared<EUHICPXT>(false);
241 addFixings(cd.cpiFixings, *curveBuildIndex);
242 auto seasonalityCurve = buildSeasonalityCurve();
243 auto curve = buildZeroInflationCurve(cd, useLastKnownFixingDateAsBaseDate, curveBuildIndex, isInterpolated,
244 seasonalityCurve);
245 BOOST_CHECK_NO_THROW(curve->zeroRate(1.0));
246
247 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
248
249 std::vector<Date> expectedPillarDates{Date(1, June, 2022), Date(1, June, 2023), Date(1, June, 2024),
250 Date(1, June, 2025), Date(1, June, 2027)};
251
252 std::vector<Real> expectedZeroRates{0.06, 0.06, 0.04, 0.03, 0.02};
253
254 std::vector<Real> expectedCPIs{100., 106., 108.171622850024, 109.281549591561, 110.414070537467};
255
256 BOOST_CHECK_EQUAL(curve->baseDate(), expectedPillarDates.front());
257
258 BOOST_CHECK_EQUAL(curve->dates().size(), expectedPillarDates.size());
259
260 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
261 BOOST_CHECK_EQUAL(curve->dates().at(i), expectedPillarDates.at(i));
262 BOOST_CHECK_CLOSE(curve->zeroRate(curve->dates().at(i), 0 * Days), expectedZeroRates.at(i), cd.tolerance);
263 BOOST_CHECK_CLOSE(curve->data().at(i), expectedZeroRates.at(i), cd.tolerance);
264 }
265
266 // Check index fixing forecasts
267
268 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
269 auto forwardCPI = index->fixing(expectedPillarDates.at(i));
270 BOOST_CHECK_CLOSE(forwardCPI, expectedCPIs.at(i), cd.tolerance);
271 }
272}
273
274BOOST_AUTO_TEST_CASE(testZeroInflationCurveNonInterpolatedLastMonthFixingWithSeasonality) {
275
276 CommonData cd;
277 Settings::instance().evaluationDate() = cd.today;
278 bool isInterpolated = false;
279 bool useLastKnownFixingDateAsBaseDate = true;
280 // Build Curve and Index
281 QuantLib::ext::shared_ptr<ZeroInflationIndex> curveBuildIndex = QuantLib::ext::make_shared<EUHICPXT>(false);
282 addFixings(cd.cpiFixings, *curveBuildIndex);
283 auto seasonalityCurve = buildSeasonalityCurve();
284 auto curve = buildZeroInflationCurve(cd, useLastKnownFixingDateAsBaseDate, curveBuildIndex, isInterpolated,
285 seasonalityCurve);
286
287 BOOST_CHECK_NO_THROW(curve->zeroRate(1.0));
288
289 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
290
291 std::vector<Date> expectedPillarDates{Date(1, July, 2022), Date(1, June, 2023), Date(1, June, 2024),
292 Date(1, June, 2025), Date(1, June, 2027)};
293
294 std::vector<Real> expectedZeroRates{0.02097086546, 0.02097086546, 0.02068868041, 0.01710609424437, 0.01223686945};
295 std::vector<Real> expectedZeroRatesWithoutSeasonality{0.02097086546, -0.05439424967, -0.01603861959, -0.00711164972,
296 -0.00213855283};
297 std::vector<Real> expectedCPIs{104, 106., 108.171622850024, 109.281549591561, 110.414070537467};
298
299 BOOST_CHECK_EQUAL(curve->baseDate(), expectedPillarDates.front());
300
301 BOOST_CHECK_EQUAL(curve->dates().size(), expectedPillarDates.size());
302
303 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
304 BOOST_CHECK_EQUAL(curve->dates().at(i), expectedPillarDates.at(i));
305 BOOST_CHECK_CLOSE(curve->data().at(i), expectedZeroRatesWithoutSeasonality.at(i), cd.tolerance);
306 BOOST_CHECK_CLOSE(curve->zeroRate(curve->dates().at(i), 0 * Days), expectedZeroRates.at(i), cd.tolerance);
307 }
308 // Check index fixing forecasts
309
310 for (size_t i = 0; i < expectedPillarDates.size(); ++i) {
311 auto forwardCPI = index->fixing(expectedPillarDates.at(i));
312 BOOST_CHECK_CLOSE(forwardCPI, expectedCPIs.at(i), cd.tolerance);
313 }
314}
315
316BOOST_AUTO_TEST_SUITE_END()
317
318BOOST_AUTO_TEST_SUITE_END()
some inflation related utilities.
BOOST_AUTO_TEST_CASE(testZeroInflationCurveNonInterpolatedLastMonthFixingUnknown)
QuantLib::Rate guessCurveBaseRate(const bool baseDateLastKnownFixing, const QuantLib::Date &swapStart, const QuantLib::Date &asof, const QuantLib::Period &swapTenor, const QuantLib::DayCounter &swapZCLegDayCounter, const QuantLib::Period &swapObsLag, const QuantLib::Rate zeroCouponRate, const QuantLib::Period &curveObsLag, const QuantLib::DayCounter &curveDayCounter, const QuantLib::ext::shared_ptr< QuantLib::ZeroInflationIndex > &index, const bool interpolated, const QuantLib::ext::shared_ptr< QuantLib::Seasonality > &seasonality)
Definition: inflation.cpp:191
Piecewise interpolated zero inflation term structure.
Fixture that can be used at top level.