[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2011 Chris Kenyon
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18 */
19
20#include "utilities.hpp"
21#include "inflationcpiswap.hpp"
22#include <ql/types.hpp>
23#include <ql/indexes/inflation/ukrpi.hpp>
24#include <ql/termstructures/bootstraphelper.hpp>
25#include <ql/time/calendars/unitedkingdom.hpp>
26#include <ql/time/daycounters/actualactual.hpp>
27#include <ql/time/daycounters/actual365fixed.hpp>
28#include <ql/termstructures/yield/zerocurve.hpp>
29#include <ql/indexes/ibor/gbplibor.hpp>
30#include <ql/termstructures/inflation/inflationhelpers.hpp>
31#include <ql/termstructures/inflation/piecewisezeroinflationcurve.hpp>
32#include <ql/cashflows/iborcoupon.hpp>
33#include <ql/cashflows/indexedcashflow.hpp>
34#include <ql/pricingengines/swap/discountingswapengine.hpp>
35#include <ql/instruments/zerocouponinflationswap.hpp>
36#include <ql/pricingengines/bond/discountingbondengine.hpp>
37#include <ql/cashflows/cpicoupon.hpp>
38#include <ql/cashflows/cpicouponpricer.hpp>
39#include <ql/instruments/cpiswap.hpp>
40#include <ql/instruments/bonds/cpibond.hpp>
41
42using namespace QuantLib;
43using namespace boost::unit_test_framework;
44
45#include <iostream>
46
47using std::fabs;
48
49namespace inflation_cpi_swap_test {
50 struct Datum {
51 Date date;
52 Rate rate;
53 };
54
55 template <class T, class U, class I>
56 std::vector<ext::shared_ptr<BootstrapHelper<T> > > makeHelpers(
57 Datum iiData[], Size N,
58 const ext::shared_ptr<I> &ii, const Period &observationLag,
59 const Calendar &calendar,
60 const BusinessDayConvention &bdc,
61 const DayCounter &dc,
62 const Handle<YieldTermStructure>& discountCurve) {
63
64 std::vector<ext::shared_ptr<BootstrapHelper<T> > > instruments;
65 for (Size i=0; i<N; i++) {
66 Date maturity = iiData[i].date;
67 Handle<Quote> quote(ext::shared_ptr<Quote>(
68 new SimpleQuote(iiData[i].rate/100.0)));
69 ext::shared_ptr<BootstrapHelper<T> > anInstrument(new U(quote, observationLag, maturity,
70 calendar, bdc, dc, ii,
71 CPI::AsIndex, discountCurve));
72 instruments.push_back(anInstrument);
73 }
74
75 return instruments;
76 }
77
78
79 struct CommonVars {
80 // common data
81
82 Size length;
83 Date startDate;
84 Real volatility;
85
86 Frequency frequency;
87 std::vector<Real> nominals;
88 Calendar calendar;
89 BusinessDayConvention convention;
90 Natural fixingDays;
91 Date evaluationDate;
92 Natural settlementDays;
93 Date settlement;
94 Period observationLag, contractObservationLag;
95 CPI::InterpolationType contractObservationInterpolation;
96 DayCounter dcZCIIS,dcNominal;
97 std::vector<Date> zciisD;
98 std::vector<Rate> zciisR;
99 ext::shared_ptr<UKRPI> ii;
100 Size zciisDataLength;
101
102 RelinkableHandle<YieldTermStructure> nominalTS;
103 ext::shared_ptr<ZeroInflationTermStructure> cpiTS;
104 RelinkableHandle<ZeroInflationTermStructure> hcpi;
105
106 // setup
107 CommonVars()
108 : nominals(1,1000000) {
109
110 // option variables
111 frequency = Annual;
112 // usual setup
113 volatility = 0.01;
114 length = 7;
115 calendar = UnitedKingdom();
116 convention = ModifiedFollowing;
117 Date today(25, November, 2009);
118 evaluationDate = calendar.adjust(today);
119 Settings::instance().evaluationDate() = evaluationDate;
120 settlementDays = 0;
121 fixingDays = 0;
122 settlement = calendar.advance(today,n: settlementDays,unit: Days);
123 startDate = settlement;
124 dcZCIIS = ActualActual(ActualActual::ISDA);
125 dcNominal = ActualActual(ActualActual::ISDA);
126
127 // uk rpi index
128 // fixing data
129 Date from(20, July, 2007);
130 //Date from(20, July, 2008);
131 Date to(20, November, 2009);
132 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
133 .withTenor(1*Months)
134 .withCalendar(UnitedKingdom())
135 .withConvention(ModifiedFollowing);
136 Real fixData[] = {
137 206.1, 207.3, 208.0, 208.9, 209.7, 210.9,
138 209.8, 211.4, 212.1, 214.0, 215.1, 216.8,
139 216.5, 217.2, 218.4, 217.7, 216,
140 212.9, 210.1, 211.4, 211.3, 211.5,
141 212.8, 213.4, 213.4, 213.4, 214.4,
142 -999.0, -999.0 };
143
144 // link from cpi index to cpi TS
145 ii = ext::make_shared<UKRPI>(args&: hcpi);
146 for (Size i=0; i<rpiSchedule.size();i++) {
147 ii->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i], forceOverwrite: true);// force overwrite in case multiple use
148 };
149
150
151 Datum nominalData[] = {
152 { .date: Date(26, November, 2009), .rate: 0.475 },
153 { .date: Date(2, December, 2009), .rate: 0.47498 },
154 { .date: Date(29, December, 2009), .rate: 0.49988 },
155 { .date: Date(25, February, 2010), .rate: 0.59955 },
156 { .date: Date(18, March, 2010), .rate: 0.65361 },
157 { .date: Date(25, May, 2010), .rate: 0.82830 },
158 // { Date(17, June, 2010), 0.7 }, // can't bootstrap with this data point
159 { .date: Date(16, September, 2010), .rate: 0.78960 },
160 { .date: Date(16, December, 2010), .rate: 0.93762 },
161 { .date: Date(17, March, 2011), .rate: 1.12037 },
162 { .date: Date(16, June, 2011), .rate: 1.31308 },
163 { .date: Date(22, September, 2011),.rate: 1.52011 },
164 { .date: Date(25, November, 2011), .rate: 1.78399 },
165 { .date: Date(26, November, 2012), .rate: 2.41170 },
166 { .date: Date(25, November, 2013), .rate: 2.83935 },
167 { .date: Date(25, November, 2014), .rate: 3.12888 },
168 { .date: Date(25, November, 2015), .rate: 3.34298 },
169 { .date: Date(25, November, 2016), .rate: 3.50632 },
170 { .date: Date(27, November, 2017), .rate: 3.63666 },
171 { .date: Date(26, November, 2018), .rate: 3.74723 },
172 { .date: Date(25, November, 2019), .rate: 3.83988 },
173 { .date: Date(25, November, 2021), .rate: 4.00508 },
174 { .date: Date(25, November, 2024), .rate: 4.16042 },
175 { .date: Date(26, November, 2029), .rate: 4.15577 },
176 { .date: Date(27, November, 2034), .rate: 4.04933 },
177 { .date: Date(25, November, 2039), .rate: 3.95217 },
178 { .date: Date(25, November, 2049), .rate: 3.80932 },
179 { .date: Date(25, November, 2059), .rate: 3.80849 },
180 { .date: Date(25, November, 2069), .rate: 3.72677 },
181 { .date: Date(27, November, 2079), .rate: 3.63082 }
182 };
183
184 std::vector<Date> nomD;
185 std::vector<Rate> nomR;
186 for (auto& i : nominalData) {
187 nomD.push_back(x: i.date);
188 nomR.push_back(x: i.rate / 100.0);
189 }
190 ext::shared_ptr<YieldTermStructure> nominal =
191 ext::make_shared<InterpolatedZeroCurve<Linear>>(args&: nomD,args&: nomR,args&: dcNominal);
192
193 nominalTS.linkTo(h: nominal);
194
195 // now build the zero inflation curve
196 observationLag = Period(2,Months);
197 contractObservationLag = Period(3,Months);
198 contractObservationInterpolation = CPI::Flat;
199
200 Datum zciisData[] = {
201 { .date: Date(25, November, 2010), .rate: 3.0495 },
202 { .date: Date(25, November, 2011), .rate: 2.93 },
203 { .date: Date(26, November, 2012), .rate: 2.9795 },
204 { .date: Date(25, November, 2013), .rate: 3.029 },
205 { .date: Date(25, November, 2014), .rate: 3.1425 },
206 { .date: Date(25, November, 2015), .rate: 3.211 },
207 { .date: Date(25, November, 2016), .rate: 3.2675 },
208 { .date: Date(25, November, 2017), .rate: 3.3625 },
209 { .date: Date(25, November, 2018), .rate: 3.405 },
210 { .date: Date(25, November, 2019), .rate: 3.48 },
211 { .date: Date(25, November, 2021), .rate: 3.576 },
212 { .date: Date(25, November, 2024), .rate: 3.649 },
213 { .date: Date(26, November, 2029), .rate: 3.751 },
214 { .date: Date(27, November, 2034), .rate: 3.77225 },
215 { .date: Date(25, November, 2039), .rate: 3.77 },
216 { .date: Date(25, November, 2049), .rate: 3.734 },
217 { .date: Date(25, November, 2059), .rate: 3.714 },
218 };
219 zciisDataLength = 17;
220 for (Size i = 0; i < zciisDataLength; i++) {
221 zciisD.push_back(x: zciisData[i].date);
222 zciisR.push_back(x: zciisData[i].rate);
223 }
224
225 // now build the helpers ...
226 std::vector<ext::shared_ptr<BootstrapHelper<ZeroInflationTermStructure> > > helpers =
227 makeHelpers<ZeroInflationTermStructure,ZeroCouponInflationSwapHelper,
228 ZeroInflationIndex>(iiData: zciisData, N: zciisDataLength, ii,
229 observationLag,
230 calendar, bdc: convention, dc: dcZCIIS,
231 discountCurve: Handle<YieldTermStructure>(nominalTS));
232
233 // we can use historical or first ZCIIS for this
234 // we know historical is WAY off market-implied, so use market implied flat.
235 Rate baseZeroRate = zciisData[0].rate/100.0;
236 ext::shared_ptr<PiecewiseZeroInflationCurve<Linear> > pCPIts(
237 new PiecewiseZeroInflationCurve<Linear>(
238 evaluationDate, calendar, dcZCIIS, observationLag,
239 ii->frequency(), baseZeroRate, helpers));
240 pCPIts->recalculate();
241 cpiTS = ext::dynamic_pointer_cast<ZeroInflationTermStructure>(r: pCPIts);
242
243
244 // make sure that the index has the latest zero inflation term structure
245 hcpi.linkTo(h: pCPIts);
246 }
247 };
248
249}
250
251
252
253void CPISwapTest::consistency() {
254 BOOST_TEST_MESSAGE("Checking CPI swap against inflation term structure...");
255
256 using namespace inflation_cpi_swap_test;
257
258 bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons();
259
260 // check inflation leg vs calculation directly from inflation TS
261 CommonVars common;
262
263 // ZeroInflationSwap aka CPISwap
264
265 Swap::Type type = Swap::Payer;
266 Real nominal = 1000000.0;
267 bool subtractInflationNominal = true;
268 // float+spread leg
269 Spread spread = 0.0;
270 DayCounter floatDayCount = Actual365Fixed();
271 BusinessDayConvention floatPaymentConvention = ModifiedFollowing;
272 Natural fixingDays = 0;
273 ext::shared_ptr<IborIndex> floatIndex(new GBPLibor(Period(6,Months),
274 common.nominalTS));
275
276 // fixed x inflation leg
277 Rate fixedRate = 0.1;//1% would be 0.01
278 Real baseCPI = 206.1; // would be 206.13871 if we were interpolating
279 DayCounter fixedDayCount = Actual365Fixed();
280 BusinessDayConvention fixedPaymentConvention = ModifiedFollowing;
281 Calendar fixedPaymentCalendar = UnitedKingdom();
282 ext::shared_ptr<ZeroInflationIndex> fixedIndex = common.ii;
283 Period contractObservationLag = common.contractObservationLag;
284 CPI::InterpolationType observationInterpolation = common.contractObservationInterpolation;
285
286 // set the schedules
287 Date startDate(2, October, 2007);
288 Date endDate(2, October, 2052);
289 Schedule floatSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate)
290 .withTenor(Period(6,Months))
291 .withCalendar(UnitedKingdom())
292 .withConvention(floatPaymentConvention)
293 .backwards()
294 ;
295 Schedule fixedSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate)
296 .withTenor(Period(6,Months))
297 .withCalendar(UnitedKingdom())
298 .withConvention(Unadjusted)
299 .backwards()
300 ;
301
302
303 CPISwap zisV(type, nominal, subtractInflationNominal,
304 spread, floatDayCount, floatSchedule,
305 floatPaymentConvention, fixingDays, floatIndex,
306 fixedRate, baseCPI, fixedDayCount, fixedSchedule,
307 fixedPaymentConvention, contractObservationLag,
308 fixedIndex, observationInterpolation);
309 Date asofDate = Settings::instance().evaluationDate();
310
311 Real floatFix[] = {0.06255,0.05975,0.0637,0.018425,0.0073438,-1,-1};
312 Real cpiFix[] = {211.4,217.2,211.4,213.4,-2,-2};
313 for(Size i=0;i<floatSchedule.size(); i++){
314 if (floatSchedule[i] < common.evaluationDate) {
315 floatIndex->addFixing(fixingDate: floatSchedule[i], fixing: floatFix[i],forceOverwrite: true);//true=overwrite
316 }
317
318 ext::shared_ptr<CPICoupon>
319 zic = ext::dynamic_pointer_cast<CPICoupon>(r: zisV.cpiLeg()[i]);
320 if (zic != nullptr) {
321 if (zic->fixingDate() < (common.evaluationDate - Period(1,Months))) {
322 fixedIndex->addFixing(fixingDate: zic->fixingDate(), fixing: cpiFix[i],forceOverwrite: true);
323 }
324 }
325 }
326
327 // simple structure so simple pricing engine - most work done by index
328 ext::shared_ptr<DiscountingSwapEngine> dse(new DiscountingSwapEngine(common.nominalTS));
329 zisV.setPricingEngine(dse);
330
331 // get float+spread & fixed*inflation leg prices separately
332 Real testInfLegNPV = 0.0;
333 for(Size i=0;i<zisV.leg(j: 0).size(); i++){
334
335 Date zicPayDate = (zisV.leg(j: 0))[i]->date();
336 if(zicPayDate > asofDate) {
337 testInfLegNPV += (zisV.leg(j: 0))[i]->amount()*common.nominalTS->discount(d: zicPayDate);
338 }
339
340 ext::shared_ptr<CPICoupon>
341 zicV = ext::dynamic_pointer_cast<CPICoupon>(r: zisV.cpiLeg()[i]);
342 if (zicV != nullptr) {
343 Real diff = fabs( x: zicV->rate() - (fixedRate*(zicV->indexFixing()/baseCPI)) );
344 QL_REQUIRE(diff<1e-8,"failed "<<i<<"th coupon reconstruction as "
345 << (fixedRate*(zicV->indexFixing()/baseCPI)) << " vs rate = "
346 <<zicV->rate() << ", with difference: " << diff);
347 }
348 }
349
350 Real error = fabs(x: testInfLegNPV - zisV.legNPV(j: 0));
351 QL_REQUIRE(error<1e-5,
352 "failed manual inf leg NPV calc vs pricing engine: " <<
353 testInfLegNPV << " vs " << zisV.legNPV(0));
354
355 Real diff = fabs(x: 1-zisV.NPV()/4191660.0);
356
357 Real max_diff = usingAtParCoupons ? 1e-5 : 3e-5;
358
359 QL_REQUIRE(diff<max_diff,
360 "failed stored consistency value test, ratio = " << diff);
361
362 // remove circular refernce
363 common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>());
364}
365
366
367void CPISwapTest::zciisconsistency() {
368 BOOST_TEST_MESSAGE("Checking CPI swap against zero-coupon inflation swap...");
369
370 using namespace inflation_cpi_swap_test;
371
372 CommonVars common;
373
374 Swap::Type ztype = Swap::Payer;
375 Real nominal = 1000000.0;
376 Date startDate(common.evaluationDate);
377 Date endDate(25, November, 2059);
378 Calendar cal = UnitedKingdom();
379 BusinessDayConvention paymentConvention = ModifiedFollowing;
380 DayCounter dummyDC, dc = ActualActual(ActualActual::ISDA);
381 Period observationLag(2,Months);
382
383 Rate quote = 0.03714;
384 ZeroCouponInflationSwap zciis(ztype, nominal, startDate, endDate, cal, paymentConvention, dc,
385 quote, common.ii, observationLag, CPI::AsIndex);
386
387 // simple structure so simple pricing engine - most work done by index
388 ext::shared_ptr<DiscountingSwapEngine>
389 dse(new DiscountingSwapEngine(common.nominalTS));
390
391 zciis.setPricingEngine(dse);
392 QL_REQUIRE(fabs(zciis.NPV())<1e-3,"zciis does not reprice to zero");
393
394 std::vector<Date> oneDate = {endDate};
395 Schedule schOneDate(oneDate, cal, paymentConvention);
396
397 Swap::Type stype = Swap::Payer;
398 Real inflationNominal = nominal;
399 Real floatNominal = inflationNominal * std::pow(x: 1.0+quote,y: 50);
400 bool subtractInflationNominal = true;
401 Real dummySpread=0.0, dummyFixedRate=0.0;
402 Natural fixingDays = 0;
403 Date baseDate = startDate - observationLag;
404 Real baseCPI = common.ii->fixing(fixingDate: baseDate);
405
406 ext::shared_ptr<IborIndex> dummyFloatIndex;
407
408 CPISwap cS(stype, floatNominal, subtractInflationNominal, dummySpread, dummyDC, schOneDate,
409 paymentConvention, fixingDays, dummyFloatIndex,
410 dummyFixedRate, baseCPI, dummyDC, schOneDate, paymentConvention, observationLag,
411 common.ii, CPI::AsIndex, inflationNominal);
412
413 cS.setPricingEngine(dse);
414 QL_REQUIRE(fabs(cS.NPV())<1e-3,"CPISwap as ZCIIS does not reprice to zero");
415
416 for (Size i=0; i<2; i++) {
417 QL_REQUIRE(fabs(cS.legNPV(i)-zciis.legNPV(i))<1e-3,"zciis leg does not equal CPISwap leg");
418 }
419 // remove circular refernce
420 common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>());
421}
422
423
424void CPISwapTest::cpibondconsistency() {
425 BOOST_TEST_MESSAGE("Checking CPI swap against CPI bond...");
426
427 using namespace inflation_cpi_swap_test;
428
429 CommonVars common;
430
431 // ZeroInflationSwap aka CPISwap
432
433 Swap::Type type = Swap::Payer;
434 Real nominal = 1000000.0;
435 bool subtractInflationNominal = true;
436 // float+spread leg
437 Spread spread = 0.0;
438 DayCounter floatDayCount = Actual365Fixed();
439 BusinessDayConvention floatPaymentConvention = ModifiedFollowing;
440 Natural fixingDays = 0;
441 ext::shared_ptr<IborIndex> floatIndex(new GBPLibor(Period(6,Months),
442 common.nominalTS));
443
444 // fixed x inflation leg
445 Rate fixedRate = 0.1;//1% would be 0.01
446 Real baseCPI = 206.1; // would be 206.13871 if we were interpolating
447 DayCounter fixedDayCount = Actual365Fixed();
448 BusinessDayConvention fixedPaymentConvention = ModifiedFollowing;
449 Calendar fixedPaymentCalendar = UnitedKingdom();
450 ext::shared_ptr<ZeroInflationIndex> fixedIndex = common.ii;
451 Period contractObservationLag = common.contractObservationLag;
452 CPI::InterpolationType observationInterpolation = common.contractObservationInterpolation;
453
454 // set the schedules
455 Date startDate(2, October, 2007);
456 Date endDate(2, October, 2052);
457 Schedule floatSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate)
458 .withTenor(Period(6,Months))
459 .withCalendar(UnitedKingdom())
460 .withConvention(floatPaymentConvention)
461 .backwards()
462 ;
463 Schedule fixedSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate)
464 .withTenor(Period(6,Months))
465 .withCalendar(UnitedKingdom())
466 .withConvention(Unadjusted)
467 .backwards()
468 ;
469
470
471 CPISwap zisV(type, nominal, subtractInflationNominal,
472 spread, floatDayCount, floatSchedule,
473 floatPaymentConvention, fixingDays, floatIndex,
474 fixedRate, baseCPI, fixedDayCount, fixedSchedule,
475 fixedPaymentConvention, contractObservationLag,
476 fixedIndex, observationInterpolation);
477
478 Real floatFix[] = {0.06255,0.05975,0.0637,0.018425,0.0073438,-1,-1};
479 Real cpiFix[] = {211.4,217.2,211.4,213.4,-2,-2};
480 for(Size i=0;i<floatSchedule.size(); i++){
481 if (floatSchedule[i] < common.evaluationDate) {
482 floatIndex->addFixing(fixingDate: floatSchedule[i], fixing: floatFix[i],forceOverwrite: true);//true=overwrite
483 }
484
485 ext::shared_ptr<CPICoupon>
486 zic = ext::dynamic_pointer_cast<CPICoupon>(r: zisV.cpiLeg()[i]);
487 if (zic != nullptr) {
488 if (zic->fixingDate() < (common.evaluationDate - Period(1,Months))) {
489 fixedIndex->addFixing(fixingDate: zic->fixingDate(), fixing: cpiFix[i],forceOverwrite: true);
490 }
491 }
492 }
493
494
495 // simple structure so simple pricing engine - most work done by index
496 ext::shared_ptr<DiscountingSwapEngine> dse(new DiscountingSwapEngine(common.nominalTS));
497 zisV.setPricingEngine(dse);
498
499 // now do the bond equivalent
500 std::vector<Rate> fixedRates(1,fixedRate);
501 Natural settlementDays = 1;// cannot be zero!
502 bool growthOnly = true;
503 CPIBond cpiB(settlementDays, nominal, growthOnly,
504 baseCPI, contractObservationLag, fixedIndex,
505 observationInterpolation, fixedSchedule,
506 fixedRates, fixedDayCount, fixedPaymentConvention);
507
508 ext::shared_ptr<DiscountingBondEngine> dbe(new DiscountingBondEngine(common.nominalTS));
509 cpiB.setPricingEngine(dbe);
510
511 QL_REQUIRE(fabs(cpiB.NPV() - zisV.legNPV(0))<1e-5,"cpi bond does not equal equivalent cpi swap leg");
512 // remove circular reference
513 common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>());
514}
515
516
517test_suite* CPISwapTest::suite() {
518 auto* suite = BOOST_TEST_SUITE("CPISwap tests");
519
520 suite->add(QUANTLIB_TEST_CASE(&CPISwapTest::consistency));
521 suite->add(QUANTLIB_TEST_CASE(&CPISwapTest::zciisconsistency));
522 suite->add(QUANTLIB_TEST_CASE(&CPISwapTest::cpibondconsistency));
523
524 return suite;
525}
526
527

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