Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
cpicapfloor.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include "utilities.hpp"
20
21#include "toplevelfixture.hpp"
22#include <boost/test/unit_test.hpp>
23#include <ql/cashflows/cpicoupon.hpp>
24#include <ql/cashflows/cpicouponpricer.hpp>
25#include <ql/cashflows/indexedcashflow.hpp>
26#include <ql/experimental/inflation/cpicapfloorengines.hpp>
27#include <ql/experimental/inflation/cpicapfloortermpricesurface.hpp>
28#include <ql/indexes/ibor/gbplibor.hpp>
29#include <ql/indexes/inflation/euhicp.hpp>
30#include <ql/indexes/inflation/ukrpi.hpp>
31#include <ql/instruments/bonds/cpibond.hpp>
32#include <ql/instruments/cpicapfloor.hpp>
33#include <ql/instruments/cpiswap.hpp>
34#include <ql/instruments/zerocouponinflationswap.hpp>
35#include <ql/math/interpolations/bilinearinterpolation.hpp>
36#include <ql/pricingengines/blackformula.hpp>
37#include <ql/pricingengines/bond/discountingbondengine.hpp>
38#include <ql/pricingengines/swap/discountingswapengine.hpp>
39#include <ql/termstructures/bootstraphelper.hpp>
40#include <ql/termstructures/inflation/inflationhelpers.hpp>
41#include <ql/termstructures/inflation/piecewisezeroinflationcurve.hpp>
42#include <ql/termstructures/volatility/inflation/constantcpivolatility.hpp>
43#include <ql/termstructures/yield/flatforward.hpp>
44#include <ql/termstructures/yield/zerocurve.hpp>
45#include <ql/time/calendars/unitedkingdom.hpp>
46#include <ql/time/daycounters/actual365fixed.hpp>
47#include <ql/time/daycounters/actualactual.hpp>
48#include <ql/types.hpp>
53
54using namespace QuantLib;
55using namespace boost::unit_test_framework;
56using namespace std;
57
58namespace {
59struct Datum {
60 Date date;
61 Rate rate;
62};
63
64BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
65
66BOOST_AUTO_TEST_SUITE(CPICapFloorTest)
67
68template <class T, class U, class I>
69std::vector<QuantLib::ext::shared_ptr<BootstrapHelper<T> > > makeHelpers(Datum iiData[], Size N, const QuantLib::ext::shared_ptr<I>& ii,
70 const Period& observationLag, const Calendar& calendar,
71 const BusinessDayConvention& bdc, const DayCounter& dc,
72 Handle<YieldTermStructure> yts) {
73
74 std::vector<QuantLib::ext::shared_ptr<BootstrapHelper<T> > > instruments;
75 for (Size i = 0; i < N; i++) {
76 Date maturity = iiData[i].date;
77 Handle<Quote> quote(QuantLib::ext::shared_ptr<Quote>(new SimpleQuote(iiData[i].rate / 100.0)));
78 QuantLib::ext::shared_ptr<BootstrapHelper<T> > anInstrument(
79 new U(quote, observationLag, maturity, calendar, bdc, dc, ii, CPI::AsIndex, yts));
80 instruments.push_back(anInstrument);
81 }
82
83 return instruments;
84}
85
86// Copy of the test setup from QuantLib/test-suite/inflationcpicapfloor.cpp
87namespace {
88struct 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 QuantLib::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 QuantLib::ext::shared_ptr<Matrix> cPriceUK;
121 QuantLib::ext::shared_ptr<Matrix> fPriceUK;
122
123 QuantLib::ext::shared_ptr<CPICapFloorTermPriceSurface> cpiCFsurfUK;
124
125 // cleanup
126
127 SavedSettings backup;
128
129 // setup
130 CommonVars() : nominals(1, 1000000) {
131 // option variables
132 frequency = Annual;
133 // usual setup
134 volatility = 0.01;
135 length = 7;
136 calendar = UnitedKingdom();
137 convention = ModifiedFollowing;
138 Date today(1, June, 2010);
139 evaluationDate = calendar.adjust(today);
140 Settings::instance().evaluationDate() = evaluationDate;
141 settlementDays = 0;
142 fixingDays = 0;
143 settlement = calendar.advance(today, settlementDays, Days);
144 startDate = settlement;
145 dcZCIIS = ActualActual(ActualActual::ISDA);
146 dcNominal = ActualActual(ActualActual::ISDA);
147
148 // uk rpi index
149 // fixing data
150 Date from(1, July, 2007);
151 Date to(1, June, 2010);
152 Schedule rpiSchedule = MakeSchedule()
153 .from(from)
154 .to(to)
155 .withTenor(1 * Months)
156 .withCalendar(UnitedKingdom())
157 .withConvention(ModifiedFollowing);
158 Real fixData[] = { 206.1, 207.3, 208.0, 208.9, 209.7, 210.9, 209.8, 211.4, 212.1, 214.0, 215.1, 216.8, // 2008
159 216.5, 217.2, 218.4, 217.7, 216.0, 212.9, 210.1, 211.4, 211.3, 211.5, 212.8, 213.4, // 2009
160 213.4, 214.4, 215.3, 216.0, 216.6, 218.0, 217.9, 219.2, 220.7, 222.8, -999, -999, // 2010
161 -999 };
162
163 // link from cpi index to cpi TS
164 ii = QuantLib::ext::make_shared<UKRPI>(hcpi);
165 for (Size i = 0; i < rpiSchedule.size(); i++) {
166 ii->addFixing(rpiSchedule[i], fixData[i], true); // force overwrite in case multiple use
167 };
168
169 Datum nominalData[] = { { Date(2, June, 2010), 0.499997 },
170 { Date(3, June, 2010), 0.524992 },
171 { Date(8, June, 2010), 0.524974 },
172 { Date(15, June, 2010), 0.549942 },
173 { Date(22, June, 2010), 0.549913 },
174 { Date(1, July, 2010), 0.574864 },
175 { Date(2, August, 2010), 0.624668 },
176 { Date(1, September, 2010), 0.724338 },
177 { Date(16, September, 2010), 0.769461 },
178 { Date(1, December, 2010), 0.997501 },
179 //{ Date( 16, December, 2010), 0.838164 },
180 { Date(17, March, 2011), 0.916996 },
181 { Date(16, June, 2011), 0.984339 },
182 { Date(22, September, 2011), 1.06085 },
183 { Date(22, December, 2011), 1.141788 },
184 { Date(1, June, 2012), 1.504426 },
185 { Date(3, June, 2013), 1.92064 },
186 { Date(2, June, 2014), 2.290824 },
187 { Date(1, June, 2015), 2.614394 },
188 { Date(1, June, 2016), 2.887445 },
189 { Date(1, June, 2017), 3.122128 },
190 { Date(1, June, 2018), 3.322511 },
191 { Date(3, June, 2019), 3.483997 },
192 { Date(1, June, 2020), 3.616896 },
193 { Date(1, June, 2022), 3.8281 },
194 { Date(2, June, 2025), 4.0341 },
195 { Date(3, June, 2030), 4.070854 },
196 { Date(1, June, 2035), 4.023202 },
197 { Date(1, June, 2040), 3.954748 },
198 { Date(1, June, 2050), 3.870953 },
199 { Date(1, June, 2060), 3.85298 },
200 { Date(2, June, 2070), 3.757542 },
201 { Date(3, June, 2080), 3.651379 } };
202 const Size nominalDataLength = 33 - 1;
203
204 std::vector<Date> nomD;
205 std::vector<Rate> nomR;
206 for (Size i = 0; i < nominalDataLength; i++) {
207 nomD.push_back(nominalData[i].date);
208 nomR.push_back(nominalData[i].rate / 100.0);
209 }
210 QuantLib::ext::shared_ptr<YieldTermStructure> nominalTS =
211 QuantLib::ext::make_shared<InterpolatedZeroCurve<Linear> >(nomD, nomR, dcNominal);
212
213 nominalUK.linkTo(nominalTS);
214
215 // now build the zero inflation curve
216 observationLag = Period(2, Months);
217 contractObservationLag = Period(3, Months);
218 contractObservationInterpolation = CPI::Flat;
219
220 Datum zciisData[] = {
221 { Date(1, June, 2011), 3.087 }, { Date(1, June, 2012), 3.12 }, { Date(1, June, 2013), 3.059 },
222 { Date(1, June, 2014), 3.11 }, { Date(1, June, 2015), 3.15 }, { Date(1, June, 2016), 3.207 },
223 { Date(1, June, 2017), 3.253 }, { Date(1, June, 2018), 3.288 }, { Date(1, June, 2019), 3.314 },
224 { Date(1, June, 2020), 3.401 }, { Date(1, June, 2022), 3.458 }, { Date(1, June, 2025), 3.52 },
225 { Date(1, June, 2030), 3.655 }, { Date(1, June, 2035), 3.668 }, { Date(1, June, 2040), 3.695 },
226 { Date(1, June, 2050), 3.634 }, { Date(1, June, 2060), 3.629 },
227 };
228 zciisDataLength = 17;
229 for (Size i = 0; i < zciisDataLength; i++) {
230 zciisD.push_back(zciisData[i].date);
231 zciisR.push_back(zciisData[i].rate);
232 }
233
234 // now build the helpers ...
235 std::vector<QuantLib::ext::shared_ptr<BootstrapHelper<ZeroInflationTermStructure> > > helpers =
236 makeHelpers<ZeroInflationTermStructure, ZeroCouponInflationSwapHelper, ZeroInflationIndex>(
237 zciisData, zciisDataLength, ii, observationLag, calendar, convention, dcZCIIS,
238 Handle<YieldTermStructure>(nominalTS));
239
240 // we can use historical or first ZCIIS for this
241 // we know historical is WAY off market-implied, so use market implied flat.
242 baseZeroRate = zciisData[0].rate / 100.0;
243 QuantLib::ext::shared_ptr<PiecewiseZeroInflationCurve<Linear>> pCPIts(
244 new PiecewiseZeroInflationCurve<Linear>(evaluationDate, calendar, dcZCIIS, observationLag, ii->frequency(),
245 baseZeroRate, helpers));
246 pCPIts->recalculate();
247 cpiUK.linkTo(pCPIts);
248 hii.linkTo(ii);
249
250 // make sure that the index has the latest zero inflation term structure
251 hcpi.linkTo(pCPIts);
252
253 // cpi CF price surf data
254 Period cfMat[] = { 3 * Years, 5 * Years, 7 * Years, 10 * Years, 15 * Years, 20 * Years, 30 * Years };
255 Real cStrike[] = { 0.03, 0.04, 0.05, 0.06 };
256 Real fStrike[] = { -0.01, 0, 0.01, 0.02 };
257 Size ncStrikes = 4, nfStrikes = 4, ncfMaturities = 7;
258
259 Real cPrice[7][4] = { { 227.6, 100.27, 38.8, 14.94 }, { 345.32, 127.9, 40.59, 14.11 },
260 { 477.95, 170.19, 50.62, 16.88 }, { 757.81, 303.95, 107.62, 43.61 },
261 { 1140.73, 481.89, 168.4, 63.65 }, { 1537.6, 607.72, 172.27, 54.87 },
262 { 2211.67, 839.24, 184.75, 45.03 } };
263 Real fPrice[7][4] = { { 15.62, 28.38, 53.61, 104.6 }, { 21.45, 36.73, 66.66, 129.6 },
264 { 24.45, 42.08, 77.04, 152.24 }, { 39.25, 63.52, 109.2, 203.44 },
265 { 36.82, 63.62, 116.97, 232.73 }, { 39.7, 67.47, 121.79, 238.56 },
266 { 41.48, 73.9, 139.75, 286.75 } };
267
268 // now load the data into vector and Matrix classes
269 cStrikesUK.clear();
270 fStrikesUK.clear();
271 cfMaturitiesUK.clear();
272 for (Size i = 0; i < ncStrikes; i++)
273 cStrikesUK.push_back(cStrike[i]);
274 for (Size i = 0; i < nfStrikes; i++)
275 fStrikesUK.push_back(fStrike[i]);
276 for (Size i = 0; i < ncfMaturities; i++)
277 cfMaturitiesUK.push_back(cfMat[i]);
278 cPriceUK = QuantLib::ext::make_shared<Matrix>(ncStrikes, ncfMaturities);
279 fPriceUK = QuantLib::ext::make_shared<Matrix>(nfStrikes, ncfMaturities);
280 for (Size i = 0; i < ncStrikes; i++) {
281 for (Size j = 0; j < ncfMaturities; j++) {
282 (*cPriceUK)[i][j] = cPrice[j][i] / 10000.0;
283 }
284 }
285 for (Size i = 0; i < nfStrikes; i++) {
286 for (Size j = 0; j < ncfMaturities; j++) {
287 (*fPriceUK)[i][j] = fPrice[j][i] / 10000.0;
288 }
289 }
290
291 Real nominal = 1.0;
292 QuantLib::ext::shared_ptr<InterpolatedCPICapFloorTermPriceSurface<Bilinear> > intplCpiCFsurfUK(
293 new InterpolatedCPICapFloorTermPriceSurface<Bilinear>(
294 nominal, baseZeroRate, observationLag, calendar, convention, dcZCIIS, ii, CPI::AsIndex, nominalUK, cStrikesUK,
295 fStrikesUK, cfMaturitiesUK, *(cPriceUK), *(fPriceUK)));
296
297 cpiCFsurfUK = intplCpiCFsurfUK;
298 }
299};
300
301class FlatZeroInflationTermStructure : public ZeroInflationTermStructure {
302public:
303 FlatZeroInflationTermStructure(const Date& referenceDate, const Calendar& calendar, const DayCounter& dayCounter,
304 Rate zeroRate, const Period& observationLag, Frequency frequency, bool indexIsInterp,
305 const Handle<YieldTermStructure>& ts)
306 : ZeroInflationTermStructure(referenceDate, calendar, dayCounter, zeroRate, observationLag, frequency),
307 zeroRate_(zeroRate), indexIsInterp_(indexIsInterp) {}
308
309 Date maxDate() const override { return Date::maxDate(); }
310 // Base date consistent with observation lag, interpolation and frequency
311 Date baseDate() const override {
312 Date base = referenceDate() - observationLag();
313 if (!indexIsInterp_) {
314 std::pair<Date, Date> ips = inflationPeriod(base, frequency());
315 base = ips.first;
316 }
317 return base;
318 }
319
320private:
321 Rate zeroRateImpl(Time t) const override { return zeroRate_; }
322 Real zeroRate_;
323 bool indexIsInterp_;
324};
325
326} // namespace
327
328// additional test cases from here
329
330BOOST_AUTO_TEST_CASE(testVolatilitySurface) {
331 CommonVars common;
332
333 Handle<YieldTermStructure> nominalTS = common.nominalUK;
334
335 // this engine is used to imply the volatility that reproduces a quoted price
336 QuantLib::ext::shared_ptr<QuantExt::CPIBlackCapFloorEngine> blackEngine =
337 QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(nominalTS,
338 QuantLib::Handle<QuantLib::CPIVolatilitySurface>());
339
340 // wrap the pointer into a handle
341 Handle<CPICapFloorTermPriceSurface> cpiPriceSurfaceHandle(common.cpiCFsurfUK);
342
343 // initialize the vol surface, taking the price surface as an input and running the vol imply calculations
345 QuantLib::ext::shared_ptr<QuantExt::StrippedCPIVolatilitySurface<QuantLib::Bilinear> > cpiVolSurface =
346 QuantLib::ext::make_shared<QuantExt::StrippedCPIVolatilitySurface<QuantLib::Bilinear> >(type, cpiPriceSurfaceHandle,
347 common.ii, blackEngine);
348
349 // attach the implied vol surface to the engine
350 blackEngine->setVolatility(QuantLib::Handle<QuantLib::CPIVolatilitySurface>(cpiVolSurface));
351
352 // reprice and check that we recover the quotes
353
354 Real nominal = 1.0;
355
356 Date startDate = Settings::instance().evaluationDate();
357 Calendar fixCalendar = UnitedKingdom(), payCalendar = UnitedKingdom();
358 BusinessDayConvention fixConvention(Unadjusted), payConvention(ModifiedFollowing);
359 Real baseCPI = common.hii->fixing(fixCalendar.adjust(startDate - common.observationLag, fixConvention));
360 CPI::InterpolationType observationInterpolation = CPI::AsIndex;
361
362 Handle<CPICapFloorTermPriceSurface> cpiCFsurfUKh(common.cpiCFsurfUK);
363 QuantLib::ext::shared_ptr<PricingEngine> engine(new InterpolatingCPICapFloorEngine(cpiCFsurfUKh));
364
365 for (Size i = 0; i < common.cStrikesUK.size(); i++) {
366 Rate strike = common.cStrikesUK[i];
367 for (Size j = 0; j < common.cfMaturitiesUK.size(); j++) {
368 Period maturity = common.cfMaturitiesUK[j];
369 Date maturityDate = startDate + maturity;
370
371 CPICapFloor aCap(Option::Call, nominal, startDate, baseCPI, maturityDate, fixCalendar, fixConvention,
372 payCalendar, payConvention, strike, common.hii.currentLink(), common.observationLag,
373 observationInterpolation);
374
375 aCap.setPricingEngine(engine);
376
377 Real cached = (*common.cPriceUK)[i][j] * 10000;
378 Real npv1 = aCap.NPV() * 10000;
379 // QL_REQUIRE(fabs(cached - npv1) < 1e-10,
380 // "InterpolatingCPICapFloorEngine does not reproduce cached price: " << cached << " vs " <<
381 // npv1);
382 BOOST_CHECK_SMALL(fabs(cached - npv1), 1e-10);
383
384 aCap.setPricingEngine(blackEngine);
385 Real npv2 = aCap.NPV() * 10000;
386 // QL_REQUIRE(fabs(cached - npv2) < 0.1,
387 // "CPIBlackCapFloorEngine does not reproduce cached price: " << cached << " vs " << npv2);
388 BOOST_CHECK_SMALL(fabs(cached - npv2), 1e-5); // depends on the default solver tolerance 1e-8
389
390 BOOST_TEST_MESSAGE("Cap " << fixed << std::setprecision(2) << strike << " " << setw(3) << maturity
391 << ": cached " << setw(7) << cached << " QL " << setw(8) << npv1 << " QLE "
392 << setw(8) << npv2 << " diff " << setw(8) << npv2 - npv1);
393 }
394 }
395
396 for (Size i = 0; i < common.fStrikesUK.size(); i++) {
397 Rate strike = common.fStrikesUK[i];
398 // fStrikesUK.push_back(fStrike[i]);
399 for (Size j = 0; j < common.cfMaturitiesUK.size(); j++) {
400 Period maturity = common.cfMaturitiesUK[j];
401 Date maturityDate = startDate + maturity;
402
403 CPICapFloor aFloor(Option::Put, nominal, startDate, baseCPI, maturityDate, fixCalendar, fixConvention,
404 payCalendar, payConvention, strike, common.hii.currentLink(), common.observationLag,
405 observationInterpolation);
406
407 aFloor.setPricingEngine(engine);
408
409 Real cached = (*common.fPriceUK)[i][j] * 10000;
410 Real npv1 = aFloor.NPV() * 10000;
411 // QL_REQUIRE(fabs(cached - npv1) < 1e-10,
412 // "InterpolatingCPICapFloorEngine does not reproduce cached price: " << cached << " vs " <<
413 // npv1);
414 BOOST_CHECK_SMALL(fabs(cached - npv1), 1e-10);
415
416 aFloor.setPricingEngine(blackEngine);
417 Real npv2 = aFloor.NPV() * 10000;
418 // QL_REQUIRE(fabs(cached - npv2) < 0.1,
419 // "CPIBlackCapFloorEngine does not reproduce cached price: " << cached << " vs " << npv2);
420 BOOST_CHECK_SMALL(fabs(cached - npv2), 1e-5); // depends on the default solver tolerance 1e-8
421
422 BOOST_TEST_MESSAGE("Floor " << fixed << std::setprecision(2) << strike << " " << setw(3) << maturity
423 << ": cached " << setw(7) << cached << " QL " << setw(8) << npv1 << " QLE "
424 << setw(8) << npv2 << " diff " << setw(8) << npv2 - npv1);
425 }
426 }
427}
428
429BOOST_AUTO_TEST_CASE(testPutCallParity) {
430
431 CommonVars common;
432
433 Handle<YieldTermStructure> nominalTS = common.nominalUK;
434
435 // this engine is used to imply the volatility that reproduces a quoted price
436 QuantLib::ext::shared_ptr<QuantExt::CPIBlackCapFloorEngine> blackEngine =
437 QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(nominalTS,
438 QuantLib::Handle<QuantLib::CPIVolatilitySurface>());
439
440 // wrap the pointer into a handle
441 Handle<CPICapFloorTermPriceSurface> cpiPriceSurfaceHandle(common.cpiCFsurfUK);
442
443 // initialize the vol surface, taking the price surface as an input and running the vol imply calculations
445 QuantLib::ext::shared_ptr<QuantExt::StrippedCPIVolatilitySurface<QuantLib::Bilinear> > cpiVolSurface =
446 QuantLib::ext::make_shared<QuantExt::StrippedCPIVolatilitySurface<QuantLib::Bilinear> >(type, cpiPriceSurfaceHandle,
447 common.ii, blackEngine);
448
449 // attach the implied vol surface to the engine
450 blackEngine->setVolatility(QuantLib::Handle<QuantLib::CPIVolatilitySurface>(cpiVolSurface));
451
452 Period mat[] = { 3 * Years, 4 * Years, 5 * Years, 6 * Years, 7 * Years, 8 * Years, 9 * Years,
453 10 * Years, 12 * Years, 15 * Years, 20 * Years, 25 * Years, 30 * Years };
454 Size nMat = 13;
455
456 Real strike[] = { 0.0, 0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05 };
457 Size nStrike = 11;
458
459 Real nominal = 1.0;
460
461 Date startDate = Settings::instance().evaluationDate();
462 Calendar fixCalendar = UnitedKingdom(), payCalendar = UnitedKingdom();
463 BusinessDayConvention fixConvention(Unadjusted), payConvention(ModifiedFollowing);
464 Real baseCPI = common.hii->fixing(fixCalendar.adjust(startDate - common.observationLag, fixConvention));
465 CPI::InterpolationType observationInterpolation = CPI::AsIndex;
466
467 Date effectiveStart = startDate - common.observationLag;
468 std::pair<Date, Date> ips = inflationPeriod(effectiveStart, common.ii->frequency());
469 effectiveStart = ips.first;
470
471 for (Size i = 0; i < nStrike; ++i) {
472
473 for (Size j = 0; j < nMat; ++j) {
474 Date maturityDate = startDate + mat[j];
475
476 CPICapFloor aCap(Option::Call, nominal, startDate, baseCPI, maturityDate, fixCalendar, fixConvention,
477 payCalendar, payConvention, strike[i], common.hii.currentLink(), common.observationLag,
478 observationInterpolation);
479 aCap.setPricingEngine(blackEngine);
480
481 CPICapFloor aFloor(Option::Put, nominal, startDate, baseCPI, maturityDate, fixCalendar, fixConvention,
482 payCalendar, payConvention, strike[i], common.hii.currentLink(), common.observationLag,
483 observationInterpolation);
484 aFloor.setPricingEngine(blackEngine);
485
486 Real capPrice = aCap.NPV() * 10000;
487 Real floorPrice = aFloor.NPV() * 10000;
488
489 // build CPI leg price manually
490 Date fixingDate = maturityDate - common.observationLag;
491 Date effectiveMaturity = fixingDate;
492 Real timeFromStart =
493 common.ii->zeroInflationTermStructure()->dayCounter().yearFraction(effectiveStart, effectiveMaturity);
494 Real K = pow(1.0 + strike[i], timeFromStart);
495 Real F = common.ii->fixing(effectiveMaturity) / baseCPI;
496 DiscountFactor disc = nominalTS->discount(maturityDate);
497 Real cpilegPrice = disc * (F - K) * 10000;
498
499 Real parity = capPrice - floorPrice - cpilegPrice;
500 BOOST_TEST_MESSAGE(std::setprecision(3) << fixed << "strike=" << strike[i] << " mat=" << mat[j] << " cap="
501 << capPrice << " floor=" << floorPrice << " cpileg=" << cpilegPrice
502 << " parity=cap-floor-cpileg=" << parity);
503
504 // FIXME: investigate why the parity check yields ~bp upfront error in some cases
505 BOOST_CHECK_SMALL(fabs(parity), 1.1); // bp upfront parity error
506 }
507 }
508}
509
510BOOST_AUTO_TEST_CASE(testInterpolatedVolatilitySurface) {
511 CommonVars common;
512
513 Handle<YieldTermStructure> nominalTS = common.nominalUK;
514
515 // this engine is used to imply the volatility that reproduces a quoted price
516 QuantLib::ext::shared_ptr<QuantExt::CPIBlackCapFloorEngine> blackEngine =
517 QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(nominalTS,
518 QuantLib::Handle<QuantLib::CPIVolatilitySurface>());
519
520 // wrap the pointer into a handle
521 Handle<CPICapFloorTermPriceSurface> cpiPriceSurfaceHandle(common.cpiCFsurfUK);
522
523 // initialize the vol surface, taking the price surface as an input and running the vol imply calculations
525 QuantLib::ext::shared_ptr<QuantExt::StrippedCPIVolatilitySurface<QuantLib::Bilinear> > cpiVolSurface =
526 QuantLib::ext::make_shared<QuantExt::StrippedCPIVolatilitySurface<QuantLib::Bilinear> >(type, cpiPriceSurfaceHandle,
527 common.ii, blackEngine);
528
529 std::vector<Period> optionTenors = cpiVolSurface->maturities();
530 std::vector<Real> strikes = cpiVolSurface->strikes();
531 vector<vector<Handle<Quote> > > quotes(optionTenors.size(),
532 vector<Handle<Quote> >(strikes.size(), Handle<Quote>()));
533 for (Size i = 0; i < optionTenors.size(); ++i) {
534 for (Size j = 0; j < strikes.size(); ++j) {
535 Real vol = cpiVolSurface->volatility(optionTenors[i], strikes[j]);
536 QuantLib::ext::shared_ptr<SimpleQuote> q(new SimpleQuote(vol));
537 quotes[i][j] = Handle<Quote>(q);
538 }
539 }
540 QuantLib::ext::shared_ptr<QuantExt::InterpolatedCPIVolatilitySurface<Bilinear> > interpolatedCpiVol =
541 QuantLib::ext::make_shared<QuantExt::InterpolatedCPIVolatilitySurface<Bilinear> >(
542 optionTenors, strikes, quotes, common.hii.currentLink(), cpiVolSurface->settlementDays(),
543 cpiVolSurface->calendar(), cpiVolSurface->businessDayConvention(), cpiVolSurface->dayCounter(),
544 cpiVolSurface->observationLag());
545
546 for (Size i = 0; i < optionTenors.size(); ++i) {
547 Date d = cpiVolSurface->optionDateFromTenor(optionTenors[i]);
548 for (Size j = 0; j < strikes.size(); ++j) {
549 Real vol1 = cpiVolSurface->volatility(d, strikes[j]);
550 Real vol2 = interpolatedCpiVol->volatility(d, strikes[j]);
551 BOOST_CHECK_SMALL(fabs(vol1 - vol2), 1.0e-10);
552 }
553 }
554}
555
556BOOST_AUTO_TEST_CASE(testSimpleCapFloor) {
557 CommonVars common;
558
559 Real rate = 0.03;
560 Real inflationRate = 0.02;
561 Real inflationBlackVol = 0.05;
562 BusinessDayConvention bdc = Unadjusted;
563 DayCounter dc = ActualActual(ActualActual::ISDA);
564 Period observationLag = 3 * Months; // EUHICPXT Caps/Swaps
565 Handle<YieldTermStructure> discountCurve(QuantLib::ext::make_shared<FlatForward>(common.evaluationDate, rate, dc));
566 RelinkableHandle<ZeroInflationTermStructure> inflationCurve;
567 Handle<ZeroInflationIndex> index(QuantLib::ext::make_shared<EUHICPXT>(inflationCurve));
568 // Make sure we use the correct index publication frequency and interpolation, consistent with the index we want to
569 // project. We therefore create the index first, then the term structure, then relink the curve. Otherwise time
570 // calculations will be inconsistent, and ATM strikes do not generate equal put/call or cap/floor prices.
571 QuantLib::ext::shared_ptr<ZeroInflationTermStructure> inflationCurvePtr =
572 QuantLib::ext::make_shared<FlatZeroInflationTermStructure>(common.evaluationDate, index->fixingCalendar(), dc,
573 inflationRate, observationLag, index->frequency(),
574 false, discountCurve);
575 inflationCurve.linkTo(inflationCurvePtr);
576 // Make sure we use the same observation lag as in the inflation curve, and same index publication frequency and
577 // interpolation. The vol surface observation lag is used in the engine for lag difference calculations compared to
578 // the instrument's lag.
579 Handle<CPIVolatilitySurface> inflationVol(QuantLib::ext::make_shared<ConstantCPIVolatility>(
580 inflationBlackVol, 0, inflationCurve->calendar(), bdc, dc, inflationCurve->observationLag(),
581 inflationCurve->frequency(), false));
582
583 QuantLib::ext::shared_ptr<PricingEngine> engine =
584 QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(discountCurve, inflationVol);
585
586 Real nominal = 10000.0;
587 Date start = common.evaluationDate;
588 Date end = start + 10 * Years;
589 Real baseCPI = 100.0;
590 Calendar fixCalendar = index->fixingCalendar();
591 Calendar payCalendar = index->fixingCalendar();
592
593 Rate capStrike = 0.03;
594 CPICapFloor atmCap(Option::Call, nominal, start, baseCPI, end, fixCalendar, bdc, payCalendar, bdc, inflationRate,
595 index.currentLink(), observationLag);
596 atmCap.setPricingEngine(engine);
597 CPICapFloor cap(Option::Call, nominal, start, baseCPI, end, fixCalendar, bdc, payCalendar, bdc, capStrike,
598 index.currentLink(), observationLag);
599 cap.setPricingEngine(engine);
600
601 Rate floorStrike = 0.01;
602 CPICapFloor atmFloor(Option::Put, nominal, start, baseCPI, end, fixCalendar, bdc, fixCalendar, bdc, inflationRate,
603 index.currentLink(), observationLag);
604 atmFloor.setPricingEngine(engine);
605 CPICapFloor floor(Option::Put, nominal, start, baseCPI, end, fixCalendar, bdc, fixCalendar, bdc, floorStrike,
606 index.currentLink(), observationLag);
607 floor.setPricingEngine(engine);
608
609 DayCounter cpiDayCounter = QuantExt::YearCounter();
610
611 // use base CPI as base fixing
612 index->addFixing(inflationCurve->baseDate(), baseCPI);
613
614 // Do cap and floor match at the money?
615 BOOST_CHECK_CLOSE(atmCap.NPV(), atmFloor.NPV(), 1e-8);
616
617 // Pedestrian CPI Cap/Floor valuation assuming baseCPI = base fixing
618 Real t = 10.0; // time to maturity is 10 (base to effective maturity)
619 Real stdDev = inflationBlackVol * sqrt(t);
620 Real forward = pow(1.0 + inflationRate, t);
621 Real floorStrikePrice = pow(1.0 + floorStrike, t);
622 Real capStrikePrice = pow(1.0 + capStrike, t);
623 Real discount = exp(-rate * t);
624 Real expectedCapNPV = nominal * blackFormula(Option::Call, capStrikePrice, forward, stdDev, discount);
625 Real expectedFloorNPV = nominal * blackFormula(Option::Put, floorStrikePrice, forward, stdDev, discount);
626
627 BOOST_TEST_MESSAGE("CPI Cap NPV " << cap.NPV() << " " << expectedCapNPV);
628 BOOST_TEST_MESSAGE("CPI Floor NPV " << floor.NPV() << " " << expectedFloorNPV);
629
630 BOOST_CHECK_CLOSE(cap.NPV(), expectedCapNPV, 0.01); // relative difference of 0.01%
631 BOOST_CHECK_CLOSE(floor.NPV(), expectedFloorNPV, 0.01); // relative difference of 0.01%
632}
633
634BOOST_AUTO_TEST_SUITE_END()
635
636BOOST_AUTO_TEST_SUITE_END()
637
638} // namespace
BOOST_AUTO_TEST_CASE(testForwardEvaluation)
Definition: ad.cpp:39
Year counter for when we want a whole number year fraction.
Definition: yearcounter.hpp:37
CPI cap/floor engine using the Black pricing formula and interpreting the volatility data as lognorma...
zero inflation volatility structure interpolated on a expiry/strike matrix of quotes
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Definition: inflation.cpp:183
RandomVariable sqrt(RandomVariable x)
CompiledFormula exp(CompiledFormula x)
CompiledFormula pow(CompiledFormula x, const CompiledFormula &y)
zero inflation volatility structure implied from a cpi cap/floor price surface
vector< Real > strikes
Fixture that can be used at top level.
helper macros and methods for tests
day counter that returns the nearest integer yearfraction