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