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