1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2011 Chris Kenyon
5
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
22#include "inflationcpicapfloor.hpp"
23#include "utilities.hpp"
24#include <ql/types.hpp>
25#include <ql/indexes/inflation/ukrpi.hpp>
26#include <ql/termstructures/bootstraphelper.hpp>
27#include <ql/time/calendars/unitedkingdom.hpp>
28#include <ql/time/daycounters/actualactual.hpp>
29#include <ql/time/daycounters/actual365fixed.hpp>
30#include <ql/termstructures/yield/zerocurve.hpp>
31#include <ql/indexes/ibor/gbplibor.hpp>
32#include <ql/termstructures/inflation/inflationhelpers.hpp>
33#include <ql/termstructures/inflation/piecewisezeroinflationcurve.hpp>
34#include <ql/cashflows/indexedcashflow.hpp>
35#include <ql/pricingengines/swap/discountingswapengine.hpp>
36#include <ql/instruments/zerocouponinflationswap.hpp>
37#include <ql/pricingengines/bond/discountingbondengine.hpp>
38#include <ql/math/interpolations/bilinearinterpolation.hpp>
39#include <ql/cashflows/cpicoupon.hpp>
40#include <ql/cashflows/cpicouponpricer.hpp>
41#include <ql/instruments/cpiswap.hpp>
42#include <ql/instruments/bonds/cpibond.hpp>
43#include <ql/instruments/cpicapfloor.hpp>
44#include <ql/experimental/inflation/cpicapfloortermpricesurface.hpp>
45#include <ql/experimental/inflation/cpicapfloorengines.hpp>
46
47#include <iostream>
48
49
50using namespace QuantLib;
51using namespace boost::unit_test_framework;
52
53
54namespace inflation_cpi_capfloor_test {
55 struct Datum {
56 Date date;
57 Rate rate;
58 };
59
60 template <class T, class U, class I>
61 std::vector<ext::shared_ptr<BootstrapHelper<T> > > makeHelpers(
62 Datum iiData[], Size N,
63 const ext::shared_ptr<I> &ii, const Period &observationLag,
64 const Calendar &calendar,
65 const BusinessDayConvention &bdc,
66 const DayCounter &dc,
67 const Handle<YieldTermStructure>& discountCurve) {
68
69 std::vector<ext::shared_ptr<BootstrapHelper<T> > > instruments;
70 for (Size i=0; i<N; i++) {
71 Date maturity = iiData[i].date;
72 Handle<Quote> quote(ext::shared_ptr<Quote>(
73 new SimpleQuote(iiData[i].rate/100.0)));
74 ext::shared_ptr<BootstrapHelper<T> > anInstrument(new U(quote, observationLag, maturity,
75 calendar, bdc, dc, ii,
76 CPI::AsIndex, discountCurve));
77 instruments.push_back(anInstrument);
78 }
79
80 return instruments;
81 }
82
83
84 struct CommonVars {
85
86 // common data
87
88 Size length;
89 Date startDate;
90 Rate baseZeroRate;
91 Real volatility;
92
93 Frequency frequency;
94 std::vector<Real> nominals;
95 Calendar calendar;
96 BusinessDayConvention convention;
97 Natural fixingDays;
98 Date evaluationDate;
99 Natural settlementDays;
100 Date settlement;
101 Period observationLag, contractObservationLag;
102 CPI::InterpolationType contractObservationInterpolation;
103 DayCounter dcZCIIS,dcNominal;
104 std::vector<Date> zciisD;
105 std::vector<Rate> zciisR;
106 ext::shared_ptr<UKRPI> ii;
107 Size zciisDataLength;
108
109 RelinkableHandle<YieldTermStructure> nominalUK;
110 RelinkableHandle<ZeroInflationTermStructure> cpiUK;
111 RelinkableHandle<ZeroInflationTermStructure> hcpi;
112
113 std::vector<Rate> cStrikesUK;
114 std::vector<Rate> fStrikesUK;
115 std::vector<Period> cfMaturitiesUK;
116 ext::shared_ptr<Matrix> cPriceUK;
117 ext::shared_ptr<Matrix> fPriceUK;
118
119 ext::shared_ptr<CPICapFloorTermPriceSurface> cpiCFsurfUK;
120
121 // setup
122 CommonVars()
123 : nominals(1,1000000) {
124 //std::cout <<"CommonVars" << std::endl;
125 // option variables
126 frequency = Annual;
127 // usual setup
128 volatility = 0.01;
129 length = 7;
130 calendar = UnitedKingdom();
131 convention = ModifiedFollowing;
132 Date today(1, June, 2010);
133 evaluationDate = calendar.adjust(today);
134 Settings::instance().evaluationDate() = evaluationDate;
135 settlementDays = 0;
136 fixingDays = 0;
137 settlement = calendar.advance(today,n: settlementDays,unit: Days);
138 startDate = settlement;
139 dcZCIIS = ActualActual(ActualActual::ISDA);
140 dcNominal = ActualActual(ActualActual::ISDA);
141
142 // uk rpi index
143 // fixing data
144 Date from(1, July, 2007);
145 Date to(1, June, 2010);
146 Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
147 .withTenor(1*Months)
148 .withCalendar(UnitedKingdom())
149 .withConvention(ModifiedFollowing);
150 Real fixData[] = {
151 206.1, 207.3, 208.0, 208.9, 209.7, 210.9,
152 209.8, 211.4, 212.1, 214.0, 215.1, 216.8, // 2008
153 216.5, 217.2, 218.4, 217.7, 216.0, 212.9,
154 210.1, 211.4, 211.3, 211.5, 212.8, 213.4, // 2009
155 213.4, 214.4, 215.3, 216.0, 216.6, 218.0,
156 217.9, 219.2, 220.7, 222.8, -999, -999, // 2010
157 -999};
158
159 // link from cpi index to cpi TS
160 ii = ext::make_shared<UKRPI>(args&: hcpi);
161 for (Size i=0; i<rpiSchedule.size();i++) {
162 ii->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i], forceOverwrite: true);// force overwrite in case multiple use
163 };
164
165
166 Datum nominalData[] = {
167 { .date: Date( 2, June, 2010), .rate: 0.499997 },
168 { .date: Date( 3, June, 2010), .rate: 0.524992 },
169 { .date: Date( 8, June, 2010), .rate: 0.524974 },
170 { .date: Date( 15, June, 2010), .rate: 0.549942 },
171 { .date: Date( 22, June, 2010), .rate: 0.549913 },
172 { .date: Date( 1, July, 2010), .rate: 0.574864 },
173 { .date: Date( 2, August, 2010), .rate: 0.624668 },
174 { .date: Date( 1, September, 2010), .rate: 0.724338 },
175 { .date: Date( 16, September, 2010), .rate: 0.769461 },
176 { .date: Date( 1, December, 2010), .rate: 0.997501 },
177 //{ Date( 16, December, 2010), 0.838164 },
178 { .date: Date( 17, March, 2011), .rate: 0.916996 },
179 { .date: Date( 16, June, 2011), .rate: 0.984339 },
180 { .date: Date( 22, September, 2011), .rate: 1.06085 },
181 { .date: Date( 22, December, 2011), .rate: 1.141788 },
182 { .date: Date( 1, June, 2012), .rate: 1.504426 },
183 { .date: Date( 3, June, 2013), .rate: 1.92064 },
184 { .date: Date( 2, June, 2014), .rate: 2.290824 },
185 { .date: Date( 1, June, 2015), .rate: 2.614394 },
186 { .date: Date( 1, June, 2016), .rate: 2.887445 },
187 { .date: Date( 1, June, 2017), .rate: 3.122128 },
188 { .date: Date( 1, June, 2018), .rate: 3.322511 },
189 { .date: Date( 3, June, 2019), .rate: 3.483997 },
190 { .date: Date( 1, June, 2020), .rate: 3.616896 },
191 { .date: Date( 1, June, 2022), .rate: 3.8281 },
192 { .date: Date( 2, June, 2025), .rate: 4.0341 },
193 { .date: Date( 3, June, 2030), .rate: 4.070854 },
194 { .date: Date( 1, June, 2035), .rate: 4.023202 },
195 { .date: Date( 1, June, 2040), .rate: 3.954748 },
196 { .date: Date( 1, June, 2050), .rate: 3.870953 },
197 { .date: Date( 1, June, 2060), .rate: 3.85298 },
198 { .date: Date( 2, June, 2070), .rate: 3.757542 },
199 { .date: Date( 3, June, 2080), .rate: 3.651379 }
200 };
201
202 std::vector<Date> nomD;
203 std::vector<Rate> nomR;
204 for (auto& i : nominalData) {
205 nomD.push_back(x: i.date);
206 nomR.push_back(x: i.rate / 100.0);
207 }
208 ext::shared_ptr<YieldTermStructure> nominalTS =
209 ext::make_shared<InterpolatedZeroCurve<Linear>>(args&: nomD,args&: nomR,args&: dcNominal);
210
211 nominalUK.linkTo(h: nominalTS);
212
213
214 // now build the zero inflation curve
215 observationLag = Period(2,Months);
216 contractObservationLag = Period(3,Months);
217 contractObservationInterpolation = CPI::Flat;
218
219 Datum zciisData[] = {
220 { .date: Date(1, June, 2011), .rate: 3.087 },
221 { .date: Date(1, June, 2012), .rate: 3.12 },
222 { .date: Date(1, June, 2013), .rate: 3.059 },
223 { .date: Date(1, June, 2014), .rate: 3.11 },
224 { .date: Date(1, June, 2015), .rate: 3.15 },
225 { .date: Date(1, June, 2016), .rate: 3.207 },
226 { .date: Date(1, June, 2017), .rate: 3.253 },
227 { .date: Date(1, June, 2018), .rate: 3.288 },
228 { .date: Date(1, June, 2019), .rate: 3.314 },
229 { .date: Date(1, June, 2020), .rate: 3.401 },
230 { .date: Date(1, June, 2022), .rate: 3.458 },
231 { .date: Date(1, June, 2025), .rate: 3.52 },
232 { .date: Date(1, June, 2030), .rate: 3.655 },
233 { .date: Date(1, June, 2035), .rate: 3.668 },
234 { .date: Date(1, June, 2040), .rate: 3.695 },
235 { .date: Date(1, June, 2050), .rate: 3.634 },
236 { .date: Date(1, June, 2060), .rate: 3.629 },
237 };
238 zciisDataLength = 17;
239 for (Size i = 0; i < zciisDataLength; i++) {
240 zciisD.push_back(x: zciisData[i].date);
241 zciisR.push_back(x: zciisData[i].rate);
242 }
243
244 // now build the helpers ...
245 std::vector<ext::shared_ptr<BootstrapHelper<ZeroInflationTermStructure> > > helpers =
246 makeHelpers<ZeroInflationTermStructure,ZeroCouponInflationSwapHelper,
247 ZeroInflationIndex>(iiData: zciisData, N: zciisDataLength, ii,
248 observationLag,
249 calendar, bdc: convention, dc: dcZCIIS,
250 discountCurve: Handle<YieldTermStructure>(nominalTS));
251
252 // we can use historical or first ZCIIS for this
253 // we know historical is WAY off market-implied, so use market implied flat.
254 baseZeroRate = zciisData[0].rate/100.0;
255 ext::shared_ptr<PiecewiseZeroInflationCurve<Linear> > pCPIts(
256 new PiecewiseZeroInflationCurve<Linear>(
257 evaluationDate, calendar, dcZCIIS, observationLag,
258 ii->frequency(), baseZeroRate, helpers));
259 pCPIts->recalculate();
260 cpiUK.linkTo(h: pCPIts);
261
262 // make sure that the index has the latest zero inflation term structure
263 hcpi.linkTo(h: pCPIts);
264
265 // cpi CF price surf data
266 Period cfMat[] = {3*Years, 5*Years, 7*Years, 10*Years, 15*Years, 20*Years, 30*Years};
267 Real cStrike[] = {0.03, 0.04, 0.05, 0.06};
268 Real fStrike[] = {-0.01, 0, 0.01, 0.02};
269 Size ncStrikes = 4, nfStrikes = 4, ncfMaturities = 7;
270
271 Real cPrice[7][4] = {
272 {227.6, 100.27, 38.8, 14.94},
273 {345.32, 127.9, 40.59, 14.11},
274 {477.95, 170.19, 50.62, 16.88},
275 {757.81, 303.95, 107.62, 43.61},
276 {1140.73, 481.89, 168.4, 63.65},
277 {1537.6, 607.72, 172.27, 54.87},
278 {2211.67, 839.24, 184.75, 45.03}};
279 Real fPrice[7][4] = {
280 {15.62, 28.38, 53.61, 104.6},
281 {21.45, 36.73, 66.66, 129.6},
282 {24.45, 42.08, 77.04, 152.24},
283 {39.25, 63.52, 109.2, 203.44},
284 {36.82, 63.62, 116.97, 232.73},
285 {39.7, 67.47, 121.79, 238.56},
286 {41.48, 73.9, 139.75, 286.75}};
287
288 // now load the data into vector and Matrix classes
289 cStrikesUK.clear();
290 fStrikesUK.clear();
291 cfMaturitiesUK.clear();
292 for(Size i = 0; i < ncStrikes; i++) cStrikesUK.push_back(x: cStrike[i]);
293 for(Size i = 0; i < nfStrikes; i++) fStrikesUK.push_back(x: fStrike[i]);
294 for(Size i = 0; i < ncfMaturities; i++) cfMaturitiesUK.push_back(x: cfMat[i]);
295 cPriceUK = ext::make_shared<Matrix>(args&: ncStrikes, args&: ncfMaturities);
296 fPriceUK = ext::make_shared<Matrix>(args&: nfStrikes, args&: ncfMaturities);
297 for(Size i = 0; i < ncStrikes; i++) {
298 for(Size j = 0; j < ncfMaturities; j++) {
299 (*cPriceUK)[i][j] = cPrice[j][i]/10000.0;
300 }
301 }
302 for(Size i = 0; i < nfStrikes; i++) {
303 for(Size j = 0; j < ncfMaturities; j++) {
304 (*fPriceUK)[i][j] = fPrice[j][i]/10000.0;
305 }
306 }
307 }
308 };
309
310}
311
312
313
314void InflationCPICapFloorTest::cpicapfloorpricesurface() {
315 BOOST_TEST_MESSAGE("Checking CPI cap/floor against price surface...");
316
317 using namespace inflation_cpi_capfloor_test;
318
319 CommonVars common;
320
321 Real nominal = 1.0;
322 InterpolatedCPICapFloorTermPriceSurface
323 <Bilinear> cpiSurf(nominal,
324 common.baseZeroRate,
325 common.observationLag,
326 common.calendar,
327 common.convention,
328 common.dcZCIIS,
329 common.ii,
330 CPI::Flat,
331 common.nominalUK,
332 common.cStrikesUK,
333 common.fStrikesUK,
334 common.cfMaturitiesUK,
335 *(common.cPriceUK),
336 *(common.fPriceUK));
337
338 // test code - note order of indices
339 for (Size i =0; i<common.fStrikesUK.size(); i++){
340
341 Real qK = common.fStrikesUK[i];
342 Size nMat = common.cfMaturitiesUK.size();
343 for (Size j=0; j<nMat; j++) {
344 Period t = common.cfMaturitiesUK[j];
345 Real a = (*(common.fPriceUK))[i][j];
346 Real b = cpiSurf.floorPrice(d: t,k: qK);
347
348 QL_REQUIRE(fabs(a-b)<1e-7,"cannot reproduce cpi floor data from surface: "
349 << a << " vs constructed = " << b);
350 }
351
352 }
353
354 for (Size i =0; i<common.cStrikesUK.size(); i++){
355
356 Real qK = common.cStrikesUK[i];
357 Size nMat = common.cfMaturitiesUK.size();
358 for (Size j=0; j<nMat; j++) {
359 Period t = common.cfMaturitiesUK[j];
360 Real a = (*(common.cPriceUK))[i][j];
361 Real b = cpiSurf.capPrice(d: t,k: qK);
362
363 QL_REQUIRE(fabs(a-b)<1e-7,"cannot reproduce cpi cap data from surface: "
364 << a << " vs constructed = " << b);
365 }
366 }
367
368 // Test the price method also i.e. does it pick out the correct premium?
369 // Look up premium from surface at 3 years and strike of 1%
370 // Expect, as 1% < ATM, to get back floor premium at 1% i.e. 53.61 bps
371 Real premium = cpiSurf.price(d: 3 * Years, k: 0.01);
372 Real expPremium = (*common.fPriceUK)[2][0];
373 if (fabs(x: premium - expPremium) > 1e-12) {
374 BOOST_ERROR("The requested premium, " << premium
375 << ", does not equal the expected premium, " << expPremium << ".");
376 }
377
378 // remove circular refernce
379 common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>());
380}
381
382
383void InflationCPICapFloorTest::cpicapfloorpricer() {
384 BOOST_TEST_MESSAGE("Checking CPI cap/floor pricer...");
385
386 using namespace inflation_cpi_capfloor_test;
387
388 CommonVars common;
389 Real nominal = 1.0;
390 ext::shared_ptr<CPICapFloorTermPriceSurface> cpiCFpriceSurf(new InterpolatedCPICapFloorTermPriceSurface
391 <Bilinear>(nominal,
392 common.baseZeroRate,
393 common.observationLag,
394 common.calendar,
395 common.convention,
396 common.dcZCIIS,
397 common.ii,
398 CPI::Flat,
399 common.nominalUK,
400 common.cStrikesUK,
401 common.fStrikesUK,
402 common.cfMaturitiesUK,
403 *(common.cPriceUK),
404 *(common.fPriceUK)));
405
406 common.cpiCFsurfUK = cpiCFpriceSurf;
407
408 // interpolation pricer first
409 // N.B. no new instrument required but we do need a new pricer
410
411 Date startDate = Settings::instance().evaluationDate();
412 Date maturity(startDate + Period(3,Years));
413 Calendar fixCalendar = UnitedKingdom(), payCalendar = UnitedKingdom();
414 BusinessDayConvention fixConvention(Unadjusted), payConvention(ModifiedFollowing);
415 Rate strike(0.03);
416 Real baseCPI = common.ii->fixing(fixingDate: fixCalendar.adjust(startDate-common.observationLag,convention: fixConvention));
417 CPI::InterpolationType observationInterpolation = CPI::AsIndex;
418 CPICapFloor aCap(Option::Call,
419 nominal,
420 startDate, // start date of contract (only)
421 baseCPI,
422 maturity, // this is pre-adjustment!
423 fixCalendar,
424 fixConvention,
425 payCalendar,
426 payConvention,
427 strike,
428 common.ii,
429 common.observationLag,
430 observationInterpolation);
431
432 Handle<CPICapFloorTermPriceSurface> cpiCFsurfUKh(common.cpiCFsurfUK);
433 ext::shared_ptr<PricingEngine>engine(new InterpolatingCPICapFloorEngine(cpiCFsurfUKh));
434
435 aCap.setPricingEngine(engine);
436
437 // We should get back the cap premium at strike 0.03 i.e. 227.6 bps
438 Real cached = (*common.cPriceUK)[0][0];
439
440 QL_REQUIRE(fabs(cached - aCap.NPV())<1e-10,"InterpolatingCPICapFloorEngine does not reproduce cached price: "
441 << cached << " vs " << aCap.NPV());
442
443 // remove circular refernce
444 common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>());
445}
446
447
448
449
450test_suite* InflationCPICapFloorTest::suite() {
451 auto* suite = BOOST_TEST_SUITE("CPIswaption tests");
452
453 suite->add(QUANTLIB_TEST_CASE(&InflationCPICapFloorTest::cpicapfloorpricesurface));
454 suite->add(QUANTLIB_TEST_CASE(&InflationCPICapFloorTest::cpicapfloorpricer));
455
456 return suite;
457}
458
459

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