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) 2008 Piero Del Boca
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#include "inflation.hpp"
22#include "utilities.hpp"
23#include <ql/cashflows/indexedcashflow.hpp>
24#include <ql/indexes/inflation/ukrpi.hpp>
25#include <ql/indexes/inflation/euhicp.hpp>
26#include <ql/indexes/inflation/ukhicp.hpp>
27#include <ql/termstructures/inflation/piecewisezeroinflationcurve.hpp>
28#include <ql/termstructures/inflation/piecewiseyoyinflationcurve.hpp>
29#include <ql/termstructures/yield/flatforward.hpp>
30#include <ql/time/date.hpp>
31#include <ql/time/daycounters/actual360.hpp>
32#include <ql/time/daycounters/thirty360.hpp>
33#include <ql/time/calendars/unitedkingdom.hpp>
34#include <ql/time/calendars/target.hpp>
35#include <ql/time/schedule.hpp>
36#include <ql/pricingengines/swap/discountingswapengine.hpp>
37#include <ql/instruments/zerocouponinflationswap.hpp>
38#include <ql/termstructures/inflation/inflationhelpers.hpp>
39#include <ql/cashflows/yoyinflationcoupon.hpp>
40#include <ql/cashflows/inflationcouponpricer.hpp>
41#include <ql/cashflows/fixedratecoupon.hpp>
42#include <ql/instruments/yearonyearinflationswap.hpp>
43
44#include <functional>
45
46
47using boost::unit_test_framework::test_suite;
48
49using namespace QuantLib;
50
51using std::fabs;
52using std::pow;
53using std::vector;
54
55#undef REPORT_FAILURE
56#define REPORT_FAILURE(d, res, periodName) \
57 BOOST_ERROR("wrong " << periodName << " inflation period for Date (1 " \
58 << d << "), Start Date ( " \
59 << res.first << "), End Date (" \
60 << res.second << ")"); \
61
62namespace inflation_test {
63
64 struct Datum {
65 Date date;
66 Rate rate;
67 };
68
69 ext::shared_ptr<YieldTermStructure> nominalTermStructure() {
70 Date evaluationDate(13, August, 2007);
71 return ext::shared_ptr<YieldTermStructure>(
72 new FlatForward(evaluationDate, 0.05, Actual360()));
73 }
74
75 template <class T>
76 std::vector<ext::shared_ptr<BootstrapHelper<T> > > makeHelpers(
77 const std::vector<Datum>& iiData,
78 std::function<ext::shared_ptr<BootstrapHelper<T> >(const Handle<Quote>&, const Date&)>
79 makeHelper) {
80
81 std::vector<ext::shared_ptr<BootstrapHelper<T> > > instruments;
82 for (Datum datum : iiData) {
83 Date maturity = datum.date;
84 Handle<Quote> quote(ext::shared_ptr<Quote>(new SimpleQuote(datum.rate / 100.0)));
85 auto anInstrument = makeHelper(quote, maturity);
86 instruments.push_back(anInstrument);
87 }
88
89 return instruments;
90 }
91}
92
93//===========================================================================================
94// zero inflation tests, index, termstructure, and swaps
95//===========================================================================================
96
97void InflationTest::testZeroIndex() {
98 BOOST_TEST_MESSAGE("Testing zero inflation indices...");
99
100 QL_DEPRECATED_DISABLE_WARNING
101
102 EUHICP euhicp(true);
103 if (euhicp.name() != "EU HICP"
104 || euhicp.frequency() != Monthly
105 || euhicp.revised()
106 || !euhicp.interpolated()
107 || euhicp.availabilityLag() != 1*Months) {
108 BOOST_ERROR("wrong EU HICP data ("
109 << euhicp.name() << ", "
110 << euhicp.frequency() << ", "
111 << euhicp.revised() << ", "
112 << euhicp.interpolated() << ", "
113 << euhicp.availabilityLag() << ")");
114 }
115
116 UKRPI ukrpi;
117 if (ukrpi.name() != "UK RPI"
118 || ukrpi.frequency() != Monthly
119 || ukrpi.revised()
120 || ukrpi.interpolated()
121 || ukrpi.availabilityLag() != 1*Months) {
122 BOOST_ERROR("wrong UK RPI data ("
123 << ukrpi.name() << ", "
124 << ukrpi.frequency() << ", "
125 << ukrpi.revised() << ", "
126 << ukrpi.interpolated() << ", "
127 << ukrpi.availabilityLag() << ")");
128 }
129
130 UKHICP ukhicp;
131 if (ukhicp.name() != "UK HICP"
132 || ukhicp.frequency() != Monthly
133 || ukhicp.revised()
134 || ukhicp.availabilityLag() != 1 * Months) {
135 BOOST_ERROR("wrong UK HICP data ("
136 << ukhicp.name() << ", "
137 << ukhicp.frequency() << ", "
138 << ukhicp.revised() << ", "
139 << ", " << ukhicp.availabilityLag() << ")");
140 }
141
142 QL_DEPRECATED_ENABLE_WARNING
143
144 // Retrieval test.
145 //----------------
146 // make sure of the evaluation date
147 Date evaluationDate(13, August, 2007);
148 evaluationDate = UnitedKingdom().adjust(evaluationDate);
149 Settings::instance().evaluationDate() = evaluationDate;
150
151 // fixing data
152 Date from(1, January, 2005);
153 Date to(13, August, 2007);
154 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
155 .withTenor(1*Months)
156 .withCalendar(UnitedKingdom())
157 .withConvention(ModifiedFollowing);
158
159 Real fixData[] = { 189.9, 189.9, 189.6, 190.5, 191.6, 192.0,
160 192.2, 192.2, 192.6, 193.1, 193.3, 193.6,
161 194.1, 193.4, 194.2, 195.0, 196.5, 197.7,
162 198.5, 198.5, 199.2, 200.1, 200.4, 201.1,
163 202.7, 201.6, 203.1, 204.4, 205.4, 206.2,
164 207.3, 206.1 };
165
166 auto iir = ext::make_shared<UKRPI>();
167 for (Size i=0; i<LENGTH(fixData); i++) {
168 iir->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
169 }
170
171 Date todayMinusLag = evaluationDate - iir->availabilityLag();
172 std::pair<Date,Date> lim = inflationPeriod(todayMinusLag, iir->frequency());
173 todayMinusLag = lim.first;
174
175 Real eps = 1.0e-8;
176
177 // -1 because last value not yet available,
178 // (no TS so can't forecast).
179 for (Size i=0; i<rpiSchedule.size()-1;i++) {
180 std::pair<Date,Date> lim = inflationPeriod(rpiSchedule[i],
181 iir->frequency());
182 for (Date d=lim.first; d<=lim.second; d++) {
183 if (d < inflationPeriod(todayMinusLag,iir->frequency()).first) {
184 if (std::fabs(x: iir->fixing(fixingDate: d) - fixData[i]) > eps)
185 BOOST_ERROR("Fixings not constant within a period: "
186 << iir->fixing(d)
187 << ", should be " << fixData[i]);
188 }
189 }
190 }
191}
192
193
194
195void InflationTest::testZeroTermStructure() {
196 BOOST_TEST_MESSAGE("Testing zero inflation term structure...");
197
198 using namespace inflation_test;
199
200 // try the Zero UK
201 Calendar calendar = UnitedKingdom();
202 BusinessDayConvention bdc = ModifiedFollowing;
203 Date evaluationDate(13, August, 2007);
204 evaluationDate = calendar.adjust(evaluationDate);
205 Settings::instance().evaluationDate() = evaluationDate;
206
207 // fixing data
208 Date from(1, January, 2005);
209 Date to(13, August, 2007);
210 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
211 .withTenor(1*Months)
212 .withCalendar(UnitedKingdom())
213 .withConvention(ModifiedFollowing);
214
215 Real fixData[] = {
216 189.9, 189.9, 189.6, 190.5, 191.6, 192.0,
217 192.2, 192.2, 192.6, 193.1, 193.3, 193.6,
218 194.1, 193.4, 194.2, 195.0, 196.5, 197.7,
219 198.5, 198.5, 199.2, 200.1, 200.4, 201.1,
220 202.7, 201.6, 203.1, 204.4, 205.4, 206.2,
221 207.3};
222
223 RelinkableHandle<ZeroInflationTermStructure> hz;
224 auto ii = ext::make_shared<UKRPI>(args&: hz);
225 for (Size i=0; i<LENGTH(fixData); i++) {
226 ii->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
227 }
228
229 Handle<YieldTermStructure> nominalTS(nominalTermStructure());
230
231 // now build the zero inflation curve
232 std::vector<Datum> zcData = {
233 { .date: Date(13, August, 2008), .rate: 2.93 },
234 { .date: Date(13, August, 2009), .rate: 2.95 },
235 { .date: Date(13, August, 2010), .rate: 2.965 },
236 { .date: Date(15, August, 2011), .rate: 2.98 },
237 { .date: Date(13, August, 2012), .rate: 3.0 },
238 { .date: Date(13, August, 2014), .rate: 3.06 },
239 { .date: Date(13, August, 2017), .rate: 3.175 },
240 { .date: Date(13, August, 2019), .rate: 3.243 },
241 { .date: Date(15, August, 2022), .rate: 3.293 },
242 { .date: Date(14, August, 2027), .rate: 3.338 },
243 { .date: Date(13, August, 2032), .rate: 3.348 },
244 { .date: Date(15, August, 2037), .rate: 3.348 },
245 { .date: Date(13, August, 2047), .rate: 3.308 },
246 { .date: Date(13, August, 2057), .rate: 3.228 }
247 };
248
249 Period observationLag = Period(3, Months);
250 DayCounter dc = Thirty360(Thirty360::BondBasis);
251 Frequency frequency = Monthly;
252
253 auto makeHelper = [&](const Handle<Quote>& quote, const Date& maturity) {
254 return ext::make_shared<ZeroCouponInflationSwapHelper>(
255 args: quote, args&: observationLag, args: maturity, args&: calendar, args&: bdc, args&: dc, args&: ii, args: CPI::AsIndex, args&: nominalTS);
256 };
257 auto helpers = makeHelpers<ZeroInflationTermStructure>(iiData: zcData, makeHelper);
258
259 Rate baseZeroRate = zcData[0].rate/100.0;
260 ext::shared_ptr<PiecewiseZeroInflationCurve<Linear> > pZITS(
261 new PiecewiseZeroInflationCurve<Linear>(
262 evaluationDate, calendar, dc, observationLag,
263 frequency, baseZeroRate, helpers));
264 hz.linkTo(h: pZITS);
265
266 //===========================================================================================
267 // first check that the quoted swaps are repriced correctly
268
269 const Real eps = 1.0e-6;
270 auto engine = ext::make_shared<DiscountingSwapEngine>(args&: nominalTS);
271
272 for (const auto& datum: zcData) {
273 ZeroCouponInflationSwap nzcis(Swap::Payer,
274 1000000.0,
275 evaluationDate,
276 datum.date,
277 calendar, bdc, dc,
278 datum.rate/100.0,
279 ii, observationLag,
280 CPI::AsIndex);
281 nzcis.setPricingEngine(engine);
282
283 BOOST_CHECK_MESSAGE(std::fabs(nzcis.NPV()) < eps,
284 "zero-coupon inflation swap does not reprice to zero"
285 << "\n NPV: " << nzcis.NPV()
286 << "\n maturity: " << nzcis.maturityDate()
287 << "\n rate: " << datum.rate);
288 }
289
290 //===========================================================================================
291 // now test the forecasting capability of the index.
292
293 from = hz->referenceDate();
294 to = hz->maxDate()-1*Months; // a bit of margin for adjustments
295 Schedule testIndex = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
296 .withTenor(1*Months)
297 .withCalendar(UnitedKingdom())
298 .withConvention(ModifiedFollowing);
299
300 // we are testing UKRPI which is not interpolated
301 Date bd = hz->baseDate();
302 Real bf = ii->fixing(fixingDate: bd);
303 for (const auto& d : testIndex) {
304 Real z = hz->zeroRate(d, instObsLag: Period(0, Days));
305 Real t = hz->dayCounter().yearFraction(d1: bd, d2: inflationPeriod(d, ii->frequency()).first);
306 Real calc = bf * std::pow(x: 1+z, y: t);
307 if (t<=0)
308 calc = ii->fixing(fixingDate: d,forecastTodaysFixing: false); // still historical
309 if (std::fabs(x: calc - ii->fixing(fixingDate: d,forecastTodaysFixing: true)) > eps)
310 BOOST_ERROR("inflation index does not forecast correctly"
311 << "\n date: " << d
312 << "\n base date: " << bd
313 << "\n base fixing: " << bf
314 << "\n expected: " << calc
315 << "\n forecast: " << ii->fixing(d,true));
316 }
317
318 //===========================================================================================
319 // Add a seasonality correction. The curve should recalculate and still reprice the swaps.
320
321 Date nextBaseDate = inflationPeriod(hz->baseDate(), ii->frequency()).second;
322 Date seasonalityBaseDate(31, January, nextBaseDate.year());
323 vector<Rate> seasonalityFactors = {
324 1.003245,
325 1.000000,
326 0.999715,
327 1.000495,
328 1.000929,
329 0.998687,
330 0.995949,
331 0.994682,
332 0.995949,
333 1.000519,
334 1.003705,
335 1.004186
336 };
337
338 ext::shared_ptr<MultiplicativePriceSeasonality> nonUnitSeasonality =
339 ext::make_shared<MultiplicativePriceSeasonality>(args&: seasonalityBaseDate, args: Monthly, args&: seasonalityFactors);
340
341 pZITS->setSeasonality(nonUnitSeasonality);
342
343 for (const auto& datum: zcData) {
344 ZeroCouponInflationSwap nzcis(Swap::Payer,
345 1000000.0,
346 evaluationDate,
347 datum.date,
348 calendar, bdc, dc,
349 datum.rate/100.0,
350 ii, observationLag,
351 CPI::AsIndex);
352 nzcis.setPricingEngine(engine);
353
354 BOOST_CHECK_MESSAGE(std::fabs(nzcis.NPV()) < eps,
355 "zero-coupon inflation swap does not reprice to zero"
356 << "\n NPV: " << nzcis.NPV()
357 << "\n maturity: " << nzcis.maturityDate()
358 << "\n rate: " << datum.rate);
359 }
360
361 //==============================================================================
362 // now do an INTERPOLATED index, i.e. repeat everything on a fake version of
363 // UKRPI (to save making another term structure)
364
365 bool interpYES = true;
366 QL_DEPRECATED_DISABLE_WARNING
367 ext::shared_ptr<UKRPI> iiyes(new UKRPI(interpYES, hz));
368 QL_DEPRECATED_ENABLE_WARNING
369 for (Size i=0; i<LENGTH(fixData);i++) {
370 iiyes->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
371 }
372
373 auto makeHelperYes = [&](const Handle<Quote>& quote, const Date& maturity) {
374 return ext::make_shared<ZeroCouponInflationSwapHelper>(
375 args: quote, args&: observationLag, args: maturity, args&: calendar, args&: bdc, args&: dc, args&: iiyes, args: CPI::AsIndex,
376 args: Handle<YieldTermStructure>(nominalTS));
377 };
378 auto helpersyes = makeHelpers<ZeroInflationTermStructure>(iiData: zcData, makeHelper: makeHelperYes);
379
380 ext::shared_ptr<PiecewiseZeroInflationCurve<Linear> > pZITSyes(
381 new PiecewiseZeroInflationCurve<Linear>(
382 evaluationDate, calendar, dc, observationLag,
383 frequency, baseZeroRate, helpersyes));
384
385 hz.linkTo(h: pZITSyes);
386
387 //===========================================================================================
388 // Test zero coupon swaps
389
390 for (const auto& datum: zcData) {
391 ZeroCouponInflationSwap nzcis(Swap::Payer,
392 1000000.0,
393 evaluationDate,
394 datum.date,
395 calendar, bdc, dc,
396 datum.rate/100.0,
397 iiyes, observationLag,
398 CPI::AsIndex);
399 nzcis.setPricingEngine(engine);
400
401 BOOST_CHECK_MESSAGE(std::fabs(nzcis.NPV()) < eps,
402 "zero-coupon inflation swap does not reprice to zero"
403 << "\n NPV: " << nzcis.NPV()
404 << "\n maturity: " << nzcis.maturityDate()
405 << "\n rate: " << datum.rate);
406 }
407
408 //===========================================================================================
409 // Perform checks on the seasonality for this interpolated index
410
411 pZITSyes->setSeasonality(nonUnitSeasonality);
412
413 for (const auto& datum: zcData) {
414 ZeroCouponInflationSwap nzcis(Swap::Payer,
415 1000000.0,
416 evaluationDate,
417 datum.date,
418 calendar, bdc, dc,
419 datum.rate/100.0,
420 iiyes, observationLag,
421 CPI::AsIndex);
422 nzcis.setPricingEngine(engine);
423
424 BOOST_CHECK_MESSAGE(std::fabs(nzcis.NPV()) < eps,
425 "zero-coupon inflation swap does not reprice to zero"
426 << "\n NPV: " << nzcis.NPV()
427 << "\n maturity: " << nzcis.maturityDate()
428 << "\n rate: " << datum.rate);
429 }
430
431 // remove circular refernce
432 hz.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>());
433}
434
435namespace inflation_test {
436
437void checkSeasonality(const Handle<ZeroInflationTermStructure>& hz,
438 const ext::shared_ptr<ZeroInflationIndex>& ii) {
439
440 QL_REQUIRE(!hz->hasSeasonality(), "We require that the initially passed in term structure "
441 << "does not have seasonality");
442
443 // Tolerance that we will use below when comparing projected index fixings
444 Rate tolerance = 1e-12;
445
446 Date trueBaseDate = inflationPeriod(hz->baseDate(), ii->frequency()).second;
447 Date seasonalityBaseDate(31, January, trueBaseDate.year());
448
449 // Create two different seasonality objects
450
451 // 1) Monthly seasonality with all elements equal to 1 <=> no seasonality
452 vector<Rate> seasonalityFactors(12, 1.0);
453 ext::shared_ptr<MultiplicativePriceSeasonality> unitSeasonality =
454 ext::make_shared<MultiplicativePriceSeasonality>(args&: seasonalityBaseDate, args: Monthly, args&: seasonalityFactors);
455
456 // 2) Seasonality with factors != 1.0
457 seasonalityFactors[0] = 1.003245;
458 seasonalityFactors[1] = 1.000000;
459 seasonalityFactors[2] = 0.999715;
460 seasonalityFactors[3] = 1.000495;
461 seasonalityFactors[4] = 1.000929;
462 seasonalityFactors[5] = 0.998687;
463 seasonalityFactors[6] = 0.995949;
464 seasonalityFactors[7] = 0.994682;
465 seasonalityFactors[8] = 0.995949;
466 seasonalityFactors[9] = 1.000519;
467 seasonalityFactors[10] = 1.003705;
468 seasonalityFactors[11] = 1.004186;
469
470 ext::shared_ptr<MultiplicativePriceSeasonality> nonUnitSeasonality =
471 ext::make_shared<MultiplicativePriceSeasonality>(args&: seasonalityBaseDate, args: Monthly, args&: seasonalityFactors);
472
473 // Create dates on which we will check fixings
474 vector<Date> fixingDates(12);
475 Date anchorDate(14, January, 2013);
476 for (Size i = 0; i < fixingDates.size(); ++i) {
477 fixingDates[i] = anchorDate + i * Months;
478 }
479
480 // Projected inflation index fixings when there is no seasonality
481 vector<Rate> noSeasonalityFixings(12, 1.0);
482 for (Size i = 0; i < fixingDates.size(); ++i) {
483 noSeasonalityFixings[i] = ii->fixing(fixingDate: fixingDates[i], forecastTodaysFixing: true);
484 }
485
486 // Set seasonality of all 1's and get the projected index fixings
487 hz->setSeasonality(unitSeasonality);
488 vector<Rate> unitSeasonalityFixings(12, 1.0);
489 for (Size i = 0; i < fixingDates.size(); ++i) {
490 unitSeasonalityFixings[i] = ii->fixing(fixingDate: fixingDates[i], forecastTodaysFixing: true);
491 }
492
493 // Check that the unit seasonality fixings agree with the no seasonality fixings
494 for (Size i = 0; i < fixingDates.size(); i++) {
495 if (fabs(x: noSeasonalityFixings[i] - unitSeasonalityFixings[i]) > tolerance) {
496 BOOST_ERROR("Seasonality doesn't work correctly when seasonality factors are set = 1"
497 << "No seasonality fixing is: " << noSeasonalityFixings[i]
498 << " but unit seasonality fixing is: " << unitSeasonalityFixings[i]
499 << " for fixing date " << io::iso_date(fixingDates[i]));
500 }
501 }
502
503 // Testing seasonality correction when seasonality factors are different from 1
504 // We expect to see that I_{SA}(t) = I_{NSA}(t) * S(t) / S(t_b)
505 Month baseCpiMonth = hz->baseDate().month();
506 Size baseCpiIndex = static_cast<Size>(baseCpiMonth) - 1;
507 Rate baseSeasonality = seasonalityFactors[baseCpiIndex];
508
509 // These are the expected fixings
510 vector<Rate> expectedSeasonalityFixings(12, 1.0);
511 for (Size i = 0; i < expectedSeasonalityFixings.size(); ++i) {
512 QL_DEPRECATED_DISABLE_WARNING
513 if (!ii->interpolated()) {
514 QL_DEPRECATED_ENABLE_WARNING
515 expectedSeasonalityFixings[i] =
516 ii->fixing(fixingDate: fixingDates[i], forecastTodaysFixing: true) * seasonalityFactors[i] / baseSeasonality;
517 } else {
518 std::pair<Date, Date> p1 = inflationPeriod(fixingDates[i], ii->frequency());
519 Date firstDayCurrentPeriod = p1.first;
520 Date firstDayNextPeriod = p1.second + 1;
521 Month firstMonth = firstDayCurrentPeriod.month();
522 Month secondMonth = firstDayNextPeriod.month();
523 Size firstMonthIndex = static_cast<Size>(firstMonth) - 1;
524 Size secondMonthIndex = static_cast<Size>(secondMonth) - 1;
525
526 Period observationLag = ii->zeroInflationTermStructure()->observationLag();
527 Date observationDate = fixingDates[i] + observationLag;
528 std::pair<Date, Date> p2 = inflationPeriod(observationDate, ii->frequency());
529 Real daysInPeriod = (p2.second + 1) - p2.first;
530 Real interpolationCoefficient = (observationDate - p2.first) / daysInPeriod;
531
532 Rate i1adj = ii->fixing(fixingDate: firstDayCurrentPeriod, forecastTodaysFixing: true) *
533 seasonalityFactors[firstMonthIndex] / baseSeasonality;
534
535 Rate i2adj = ii->fixing(fixingDate: firstDayNextPeriod, forecastTodaysFixing: true) *
536 seasonalityFactors[secondMonthIndex] / baseSeasonality;
537 expectedSeasonalityFixings[i] =
538 i1adj + (i2adj - i1adj) * interpolationCoefficient;
539 }
540 }
541
542 // Set the seasonality and calculate the actual seasonally adjusted fixings
543 hz->setSeasonality(nonUnitSeasonality);
544 vector<Rate> nonUnitSeasonalityFixings(12, 1.0);
545 for (Size i = 0; i < fixingDates.size(); ++i) {
546 nonUnitSeasonalityFixings[i] = ii->fixing(fixingDate: fixingDates[i], forecastTodaysFixing: true);
547 }
548
549 // Check that the calculated fixings agree with the expected fixings
550 for (Size i = 0; i < fixingDates.size(); i++) {
551 if (fabs(x: expectedSeasonalityFixings[i] - nonUnitSeasonalityFixings[i]) > tolerance) {
552 BOOST_ERROR("Seasonality doesn't work correctly for non-unit seasonality factors."
553 << " Expected fixing is: " << expectedSeasonalityFixings[i]
554 << " but calculated fixing is: " << nonUnitSeasonalityFixings[i]
555 << " for fixing date " << io::iso_date(fixingDates[i]));
556 }
557 }
558
559 // Testing that unsetting seasonality works also
560 hz->setSeasonality();
561 vector<Rate> unsetSeasonalityFixings(12, 1.0);
562 for (Size i = 0; i < fixingDates.size(); ++i) {
563 unsetSeasonalityFixings[i] = ii->fixing(fixingDate: fixingDates[i], forecastTodaysFixing: true);
564 }
565
566 // Check that seasonality has been unset by comparing with the no seasonality fixings
567 for (Size i = 0; i < fixingDates.size(); i++) {
568 if (fabs(x: noSeasonalityFixings[i] - unsetSeasonalityFixings[i]) > tolerance) {
569 BOOST_ERROR("Unsetting seasonality doesn't work correctly."
570 << " No seasonality fixing is: " << noSeasonalityFixings[i]
571 << " but after unsetting seasonality fixing is: " << unitSeasonalityFixings[i]
572 << " for fixing date " << io::iso_date(fixingDates[i]));
573 }
574 }
575}
576
577}
578
579void InflationTest::testSeasonalityCorrection() {
580 BOOST_TEST_MESSAGE("Testing seasonality correction on zero inflation term structure...");
581
582 using namespace inflation_test;
583
584 // try the Zero UK
585 Calendar calendar = UnitedKingdom();
586 Date evaluationDate(13, August, 2007);
587 evaluationDate = calendar.adjust(evaluationDate);
588 Settings::instance().evaluationDate() = evaluationDate;
589
590 // fixing data
591 Date from(1, January, 2005);
592 Date to(13, August, 2007);
593 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
594 .withTenor(1*Months)
595 .withCalendar(UnitedKingdom())
596 .withConvention(ModifiedFollowing);
597
598 Real fixData[] = { 189.9, 189.9, 189.6, 190.5, 191.6, 192.0,
599 192.2, 192.2, 192.6, 193.1, 193.3, 193.6,
600 194.1, 193.4, 194.2, 195.0, 196.5, 197.7,
601 198.5, 198.5, 199.2, 200.1, 200.4, 201.1,
602 202.7, 201.6, 203.1, 204.4, 205.4, 206.2,
603 207.3};
604
605 RelinkableHandle<ZeroInflationTermStructure> hz;
606 auto ii = ext::make_shared<UKRPI>(args&: hz);
607 for (Size i=0; i<LENGTH(fixData); i++) {
608 ii->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
609 }
610
611 ext::shared_ptr<YieldTermStructure> nominalTS = nominalTermStructure();
612
613 std::vector<Date> nodes = {
614 Date(1, June, 2007),
615 Date(1, June, 2008),
616 Date(1, June, 2009),
617 Date(1, June, 2010),
618 Date(1, June, 2011),
619 Date(1, June, 2012),
620 Date(1, June, 2014),
621 Date(1, June, 2017),
622 Date(1, June, 2019),
623 Date(1, June, 2022),
624 Date(1, June, 2027),
625 Date(1, June, 2032),
626 Date(1, June, 2037),
627 Date(1, June, 2047),
628 Date(1, June, 2057)
629 };
630
631 std::vector<Rate> rates = {
632 0.0293,
633 0.0293,
634 0.0295,
635 0.02965,
636 0.0298,
637 0.03,
638 0.0306,
639 0.03175,
640 0.03243,
641 0.03293,
642 0.03338,
643 0.03348,
644 0.03348,
645 0.03308,
646 0.03228
647 };
648
649 Period observationLag = Period(2,Months);
650 DayCounter dc = Thirty360(Thirty360::BondBasis);
651 Frequency frequency = Monthly;
652
653 auto zeroCurve = ext::make_shared<InterpolatedZeroInflationCurve<Linear>>(
654 args&: evaluationDate, args&: calendar, args&: dc, args&: observationLag,
655 args&: frequency, args&: nodes, args&: rates);
656 hz.linkTo(h: zeroCurve);
657
658 // Perform checks on the seasonality for this non-interpolated index
659 checkSeasonality(hz, ii);
660
661 //==============================================================================
662 // now do an INTERPOLATED index, i.e. repeat everything on a fake version of
663 // UKRPI (to save making another term structure)
664
665 bool interpYES = true;
666 QL_DEPRECATED_DISABLE_WARNING
667 ext::shared_ptr<UKRPI> iiyes(new UKRPI(interpYES, hz));
668 QL_DEPRECATED_ENABLE_WARNING
669 for (Size i=0; i<LENGTH(fixData);i++) {
670 iiyes->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
671 }
672
673 // Perform checks on the seasonality for this interpolated index
674 checkSeasonality(hz, ii: iiyes);
675}
676
677void InflationTest::testZeroIndexFutureFixing() {
678 BOOST_TEST_MESSAGE("Testing that zero inflation indices forecast future fixings...");
679
680 EUHICP euhicp;
681
682 Date sample_date = Date(1,December,2013);
683 Real sample_fixing = 117.48;
684 euhicp.addFixing(fixingDate: sample_date, fixing: sample_fixing);
685
686 // fixing date in the past
687 Date evaluationDate = euhicp.fixingCalendar().adjust(sample_date + 2*Weeks);
688 Settings::instance().evaluationDate() = evaluationDate;
689 Real fixing = euhicp.fixing(fixingDate: sample_date);
690 if (std::fabs(x: fixing - sample_fixing) > 1e-12)
691 BOOST_ERROR("Failed to retrieve correct fixing: "
692 << "\n returned: " << fixing
693 << "\n expected: " << sample_fixing);
694
695 // fixing date in the future
696 evaluationDate = euhicp.fixingCalendar().adjust(sample_date - 2*Weeks);
697 Settings::instance().evaluationDate() = evaluationDate;
698 bool retrieved = false;
699 try {
700 fixing = euhicp.fixing(fixingDate: sample_date);
701 // the above should throw for lack of a forecast curve, so
702 // this shouldn't be executed and retrieved should stay false
703 retrieved = true;
704 } catch (Error&) {}
705
706 if (retrieved)
707 BOOST_ERROR("Retrieved future fixing: "
708 << "\n returned: " << fixing);
709}
710
711void InflationTest::testInterpolatedZeroTermStructure() {
712 BOOST_TEST_MESSAGE("Testing interpolated zero-rate inflation curve...");
713
714 Date today = Date(27, January, 2022);
715 Settings::instance().evaluationDate() = today;
716
717 Period lag = 3 * Months;
718
719 std::vector<Date> dates = {
720 today - lag,
721 today + 7 * Days,
722 today + 14 * Days,
723 today + 1 * Months,
724 today + 2 * Months,
725 today + 3 * Months,
726 today + 6 * Months,
727 today + 1 * Years,
728 today + 2 * Years,
729 today + 5 * Years,
730 today + 10 * Years
731 };
732 std::vector<Rate> rates = { 0.01, 0.01, 0.011, 0.012, 0.013, 0.015, 0.018, 0.02, 0.025, 0.03, 0.03 };
733
734 auto curve = ext::make_shared<InterpolatedZeroInflationCurve<Linear>>(
735 args&: today, args: TARGET(), args: Actual360(), args&: lag, args: Monthly, args&: dates, args&: rates);
736
737 auto nodes = curve->nodes();
738
739 BOOST_CHECK_MESSAGE(nodes.size() == dates.size(), "different number of nodes and input dates");
740
741 for (Size i=0; i<dates.size(); ++i) {
742 BOOST_CHECK_MESSAGE(dates[i] == nodes[i].first,
743 "node " << i << " at " << nodes[i].first << "; " << dates[i] << " expected");
744 }
745}
746
747//===========================================================================================
748// year on year tests, index, termstructure, and swaps
749//===========================================================================================
750
751void InflationTest::testQuotedYYIndex() {
752 BOOST_TEST_MESSAGE("Testing quoted year-on-year inflation indices...");
753
754 YYEUHICP yyeuhicp(true);
755 if (yyeuhicp.name() != "EU YY_HICP"
756 || yyeuhicp.frequency() != Monthly
757 || yyeuhicp.revised()
758 || !yyeuhicp.interpolated()
759 || yyeuhicp.ratio()
760 || yyeuhicp.availabilityLag() != 1*Months) {
761 BOOST_ERROR("wrong year-on-year EU HICP data ("
762 << yyeuhicp.name() << ", "
763 << yyeuhicp.frequency() << ", "
764 << yyeuhicp.revised() << ", "
765 << yyeuhicp.interpolated() << ", "
766 << yyeuhicp.ratio() << ", "
767 << yyeuhicp.availabilityLag() << ")");
768 }
769
770 YYUKRPI yyukrpi(false);
771 if (yyukrpi.name() != "UK YY_RPI"
772 || yyukrpi.frequency() != Monthly
773 || yyukrpi.revised()
774 || yyukrpi.interpolated()
775 || yyukrpi.ratio()
776 || yyukrpi.availabilityLag() != 1*Months) {
777 BOOST_ERROR("wrong year-on-year UK RPI data ("
778 << yyukrpi.name() << ", "
779 << yyukrpi.frequency() << ", "
780 << yyukrpi.revised() << ", "
781 << yyukrpi.interpolated() << ", "
782 << yyukrpi.ratio() << ", "
783 << yyukrpi.availabilityLag() << ")");
784 }
785}
786
787
788void InflationTest::testRatioYYIndex() {
789 BOOST_TEST_MESSAGE("Testing ratio year-on-year inflation indices...");
790
791 auto euhicp = ext::make_shared<EUHICP>();
792 auto ukrpi = ext::make_shared<UKRPI>();
793
794 YoYInflationIndex yyeuhicpr(euhicp, true);
795 if (yyeuhicpr.name() != "EU YYR_HICP"
796 || yyeuhicpr.frequency() != Monthly
797 || yyeuhicpr.revised()
798 || !yyeuhicpr.interpolated()
799 || !yyeuhicpr.ratio()
800 || yyeuhicpr.availabilityLag() != 1*Months) {
801 BOOST_ERROR("wrong year-on-year EU HICPr data ("
802 << yyeuhicpr.name() << ", "
803 << yyeuhicpr.frequency() << ", "
804 << yyeuhicpr.revised() << ", "
805 << yyeuhicpr.interpolated() << ", "
806 << yyeuhicpr.ratio() << ", "
807 << yyeuhicpr.availabilityLag() << ")");
808 }
809
810 YoYInflationIndex yyukrpir(ukrpi, false);
811 if (yyukrpir.name() != "UK YYR_RPI"
812 || yyukrpir.frequency() != Monthly
813 || yyukrpir.revised()
814 || yyukrpir.interpolated()
815 || !yyukrpir.ratio()
816 || yyukrpir.availabilityLag() != 1*Months) {
817 BOOST_ERROR("wrong year-on-year UK RPIr data ("
818 << yyukrpir.name() << ", "
819 << yyukrpir.frequency() << ", "
820 << yyukrpir.revised() << ", "
821 << yyukrpir.interpolated() << ", "
822 << yyukrpir.ratio() << ", "
823 << yyukrpir.availabilityLag() << ")");
824 }
825
826
827 // Retrieval test.
828 //----------------
829 // make sure of the evaluation date
830 Date evaluationDate(13, August, 2007);
831 evaluationDate = UnitedKingdom().adjust(evaluationDate);
832 Settings::instance().evaluationDate() = evaluationDate;
833
834 // fixing data
835 Date from(1, January, 2005);
836 Date to(13, August, 2007);
837 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
838 .withTenor(1*Months)
839 .withCalendar(UnitedKingdom())
840 .withConvention(ModifiedFollowing);
841
842 Real fixData[] = { 189.9, 189.9, 189.6, 190.5, 191.6, 192.0,
843 192.2, 192.2, 192.6, 193.1, 193.3, 193.6,
844 194.1, 193.4, 194.2, 195.0, 196.5, 197.7,
845 198.5, 198.5, 199.2, 200.1, 200.4, 201.1,
846 202.7, 201.6, 203.1, 204.4, 205.4, 206.2,
847 207.3 };
848
849 for (Size i=0; i<LENGTH(fixData);i++) {
850 ukrpi->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
851 }
852
853 auto iir = ext::make_shared<YoYInflationIndex>(args&: ukrpi, args: false);
854 auto iirYES = ext::make_shared<YoYInflationIndex>(args&: ukrpi, args: true);
855
856 Date todayMinusLag = evaluationDate - iir->availabilityLag();
857 std::pair<Date,Date> lim = inflationPeriod(todayMinusLag, iir->frequency());
858 todayMinusLag = lim.second + 1 - 2*Period(iir->frequency());
859
860 Real eps = 1.0e-8;
861
862 // Interpolation tests
863 //--------------------
864 // (no TS so can't forecast).
865 for (Size i=13; i<rpiSchedule.size();i++) {
866 std::pair<Date,Date> lim = inflationPeriod(rpiSchedule[i],
867 iir->frequency());
868 std::pair<Date,Date> limBef = inflationPeriod(rpiSchedule[i-12],
869 iir->frequency());
870 for (Date d=lim.first; d<=lim.second; d++) {
871 if (d < todayMinusLag) {
872 Rate expected = fixData[i]/fixData[i-12] - 1.0;
873 Rate calculated = iir->fixing(fixingDate: d);
874 BOOST_CHECK_MESSAGE(std::fabs(calculated - expected) < eps,
875 "Non-interpolated fixings not constant within a period: "
876 << calculated
877 << ", should be "
878 << expected);
879
880 Real dp= lim.second + 1- lim.first;
881 Real dpBef=limBef.second + 1 - limBef.first;
882 Real dl = d-lim.first;
883 // potentially does not work on 29th Feb
884 Real dlBef = NullCalendar().advance(date: d, period: -1*Years, convention: ModifiedFollowing)
885 -limBef.first;
886
887 Real linearNow = fixData[i] + (fixData[i+1]-fixData[i])*dl/dp;
888 Real linearBef = fixData[i-12] + (fixData[i+1-12]-fixData[i-12])*dlBef/dpBef;
889 Rate expectedYES = linearNow / linearBef - 1.0;
890 Rate calculatedYES = iirYES->fixing(fixingDate: d);
891 BOOST_CHECK_MESSAGE(fabs(expectedYES-calculatedYES)<eps,
892 "Error in interpolated fixings: expect "<<expectedYES
893 <<" see " << calculatedYES
894 <<" flat " << calculated
895 <<", data: "<< fixData[i-12] <<", "<< fixData[i+1-12]
896 <<", "<< fixData[i] <<", "<< fixData[i+1]
897 <<", fac: "<< dp <<", "<< dl
898 <<", "<< dpBef <<", "<< dlBef
899 <<", to: "<<linearNow<<", "<<linearBef
900 );
901 }
902 }
903 }
904}
905
906
907void InflationTest::testOldRatioYYIndex() {
908 BOOST_TEST_MESSAGE("Testing old-style ratio year-on-year inflation indices...");
909
910 QL_DEPRECATED_DISABLE_WARNING
911
912 YYEUHICPr yyeuhicpr(true);
913
914 QL_DEPRECATED_ENABLE_WARNING
915
916 if (yyeuhicpr.name() != "EU YYR_HICP"
917 || yyeuhicpr.frequency() != Monthly
918 || yyeuhicpr.revised()
919 || !yyeuhicpr.interpolated()
920 || !yyeuhicpr.ratio()
921 || yyeuhicpr.availabilityLag() != 1*Months) {
922 BOOST_ERROR("wrong year-on-year EU HICPr data ("
923 << yyeuhicpr.name() << ", "
924 << yyeuhicpr.frequency() << ", "
925 << yyeuhicpr.revised() << ", "
926 << yyeuhicpr.interpolated() << ", "
927 << yyeuhicpr.ratio() << ", "
928 << yyeuhicpr.availabilityLag() << ")");
929 }
930
931 QL_DEPRECATED_DISABLE_WARNING
932
933 YYUKRPIr yyukrpir(false);
934
935 QL_DEPRECATED_ENABLE_WARNING
936
937 if (yyukrpir.name() != "UK YYR_RPI"
938 || yyukrpir.frequency() != Monthly
939 || yyukrpir.revised()
940 || yyukrpir.interpolated()
941 || !yyukrpir.ratio()
942 || yyukrpir.availabilityLag() != 1*Months) {
943 BOOST_ERROR("wrong year-on-year UK RPIr data ("
944 << yyukrpir.name() << ", "
945 << yyukrpir.frequency() << ", "
946 << yyukrpir.revised() << ", "
947 << yyukrpir.interpolated() << ", "
948 << yyukrpir.ratio() << ", "
949 << yyukrpir.availabilityLag() << ")");
950 }
951
952
953 // Retrieval test.
954 //----------------
955 // make sure of the evaluation date
956 Date evaluationDate(13, August, 2007);
957 evaluationDate = UnitedKingdom().adjust(evaluationDate);
958 Settings::instance().evaluationDate() = evaluationDate;
959
960 // fixing data
961 Date from(1, January, 2005);
962 Date to(13, August, 2007);
963 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
964 .withTenor(1*Months)
965 .withCalendar(UnitedKingdom())
966 .withConvention(ModifiedFollowing);
967
968 Real fixData[] = { 189.9, 189.9, 189.6, 190.5, 191.6, 192.0,
969 192.2, 192.2, 192.6, 193.1, 193.3, 193.6,
970 194.1, 193.4, 194.2, 195.0, 196.5, 197.7,
971 198.5, 198.5, 199.2, 200.1, 200.4, 201.1,
972 202.7, 201.6, 203.1, 204.4, 205.4, 206.2,
973 207.3 };
974
975 bool interp = false;
976
977 QL_DEPRECATED_DISABLE_WARNING
978
979 ext::shared_ptr<YYUKRPIr> iir(new YYUKRPIr(interp));
980 ext::shared_ptr<YYUKRPIr> iirYES(new YYUKRPIr(true));
981
982 QL_DEPRECATED_ENABLE_WARNING
983
984 for (Size i=0; i<LENGTH(fixData);i++) {
985 iir->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
986 iirYES->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
987 }
988
989 Date todayMinusLag = evaluationDate - iir->availabilityLag();
990 std::pair<Date,Date> lim = inflationPeriod(todayMinusLag, iir->frequency());
991 todayMinusLag = lim.second + 1 - 2*Period(iir->frequency());
992
993 Real eps = 1.0e-8;
994
995 // Interpolation tests
996 //--------------------
997 // (no TS so can't forecast).
998 for (Size i=13; i<rpiSchedule.size();i++) {
999 std::pair<Date,Date> lim = inflationPeriod(rpiSchedule[i],
1000 iir->frequency());
1001 std::pair<Date,Date> limBef = inflationPeriod(rpiSchedule[i-12],
1002 iir->frequency());
1003 for (Date d=lim.first; d<=lim.second; d++) {
1004 if (d < todayMinusLag) {
1005 Rate expected = fixData[i]/fixData[i-12] - 1.0;
1006 Rate calculated = iir->fixing(fixingDate: d);
1007 BOOST_CHECK_MESSAGE(std::fabs(calculated - expected) < eps,
1008 "Non-interpolated fixings not constant within a period: "
1009 << calculated
1010 << ", should be "
1011 << expected);
1012
1013 Real dp= lim.second + 1- lim.first;
1014 Real dpBef=limBef.second + 1 - limBef.first;
1015 Real dl = d-lim.first;
1016 // potentially does not work on 29th Feb
1017 Real dlBef = NullCalendar().advance(date: d, period: -1*Years, convention: ModifiedFollowing)
1018 -limBef.first;
1019
1020 Real linearNow = fixData[i] + (fixData[i+1]-fixData[i])*dl/dp;
1021 Real linearBef = fixData[i-12] + (fixData[i+1-12]-fixData[i-12])*dlBef/dpBef;
1022 Rate expectedYES = linearNow / linearBef - 1.0;
1023 Rate calculatedYES = iirYES->fixing(fixingDate: d);
1024 BOOST_CHECK_MESSAGE(fabs(expectedYES-calculatedYES)<eps,
1025 "Error in interpolated fixings: expect "<<expectedYES
1026 <<" see " << calculatedYES
1027 <<" flat " << calculated
1028 <<", data: "<< fixData[i-12] <<", "<< fixData[i+1-12]
1029 <<", "<< fixData[i] <<", "<< fixData[i+1]
1030 <<", fac: "<< dp <<", "<< dl
1031 <<", "<< dpBef <<", "<< dlBef
1032 <<", to: "<<linearNow<<", "<<linearBef
1033 );
1034 }
1035 }
1036 }
1037}
1038
1039
1040void InflationTest::testYYTermStructure() {
1041 BOOST_TEST_MESSAGE("Testing year-on-year inflation term structure...");
1042
1043 using namespace inflation_test;
1044
1045 // try the YY UK
1046 Calendar calendar = UnitedKingdom();
1047 BusinessDayConvention bdc = ModifiedFollowing;
1048 Date evaluationDate(13, August, 2007);
1049 evaluationDate = calendar.adjust(evaluationDate);
1050 Settings::instance().evaluationDate() = evaluationDate;
1051
1052
1053 // fixing data
1054 Date from(1, January, 2005);
1055 Date to(13, August, 2007);
1056 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
1057 .withTenor(1*Months)
1058 .withCalendar(UnitedKingdom())
1059 .withConvention(ModifiedFollowing);
1060 Real fixData[] = { 189.9, 189.9, 189.6, 190.5, 191.6, 192.0,
1061 192.2, 192.2, 192.6, 193.1, 193.3, 193.6,
1062 194.1, 193.4, 194.2, 195.0, 196.5, 197.7,
1063 198.5, 198.5, 199.2, 200.1, 200.4, 201.1,
1064 202.7, 201.6, 203.1, 204.4, 205.4, 206.2,
1065 207.3 };
1066
1067 RelinkableHandle<YoYInflationTermStructure> hy;
1068 bool interp = false;
1069 auto rpi = ext::make_shared<UKRPI>();
1070 auto iir = ext::make_shared<YoYInflationIndex>(args&: rpi, args&: interp, args&: hy);
1071 for (Size i=0; i<LENGTH(fixData); i++) {
1072 rpi->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
1073 }
1074
1075
1076
1077 ext::shared_ptr<YieldTermStructure> nominalTS = nominalTermStructure();
1078
1079 // now build the YoY inflation curve
1080 std::vector<Datum> yyData = {
1081 { .date: Date(13, August, 2008), .rate: 2.95 },
1082 { .date: Date(13, August, 2009), .rate: 2.95 },
1083 { .date: Date(13, August, 2010), .rate: 2.93 },
1084 { .date: Date(15, August, 2011), .rate: 2.955 },
1085 { .date: Date(13, August, 2012), .rate: 2.945 },
1086 { .date: Date(13, August, 2013), .rate: 2.985 },
1087 { .date: Date(13, August, 2014), .rate: 3.01 },
1088 { .date: Date(13, August, 2015), .rate: 3.035 },
1089 { .date: Date(13, August, 2016), .rate: 3.055 }, // note that
1090 { .date: Date(13, August, 2017), .rate: 3.075 }, // some dates will be on
1091 { .date: Date(13, August, 2019), .rate: 3.105 }, // holidays but the payment
1092 { .date: Date(15, August, 2022), .rate: 3.135 }, // calendar will roll them
1093 { .date: Date(13, August, 2027), .rate: 3.155 },
1094 { .date: Date(13, August, 2032), .rate: 3.145 },
1095 { .date: Date(13, August, 2037), .rate: 3.145 }
1096 };
1097
1098 Period observationLag = Period(2,Months);
1099 DayCounter dc = Thirty360(Thirty360::BondBasis);
1100
1101 // now build the helpers ...
1102 auto makeHelper = [&](const Handle<Quote>& quote, const Date& maturity) {
1103 return ext::make_shared<YearOnYearInflationSwapHelper>(
1104 args: quote, args&: observationLag, args: maturity, args&: calendar, args&: bdc, args&: dc, args&: iir,
1105 args: Handle<YieldTermStructure>(nominalTS));
1106 };
1107 auto helpers = makeHelpers<YoYInflationTermStructure>(iiData: yyData, makeHelper);
1108
1109 Rate baseYYRate = yyData[0].rate/100.0;
1110 ext::shared_ptr<PiecewiseYoYInflationCurve<Linear> > pYYTS(
1111 new PiecewiseYoYInflationCurve<Linear>(
1112 evaluationDate, calendar, dc, observationLag,
1113 iir->frequency(),iir->interpolated(), baseYYRate,
1114 helpers));
1115 pYYTS->recalculate();
1116
1117 // validation
1118 // yoy swaps should reprice to zero
1119 // yy rates should not equal yySwap rates
1120 Real eps = 0.000001;
1121 // usual swap engine
1122 Handle<YieldTermStructure> hTS(nominalTS);
1123 ext::shared_ptr<PricingEngine> sppe(new DiscountingSwapEngine(hTS));
1124
1125 // make sure that the index has the latest yoy term structure
1126 hy.linkTo(h: pYYTS);
1127
1128 for (Size j = 1; j < yyData.size(); j++) {
1129
1130 from = nominalTS->referenceDate();
1131 to = yyData[j].date;
1132 Schedule yoySchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
1133 .withConvention(Unadjusted) // fixed leg gets calendar from
1134 .withCalendar(calendar) // schedule
1135 .withTenor(1*Years)
1136 .backwards()
1137 ;
1138
1139 YearOnYearInflationSwap yyS2(Swap::Payer,
1140 1000000.0,
1141 yoySchedule,//fixed schedule, but same as yoy
1142 yyData[j].rate/100.0,
1143 dc,
1144 yoySchedule,
1145 iir,
1146 observationLag,
1147 0.0, //spread on index
1148 dc,
1149 UnitedKingdom());
1150
1151 yyS2.setPricingEngine(sppe);
1152
1153 BOOST_CHECK_MESSAGE(fabs(yyS2.NPV())<eps,"fresh yoy swap NPV!=0 from TS "
1154 <<"swap quote for pt " << j
1155 << ", is " << yyData[j].rate/100.0
1156 <<" vs YoY rate "<< pYYTS->yoyRate(yyData[j].date-observationLag)
1157 <<" at quote date "<<(yyData[j].date-observationLag)
1158 <<", NPV of a fresh yoy swap is " << yyS2.NPV()
1159 <<"\n fair rate " << yyS2.fairRate()
1160 <<" payment "<<yyS2.paymentConvention());
1161 }
1162
1163 Size jj=3;
1164 for (Size k = 0; k < 14; k++) {
1165
1166 from = nominalTS->referenceDate() - k*Months;
1167 to = yyData[jj].date - k*Months;
1168 Schedule yoySchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
1169 .withConvention(Unadjusted) // fixed leg gets calendar from
1170 .withCalendar(calendar) // schedule
1171 .withTenor(1*Years)
1172 .backwards()
1173 ;
1174
1175 YearOnYearInflationSwap yyS3(Swap::Payer,
1176 1000000.0,
1177 yoySchedule,//fixed schedule, but same as yoy
1178 yyData[jj].rate/100.0,
1179 dc,
1180 yoySchedule,
1181 iir,
1182 observationLag,
1183 0.0, //spread on index
1184 dc,
1185 UnitedKingdom());
1186
1187 yyS3.setPricingEngine(sppe);
1188
1189 BOOST_CHECK_MESSAGE(fabs(yyS3.NPV())< 20000.0,
1190 "unexpected size of aged YoY swap, aged "
1191 <<k<<" months: YY aged NPV = " << yyS3.NPV()
1192 <<", legs "<< yyS3.legNPV(0) << " and " << yyS3.legNPV(1)
1193 );
1194 }
1195 // remove circular refernce
1196 hy.linkTo(h: ext::shared_ptr<YoYInflationTermStructure>());
1197}
1198
1199void InflationTest::testPeriod() {
1200 BOOST_TEST_MESSAGE("Testing inflation period...");
1201
1202 Date d;
1203 Frequency f;
1204 std::pair<Date,Date> res;
1205 int days[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
1206
1207 for (int year = 1950; year < 2051; ++year) {
1208
1209 if (Date::isLeap(y: year))
1210 days[2] = 29;
1211 else
1212 days[2] = 28;
1213
1214 for (Size i=1; i<=12; ++i){
1215
1216 d = Date(1,Month(i),year);
1217
1218 f = Monthly;
1219 res = inflationPeriod (d,f);
1220 if (res.first != Date(1,Month(i),year)
1221 || res.second != Date(days[i],Month(i),year)) {
1222 REPORT_FAILURE(d, res, "Monthly");
1223 }
1224
1225 f = Quarterly;
1226 res = inflationPeriod (d,f);
1227
1228 if ( (i==1 || i==2 || i==3) &&
1229 (res.first != Date(1,Month(1),year)
1230 || res.second != Date(31,Month(3),year))) {
1231 REPORT_FAILURE(d, res, "Quarterly");
1232 }
1233 else if ( (i==4 || i==5 || i==6) &&
1234 (res.first != Date(1,Month(4),year)
1235 || res.second != Date(30,Month(6),year))) {
1236 REPORT_FAILURE(d, res, "Quarterly");
1237 }
1238 else if ( (i==7 || i==8 || i==9) &&
1239 (res.first != Date(1,Month(7),year)
1240 || res.second != Date(30,Month(9),year))) {
1241 REPORT_FAILURE(d, res, "Quarterly");
1242 }
1243 else if ( (i==10 || i==11 || i==12) &&
1244 (res.first != Date(1,Month(10),year)
1245 || res.second != Date(31,Month(12),year))) {
1246 REPORT_FAILURE(d, res, "Quarterly");
1247 }
1248
1249 f = Semiannual;
1250 res = inflationPeriod (d,f);
1251
1252 if ( (i>0 && i<7) && (
1253 res.first != Date(1,Month(1),year)
1254 || res.second != Date(30,Month(6),year))) {
1255 REPORT_FAILURE(d, res, "Semiannual");
1256 }
1257 else if ( (i>6 && i<13) && (
1258 res.first != Date(1,Month(7),year)
1259 || res.second != Date(31,Month(12),year))) {
1260 REPORT_FAILURE(d, res, "Semiannual");
1261 }
1262
1263 f = Annual;
1264 res = inflationPeriod (d,f);
1265
1266 if (res.first != Date(1,Month(1),year)
1267 || res.second != Date(31,Month(12),year)) {
1268 REPORT_FAILURE(d, res, "Annual");
1269 }
1270 }
1271 }
1272}
1273
1274void InflationTest::testCpiFlatInterpolation() {
1275 BOOST_TEST_MESSAGE("Testing CPI flat interpolation...");
1276
1277 Settings::instance().evaluationDate() = Date(10, February, 2022);
1278
1279 QL_DEPRECATED_DISABLE_WARNING
1280
1281 auto testIndex1 = ext::make_shared<UKRPI>(args: false);
1282 auto testIndex2 = ext::make_shared<UKRPI>(args: true);
1283
1284 QL_DEPRECATED_ENABLE_WARNING
1285
1286 testIndex1->addFixing(fixingDate: Date(1, November, 2020), fixing: 293.5);
1287 testIndex1->addFixing(fixingDate: Date(1, December, 2020), fixing: 295.4);
1288 testIndex1->addFixing(fixingDate: Date(1, January, 2021), fixing: 294.6);
1289 testIndex1->addFixing(fixingDate: Date(1, February, 2021), fixing: 296.0);
1290 testIndex1->addFixing(fixingDate: Date(1, March, 2021), fixing: 296.9);
1291
1292 for (const auto& testIndex : {testIndex1, testIndex2}) {
1293
1294 Real calculated = CPI::laggedFixing(index: testIndex, date: Date(10, February, 2021), observationLag: 3 * Months, interpolationType: CPI::Flat);
1295 Real expected = 293.5;
1296
1297 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1298 "failed to retrieve inflation fixing" <<
1299 "\n expected: " << expected <<
1300 "\n calculated: " << calculated);
1301
1302 calculated = CPI::laggedFixing(index: testIndex, date: Date(12, May, 2021), observationLag: 3 * Months, interpolationType: CPI::Flat);
1303 expected = 296.0;
1304
1305 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1306 "failed to retrieve inflation fixing" <<
1307 "\n expected: " << expected <<
1308 "\n calculated: " << calculated);
1309
1310 calculated = CPI::laggedFixing(index: testIndex, date: Date(25, June, 2021), observationLag: 3 * Months, interpolationType: CPI::Flat);
1311 expected = 296.9;
1312
1313 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1314 "failed to retrieve inflation fixing" <<
1315 "\n expected: " << expected <<
1316 "\n calculated: " << calculated);
1317 }
1318}
1319
1320void InflationTest::testCpiLinearInterpolation() {
1321 BOOST_TEST_MESSAGE("Testing CPI linear interpolation...");
1322
1323 Settings::instance().evaluationDate() = Date(10, February, 2022);
1324
1325 QL_DEPRECATED_DISABLE_WARNING
1326
1327 auto testIndex1 = ext::make_shared<UKRPI>(args: false);
1328 auto testIndex2 = ext::make_shared<UKRPI>(args: true);
1329
1330 QL_DEPRECATED_ENABLE_WARNING
1331
1332 testIndex1->addFixing(fixingDate: Date(1, November, 2020), fixing: 293.5);
1333 testIndex1->addFixing(fixingDate: Date(1, December, 2020), fixing: 295.4);
1334 testIndex1->addFixing(fixingDate: Date(1, January, 2021), fixing: 294.6);
1335 testIndex1->addFixing(fixingDate: Date(1, February, 2021), fixing: 296.0);
1336 testIndex1->addFixing(fixingDate: Date(1, March, 2021), fixing: 296.9);
1337
1338 auto check = [&](const ext::shared_ptr<ZeroInflationIndex>& testIndex) {
1339 Real calculated = CPI::laggedFixing(index: testIndex, date: Date(10, February, 2021), observationLag: 3 * Months, interpolationType: CPI::Linear);
1340 Real expected = 293.5 * (19/28.0) + 295.4 * (9/28.0);
1341
1342 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1343 "failed to retrieve inflation fixing" <<
1344 "\n expected: " << expected <<
1345 "\n calculated: " << calculated);
1346
1347 calculated = CPI::laggedFixing(index: testIndex, date: Date(12, May, 2021), observationLag: 3 * Months, interpolationType: CPI::Linear);
1348 expected = 296.0 * (20/31.0) + 296.9 * (11/31.0);
1349
1350 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1351 "failed to retrieve inflation fixing" <<
1352 "\n expected: " << expected <<
1353 "\n calculated: " << calculated);
1354
1355 // this would require April's fixing
1356 BOOST_CHECK_THROW(
1357 calculated = CPI::laggedFixing(testIndex, Date(25, June, 2021), 3 * Months, CPI::Linear),
1358 Error);
1359
1360 // however, this is a special case
1361 calculated = CPI::laggedFixing(index: testIndex, date: Date(1, June, 2021), observationLag: 3 * Months, interpolationType: CPI::Linear);
1362 expected = 296.9;
1363
1364 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1365 "failed to retrieve inflation fixing" <<
1366 "\n expected: " << expected <<
1367 "\n calculated: " << calculated);
1368 };
1369
1370 check(testIndex1);
1371 check(testIndex2);
1372}
1373
1374void InflationTest::testCpiAsIndexInterpolation() {
1375 BOOST_TEST_MESSAGE("Testing CPI as-index interpolation...");
1376
1377 Date today = Date(10, February, 2022);
1378 Settings::instance().evaluationDate() = today;
1379
1380 // AsIndex requires a term structure, even for fixings in the past
1381 std::vector<Date> dates = { today - 3*Months, today + 5*Years };
1382 std::vector<Rate> rates = { 0.02, 0.02 };
1383 Handle<ZeroInflationTermStructure> mock_curve(
1384 ext::make_shared<ZeroInflationCurve>(args&: today, args: TARGET(), args: Actual360(),
1385 args: 3 * Months, args: Monthly, args&: dates, args&: rates));
1386
1387 QL_DEPRECATED_DISABLE_WARNING
1388
1389 auto testIndex1 = ext::make_shared<UKRPI>(args: false, args&: mock_curve);
1390 auto testIndex2 = ext::make_shared<UKRPI>(args: true, args&: mock_curve);
1391
1392 QL_DEPRECATED_ENABLE_WARNING
1393
1394 testIndex1->addFixing(fixingDate: Date(1, November, 2020), fixing: 293.5);
1395 testIndex1->addFixing(fixingDate: Date(1, December, 2020), fixing: 295.4);
1396 testIndex1->addFixing(fixingDate: Date(1, January, 2021), fixing: 294.6);
1397 testIndex1->addFixing(fixingDate: Date(1, February, 2021), fixing: 296.0);
1398 testIndex1->addFixing(fixingDate: Date(1, March, 2021), fixing: 296.9);
1399
1400 Real calculated = CPI::laggedFixing(index: testIndex1, date: Date(10, February, 2021), observationLag: 3 * Months, interpolationType: CPI::AsIndex);
1401 Real expected = 293.5;
1402
1403 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1404 "failed to retrieve inflation fixing" <<
1405 "\n expected: " << expected <<
1406 "\n calculated: " << calculated);
1407
1408 calculated = CPI::laggedFixing(index: testIndex2, date: Date(10, February, 2021), observationLag: 3 * Months, interpolationType: CPI::AsIndex);
1409 expected = 293.5 * (19/28.0) + 295.4 * (9/28.0);
1410
1411 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1412 "failed to retrieve inflation fixing" <<
1413 "\n expected: " << expected <<
1414 "\n calculated: " << calculated);
1415
1416 calculated = CPI::laggedFixing(index: testIndex1, date: Date(12, May, 2021), observationLag: 3 * Months, interpolationType: CPI::AsIndex);
1417 expected = 296.0;
1418
1419 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1420 "failed to retrieve inflation fixing" <<
1421 "\n expected: " << expected <<
1422 "\n calculated: " << calculated);
1423
1424 calculated = CPI::laggedFixing(index: testIndex2, date: Date(12, May, 2021), observationLag: 3 * Months, interpolationType: CPI::AsIndex);
1425 expected = 296.0 * (20/31.0) + 296.9 * (11/31.0);
1426
1427 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1428 "failed to retrieve inflation fixing" <<
1429 "\n expected: " << expected <<
1430 "\n calculated: " << calculated);
1431
1432 calculated = CPI::laggedFixing(index: testIndex1, date: Date(25, June, 2021), observationLag: 3 * Months, interpolationType: CPI::AsIndex);
1433 expected = 296.9;
1434
1435 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1436 "failed to retrieve inflation fixing" <<
1437 "\n expected: " << expected <<
1438 "\n calculated: " << calculated);
1439
1440 // this would require April's fixing
1441 BOOST_CHECK_THROW(calculated = CPI::laggedFixing(testIndex2, Date(25, June, 2021), 3 * Months, CPI::AsIndex),
1442 Error);
1443
1444 // however, this is a special case
1445 calculated = CPI::laggedFixing(index: testIndex2, date: Date(1, June, 2021), observationLag: 3 * Months, interpolationType: CPI::AsIndex);
1446 expected = 296.9;
1447
1448 BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
1449 "failed to retrieve inflation fixing" <<
1450 "\n expected: " << expected <<
1451 "\n calculated: " << calculated);
1452}
1453
1454test_suite* InflationTest::suite() {
1455
1456 auto* suite = BOOST_TEST_SUITE("Inflation tests");
1457
1458 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testPeriod));
1459
1460 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testZeroIndex));
1461 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testZeroTermStructure));
1462 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testSeasonalityCorrection));
1463 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testZeroIndexFutureFixing));
1464 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testInterpolatedZeroTermStructure));
1465
1466 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testQuotedYYIndex));
1467 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testRatioYYIndex));
1468 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testOldRatioYYIndex));
1469 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testYYTermStructure));
1470
1471 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testCpiFlatInterpolation));
1472 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testCpiLinearInterpolation));
1473 suite->add(QUANTLIB_TEST_CASE(&InflationTest::testCpiAsIndexInterpolation));
1474
1475 return suite;
1476}
1477

source code of quantlib/test-suite/inflation.cpp