Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
inflationvol.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2022 Quaternion Risk Management Ltd
3
4 This file is part of ORE, a free-software/open-source library
5 for transparent pricing and risk analysis - http://opensourcerisk.org
6
7 ORE is free software: you can redistribute it and/or modify it
8 under the terms of the Modified BSD License. You should have received a
9 copy of the license along with this program.
10 The license is also available online at <http://opensourcerisk.org>
11
12 This program is distributed on the basis that it will form a useful
13 contribution to risk analytics and model standardisation, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
16*/
17
18#include "toplevelfixture.hpp"
19#include <boost/test/unit_test.hpp>
20
21#include <ql/indexes/inflation/aucpi.hpp>
22#include <ql/indexes/inflation/euhicp.hpp>
23#include <ql/math/interpolations/bilinearinterpolation.hpp>
24#include <ql/math/matrix.hpp>
25#include <ql/pricingengines/blackcalculator.hpp>
26#include <ql/termstructures/inflation/inflationhelpers.hpp>
27#include <ql/termstructures/inflationtermstructure.hpp>
28#include <ql/termstructures/volatility/inflation/cpivolatilitystructure.hpp>
29#include <ql/termstructures/yield/flatforward.hpp>
30#include <ql/time/calendars/nullcalendar.hpp>
31#include <ql/time/daycounters/actual365fixed.hpp>
37
38using namespace boost::unit_test_framework;
39using namespace QuantLib;
40using namespace std;
41
42namespace {
43
44struct CommonData {
45
46 Date today;
47 Real tolerance;
48 DayCounter dayCounter;
49 Calendar fixingCalendar;
50 BusinessDayConvention bdc;
51 Period obsLag;
52
53 std::vector<Period> zeroCouponPillars{1 * Years, 2 * Years, 3 * Years, 5 * Years};
54 std::vector<Rate> zeroCouponQuotes{0.06, 0.04, 0.03, 0.02};
55
56 QuantLib::ext::shared_ptr<SimpleQuote> flatZero = QuantLib::ext::make_shared<SimpleQuote>(0.01);
57
58 Handle<YieldTermStructure> discountTS;
59
60 std::map<Date, Rate> cpiFixings{{Date(1, May, 2021), 97.8744653499849},
61 {Date(1, Jun, 2021), 98.0392156862745},
62 {Date(1, Jul, 2021), 98.1989155376188},
63 {Date(1, Aug, 2021), 98.3642120151039},
64 {Date(1, Sep, 2021), 98.5297867331921},
65 {Date(1, Oct, 2021), 98.6902856945937},
66 {Date(1, Nov, 2021), 98.8564092866721},
67 {Date(1, Dec, 2021), 99.0174402961208},
68 {Date(1, Jan, 2022), 99.1841145816863},
69 {Date(1, Feb, 2022), 99.3510694270946},
70 {Date(1, Mar, 2022), 99.5021088919576},
71 {Date(1, Apr, 2022), 99.6695990114986},
72 {Date(1, May, 2022), 99.8319546569845},
73 {Date(1, Jun, 2022), 100},
74 {Date(1, July, 2022), 104}};
75
76 std::vector<Rate> strikes{0.02, 0.04, 0.06, 0.08};
77 std::vector<Period> tenors{1 * Years, 2 * Years, 3 * Years};
78
79 vector<vector<Handle<Quote>>> vols{
80 {Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.3)), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.32)),
81 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.34)), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.36))},
82 {Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.35)), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.37)),
83 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.39)), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.41))},
84 {Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.40)), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.42)),
85 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.44)), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.46))}};
86
87 CommonData()
88 : today(15, Aug, 2022), tolerance(1e-6), dayCounter(Actual365Fixed()), fixingCalendar(NullCalendar()),
89 bdc(ModifiedFollowing), obsLag(2, Months),
90 discountTS(Handle<YieldTermStructure>(
91 QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), Handle<Quote>(flatZero), dayCounter))) {
92 Settings::instance().evaluationDate() = today;
93 };
94};
95
96struct CPICapFloorPriceData {
97 std::vector<Period> tenors;
98 std::vector<double> cStrikes;
99 std::vector<double> fStrikes;
100 QuantLib::Matrix cPrices;
101 QuantLib::Matrix fPrices;
102};
103
104QuantLib::ext::shared_ptr<ZeroInflationCurve>
105buildZeroInflationCurve(CommonData& cd, bool useLastKnownFixing, const QuantLib::ext::shared_ptr<ZeroInflationIndex>& index,
106 const bool isInterpolated, const QuantLib::ext::shared_ptr<Seasonality>& seasonality = nullptr,
107 const QuantLib::Date& startDate = Date()) {
108 Date today = Settings::instance().evaluationDate();
109 Date start = startDate;
110 if (startDate == Date()) {
111 start = today;
112 }
113 DayCounter dc = cd.dayCounter;
114
115 BusinessDayConvention bdc = ModifiedFollowing;
116
117 std::vector<QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper>> helpers;
118 for (size_t i = 0; i < cd.zeroCouponQuotes.size(); ++i) {
119 Date maturity = start + cd.zeroCouponPillars[i];
120 Rate quote = cd.zeroCouponQuotes[i];
121 QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper> instrument =
122 QuantLib::ext::make_shared<ZeroCouponInflationSwapHelper>(
123 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(quote)), cd.obsLag, maturity, cd.fixingCalendar, bdc, dc,
124 index, isInterpolated ? CPI::Linear : CPI::Flat, Handle<YieldTermStructure>(cd.discountTS), start);
125 helpers.push_back(instrument);
126 }
127 Rate baseRate = QuantExt::ZeroInflation::guessCurveBaseRate(useLastKnownFixing, start, today, cd.zeroCouponPillars[0],
128 cd.dayCounter, cd.obsLag, cd.zeroCouponQuotes[0],
129 cd.obsLag, cd.dayCounter, index, isInterpolated);
130 QuantLib::ext::shared_ptr<ZeroInflationCurve> curve = QuantLib::ext::make_shared<QuantExt::PiecewiseZeroInflationCurve<Linear>>(
131 today, cd.fixingCalendar, dc, cd.obsLag, index->frequency(), baseRate, helpers, 1e-10, index,
132 useLastKnownFixing);
133 if (seasonality) {
134 curve->setSeasonality(seasonality);
135 }
136 return curve;
137}
138
139QuantLib::ext::shared_ptr<ZeroInflationIndex> buildIndexWithForwardTermStructure(CommonData& cd, bool useLastKnownFixing = true,
140 bool isInterpolated = false,
141 const Date& startDate = Date()) {
142 QuantLib::ext::shared_ptr<ZeroInflationIndex> curveBuildIndex = QuantLib::ext::make_shared<QuantLib::EUHICPXT>(isInterpolated);
143 for (const auto& [date, fixing] : cd.cpiFixings) {
144 curveBuildIndex->addFixing(date, fixing);
145 }
146 auto curve = buildZeroInflationCurve(cd, useLastKnownFixing, curveBuildIndex, isInterpolated, nullptr, startDate);
147
148 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
149
150 return index;
151}
152
153QuantLib::ext::shared_ptr<CPIVolatilitySurface> buildVolSurface(CommonData& cd,
154 const QuantLib::ext::shared_ptr<ZeroInflationIndex>& index,
155 const QuantLib::Date& startDate = QuantLib::Date()) {
156 auto surface = QuantLib::ext::make_shared<QuantExt::InterpolatedCPIVolatilitySurface<Bilinear>>(
157 cd.tenors, cd.strikes, cd.vols, index, 0, cd.fixingCalendar, ModifiedFollowing, cd.dayCounter, cd.obsLag,
158 startDate);
159 surface->enableExtrapolation();
160 return surface;
161}
162
163CPICapFloorPriceData pricesFromVolQuotes(CommonData& cd, const QuantLib::ext::shared_ptr<ZeroInflationIndex>& index,
164 bool interpolated = false, const Date& startDate = Date()) {
165
166 CPICapFloorPriceData priceData;
167
168 priceData.tenors = cd.tenors;
169 priceData.fStrikes = cd.strikes;
170 priceData.cStrikes = cd.strikes;
171 priceData.cPrices = Matrix(cd.strikes.size(), cd.tenors.size(), 0.0);
172 priceData.fPrices = Matrix(cd.strikes.size(), cd.tenors.size(), 0.0);
173
174 Date capFloorStartDate = startDate == Date() ? cd.today : startDate;
175 Date capFloorBaseDate = capFloorStartDate - cd.obsLag;
176 if (!interpolated) {
177 capFloorBaseDate = inflationPeriod(capFloorBaseDate, index->frequency()).first;
178 }
179 // first day of the month of today - 2M
180 Date lastKnownFixing(1, July, 2022);
181
182 Handle<ZeroInflationIndex> hindex(index);
183
184 double baseCPI = index->fixing(capFloorBaseDate);
185
186 for (size_t i = 0; i < cd.strikes.size(); ++i) {
187 for (size_t j = 0; j < cd.tenors.size(); ++j) {
188 double vol = cd.vols[j][i]->value();
189 Date optionFixingDate = capFloorBaseDate + cd.tenors[j];
190 Date optionPaymentDate = cd.today + cd.tenors[j];
191 double ttm = cd.dayCounter.yearFraction(capFloorBaseDate, optionFixingDate);
192 double atmf = index->fixing(optionFixingDate) / baseCPI;
193 double strike = std::pow(1 + cd.strikes[i], ttm);
194 double discountFactor = cd.discountTS->discount(optionPaymentDate);
195 double volTimeFrom = cd.dayCounter.yearFraction(lastKnownFixing, optionFixingDate);
196
197 QuantLib::BlackCalculator callPricer(Option::Call, strike, atmf, sqrt(volTimeFrom) * vol, discountFactor);
198 QuantLib::BlackCalculator putPricer(Option::Put, strike, atmf, sqrt(volTimeFrom) * vol, discountFactor);
199
200 priceData.cPrices[i][j] = callPricer.value();
201 priceData.fPrices[i][j] = putPricer.value();
202 }
203 }
204 return priceData;
205}
206
207QuantLib::ext::shared_ptr<CPIVolatilitySurface> buildVolSurfaceFromPrices(CommonData& cd, CPICapFloorPriceData& priceData,
208 const QuantLib::ext::shared_ptr<ZeroInflationIndex>& index,
209 const bool useLastKnownFixing,
210 const Date& startDate = Date(),
211 bool ignoreMissingQuotes = false) {
212
213 QuantLib::ext::shared_ptr<QuantExt::CPIBlackCapFloorEngine> engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(
214 cd.discountTS, QuantLib::Handle<QuantLib::CPIVolatilitySurface>(), useLastKnownFixing);
215
216 QuantLib::ext::shared_ptr<QuantExt::CPIPriceVolatilitySurface<QuantLib::Linear, QuantLib::Linear>> cpiCapFloorVolSurface;
217 cpiCapFloorVolSurface = QuantLib::ext::make_shared<QuantExt::CPIPriceVolatilitySurface<QuantLib::Linear, QuantLib::Linear>>(
218 QuantExt::PriceQuotePreference::CapFloor, cd.obsLag, cd.fixingCalendar, cd.bdc, cd.dayCounter, index,
219 cd.discountTS, priceData.cStrikes, priceData.fStrikes, priceData.tenors, priceData.cPrices, priceData.fPrices,
220 engine, startDate, ignoreMissingQuotes);
221
222 cpiCapFloorVolSurface->enableExtrapolation();
223 return cpiCapFloorVolSurface;
224}
225
226} // namespace
227
228BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
229
230BOOST_AUTO_TEST_SUITE(InflationCPIVolatilityTest)
231
232BOOST_AUTO_TEST_CASE(testCPIVolatilitySurface) {
233 // Test case when the ZCIIS and Cap/Floors start today with using todays fixing
234 CommonData cd;
235
236 Date lastKnownFixing(1, Jul, 2022);
237 Date capFloorBaseDate(1, Jun, 2022); // first day of the month of today - 2M
238
239 auto index = buildIndexWithForwardTermStructure(cd);
240
241 Handle<ZeroInflationIndex> hindex(index);
242
243 BOOST_CHECK_EQUAL(index->zeroInflationTermStructure()->baseDate(), lastKnownFixing);
244
245 auto volSurface = buildVolSurface(cd, index);
246
247 // Expect the base fixing date of the cap/floor today - 2M
248 BOOST_CHECK_EQUAL(volSurface->baseDate(), capFloorBaseDate);
249
250 double baseCPI = index->fixing(volSurface->baseDate());
251
252 BOOST_CHECK_CLOSE(baseCPI, cd.cpiFixings[capFloorBaseDate], cd.tolerance);
253
254 for (size_t i = 0; i < cd.tenors.size(); ++i) {
255 auto fixingDate = volSurface->baseDate() + cd.tenors[i];
256 for (size_t j = 0; j < cd.strikes.size(); ++j) {
257 auto expectedVol = cd.vols[i][j]->value();
258 auto volByTenor = volSurface->volatility(cd.tenors[i], cd.strikes[j]);
259 auto volByFixingDate = volSurface->volatility(fixingDate, cd.strikes[j], 0 * Days);
260 BOOST_CHECK_CLOSE(volByTenor, expectedVol, cd.tolerance);
261 BOOST_CHECK_CLOSE(volByFixingDate, expectedVol, cd.tolerance);
262 }
263 }
264}
265
266BOOST_AUTO_TEST_CASE(testCPIBlackCapFloorEngine) {
267 // Test case when the ZCIIS and Cap/Floors start today with using todays fixing
268 CommonData cd;
269 auto index = buildIndexWithForwardTermStructure(cd);
270
271 auto volSurface = buildVolSurface(cd, index);
272
273 double baseCPI = index->fixing(volSurface->baseDate());
274
275 auto priceData = pricesFromVolQuotes(cd, index, false, Date());
276
277 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(
278 cd.discountTS, Handle<CPIVolatilitySurface>(volSurface), true);
279
280 for (size_t i = 0; i < priceData.cStrikes.size(); ++i) {
281 double strike = priceData.cStrikes[i];
282 for (size_t j = 0; j < priceData.tenors.size(); ++j) {
283 Period tenor = priceData.tenors[j];
284 CPICapFloor cap(Option::Call, 1.0, cd.today, baseCPI, cd.today + tenor, cd.fixingCalendar, cd.bdc,
285 cd.fixingCalendar, cd.bdc, strike, index, cd.obsLag, CPI::Flat);
286 cap.setPricingEngine(engine);
287 BOOST_CHECK_CLOSE(cap.NPV(), priceData.cPrices[i][j], cd.tolerance);
288 }
289 }
290
291 for (size_t i = 0; i < priceData.fStrikes.size(); ++i) {
292 double strike = priceData.fStrikes[i];
293 for (size_t j = 0; j < priceData.tenors.size(); ++j) {
294 Period tenor = priceData.tenors[j];
295 CPICapFloor put(Option::Put, 1.0, cd.today, baseCPI, cd.today + tenor, cd.fixingCalendar, cd.bdc,
296 cd.fixingCalendar, cd.bdc, strike, index, cd.obsLag, CPI::Flat);
297 put.setPricingEngine(engine);
298 BOOST_CHECK_CLOSE(put.NPV(), priceData.fPrices[i][j], cd.tolerance);
299 }
300 }
301}
302
303BOOST_AUTO_TEST_CASE(testCPIBlackCapFloorEngineSeasonedCapFloors) {
304 // Pricing seasoned cap/floors
305 CommonData cd;
306
307 Date lastKnownFixing(1, Jul, 2022);
308 Date capFloorBaseDate(1, Jun, 2022); // first day of the month of today - 2M
309
310 auto index = buildIndexWithForwardTermStructure(cd);
311
312 auto volSurface = buildVolSurface(cd, index);
313
314 double baseCPI = index->fixing(volSurface->baseDate());
315
316 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(
317 cd.discountTS, Handle<CPIVolatilitySurface>(volSurface), true);
318
319 Date seasonedStartDate(15, Aug, 2021);
320 Date seasonedMaturity(15, Aug, 2024);
321 Date seasonedBaseFixingDate(1, Jun, 2021);
322 Date seasonedFixingDate(1, Jun, 2024);
323 double seasonedStrike = 0.03;
324 double seasonedBaseCPI = index->fixing(seasonedBaseFixingDate);
325
326 double K = pow(1 + seasonedStrike, cd.dayCounter.yearFraction(seasonedBaseFixingDate, seasonedFixingDate));
327 double atm = index->fixing(seasonedFixingDate) / seasonedBaseCPI;
328
329 double adjustedStrike = std::pow(K * seasonedBaseCPI / baseCPI,
330 1.0 / cd.dayCounter.yearFraction(volSurface->baseDate(), seasonedFixingDate)) -
331 1.0;
332
333 double volTimeFrom = cd.dayCounter.yearFraction(lastKnownFixing, seasonedFixingDate);
334 double vol = volSurface->volatility(seasonedFixingDate, adjustedStrike, 0 * Days, false);
335 double discountFactor = cd.discountTS->discount(seasonedMaturity);
336 QuantLib::BlackCalculator callPricer(Option::Call, K, atm, sqrt(volTimeFrom) * vol, discountFactor);
337
338 QuantLib::CPICapFloor cap(Option::Call, 1.0, seasonedStartDate, Null<double>(), seasonedMaturity, cd.fixingCalendar,
339 cd.bdc, cd.fixingCalendar, cd.bdc, seasonedStrike, index,
340 cd.obsLag, CPI::Flat);
341
342 cap.setPricingEngine(engine);
343
344 BOOST_CHECK_CLOSE(cap.NPV(), callPricer.value(), cd.tolerance);
345}
346
347BOOST_AUTO_TEST_CASE(testCPIPriceVolSurface) {
348 // Test case when the ZCIIS and Cap/Floors start today with using todays fixing
349 CommonData cd;
350 auto index = buildIndexWithForwardTermStructure(cd);
351
352 auto priceData = pricesFromVolQuotes(cd, index, false, Date());
353
354 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, Date(), false);
355
356 for (size_t i = 0; i < cd.tenors.size(); ++i) {
357 auto fixingDate = volSurface->baseDate() + cd.tenors[i];
358 for (size_t j = 0; j < cd.strikes.size(); ++j) {
359 auto expectedVol = cd.vols[i][j]->value();
360 auto volByTenor = volSurface->volatility(cd.tenors[i], cd.strikes[j]);
361 auto volByFixingDate = volSurface->volatility(fixingDate, cd.strikes[j], 0 * Days);
362 BOOST_CHECK_CLOSE(volByTenor, expectedVol, cd.tolerance);
363 BOOST_CHECK_CLOSE(volByFixingDate, expectedVol, cd.tolerance);
364 }
365 }
366}
367
368BOOST_AUTO_TEST_CASE(testCPIPriceVolSurfaceMissingQuoteInterpolation) {
369 // Test case when the ZCIIS and Cap/Floors start today with using todays fixing
370 CommonData cd;
371 auto index = buildIndexWithForwardTermStructure(cd);
372
373 // Remove 1 Quote and check if its get interpolated
374 for (Size tenorIdx = 1; tenorIdx < cd.tenors.size() - 1; tenorIdx++) {
375 for (Size strikeIdx = 1; strikeIdx < cd.strikes.size() - 1; strikeIdx++) {
376 auto priceData = pricesFromVolQuotes(cd, index, false, Date());
377 priceData.cPrices[strikeIdx][tenorIdx] = Null<Real>();
378 priceData.fPrices[strikeIdx][tenorIdx] = Null<Real>();
379
380 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, Date(), true);
381
382 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes[strikeIdx]);
383
384 double expectedVol =
385 cd.vols[tenorIdx][strikeIdx - 1]->value() +
386 (cd.vols[tenorIdx][strikeIdx + 1]->value() - cd.vols[tenorIdx][strikeIdx - 1]->value()) *
387 (cd.strikes[strikeIdx] - cd.strikes[strikeIdx - 1]) /
388 (cd.strikes[strikeIdx + 1] - cd.strikes[strikeIdx - 1]);
389
390 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
391 }
392 }
393}
394
395
396BOOST_AUTO_TEST_CASE(testCPIPriceVolSurfaceMissingQuoteExtrapolation) {
397 // Test case when the ZCIIS and Cap/Floors start today with using todays fixing
398 CommonData cd;
399 auto index = buildIndexWithForwardTermStructure(cd);
400
401 // Remove 1 Quote and check if its get extrapolated
402 for (Size tenorIdx = 1; tenorIdx < cd.tenors.size() - 1; tenorIdx++) {
403
404 auto priceData = pricesFromVolQuotes(cd, index, false, Date());
405
406 priceData.cPrices[0][tenorIdx] = Null<Real>();
407 priceData.fPrices[0][tenorIdx] = Null<Real>();
408
409 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, Date(), true);
410
411 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes.front());
412
413 double expectedVol = cd.vols[tenorIdx][1]->value();
414
415 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
416 }
417
418 // Remove 1 Quote and check if its get extrapolated
419 for (Size tenorIdx = 1; tenorIdx < cd.tenors.size() - 1; tenorIdx++) {
420
421 auto priceData = pricesFromVolQuotes(cd, index, false, Date());
422
423 priceData.cPrices[cd.strikes.size()-1][tenorIdx] = Null<Real>();
424 priceData.fPrices[cd.strikes.size() - 1][tenorIdx] = Null<Real>();
425
426 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, Date(), true);
427
428 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes.back());
429
430 double expectedVol = cd.vols[tenorIdx][cd.strikes.size() - 2]->value();
431
432 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
433 }
434
435 // Remove 2 Quote and check if its get extrapolated
436 for (Size tenorIdx = 1; tenorIdx < cd.tenors.size() - 1; tenorIdx++) {
437
438 auto priceData = pricesFromVolQuotes(cd, index, false, Date());
439
440 priceData.cPrices[0][tenorIdx] = Null<Real>();
441 priceData.fPrices[0][tenorIdx] = Null<Real>();
442
443 priceData.cPrices[1][tenorIdx] = Null<Real>();
444 priceData.fPrices[1][tenorIdx] = Null<Real>();
445
446 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, Date(), true);
447
448 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes.front());
449
450 double expectedVol = cd.vols[tenorIdx][2]->value();
451
452 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
453 }
454}
455
456
457BOOST_AUTO_TEST_CASE(testCPIPriceVolSurfaceAllButOneQuoteMissing) {
458 // Test case when the ZCIIS and Cap/Floors start today with using todays fixing
459 CommonData cd;
460 auto index = buildIndexWithForwardTermStructure(cd);
461
462 // Remove all QUotes for a strike
463 auto priceDataAllQuotes = pricesFromVolQuotes(cd, index, false, Date());
464 auto priceData = pricesFromVolQuotes(cd, index, false, Date());
465 for (Size strikeIdx = 0; strikeIdx < cd.strikes.size(); strikeIdx++) {
466 priceData.cPrices[strikeIdx][0] = Null<Real>();
467 priceData.fPrices[strikeIdx][0] = Null<Real>();
468 }
469 priceData.cPrices[1][0] = priceDataAllQuotes.cPrices[1][0];
470 priceData.fPrices[1][0] = priceDataAllQuotes.fPrices[1][0];
471
472 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, Date(), true);
473
474 double vol = volSurface->volatility(cd.tenors[0], cd.strikes[0]);
475 double expectedVol = cd.vols[0][1]->value();
476 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
477
478 for (Size tenorIdx = 0; tenorIdx < cd.tenors.size(); tenorIdx++) {
479 for (Size strikeIdx = 0; strikeIdx < cd.strikes.size(); strikeIdx++) {
480 priceData.cPrices[strikeIdx][tenorIdx] = Null<Real>();
481 priceData.fPrices[strikeIdx][tenorIdx] = Null<Real>();
482 }
483 priceData.cPrices[1][tenorIdx] = priceDataAllQuotes.cPrices[1][tenorIdx];
484 priceData.fPrices[1][tenorIdx] = priceDataAllQuotes.fPrices[1][tenorIdx];
485 }
486
487 volSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, Date(), true);
488 for (Size tenorIdx = 0; tenorIdx < cd.tenors.size(); tenorIdx++) {
489 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes[0]);
490 double expectedVol = cd.vols[tenorIdx][1]->value();
491 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
492 }
493}
494
495
496BOOST_AUTO_TEST_CASE(testVolatiltiySurfaceWithStartDate) {
497 // Test case when the ZCIIS and Cap/Floors don't start today
498 // but depend on the publishing schedule of the fixings
499 CommonData cd;
500 Date today(15, July, 2022);
501 cd.today = today;
502 cd.obsLag = 3 * Months;
503 Settings::instance().evaluationDate() = today;
504 std::map<Date, double> fixings{{Date(1, Mar, 2022), 100.0}};
505 // the Q2 fixing not published yet, the zciis swaps and caps start on 15th Jun and
506 // reference on the Q1 fixing
507 Date startDate(15, Jun, 2022);
508 Date lastKnownFixing(1, Jan, 2022);
509
510 QuantLib::ext::shared_ptr<ZeroInflationIndex> curveBuildIndex = QuantLib::ext::make_shared<QuantLib::AUCPI>(Quarterly, true, false);
511 for (const auto& [date, fixing] : fixings) {
512 curveBuildIndex->addFixing(date, fixing);
513 }
514
515 auto curve = buildZeroInflationCurve(cd, true, curveBuildIndex, false, nullptr, startDate);
516
517 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
518
519 BOOST_CHECK_EQUAL(curve->baseDate(), lastKnownFixing);
520
521 BOOST_CHECK_EQUAL(curve->dates()[1], Date(1, Jan, 2023));
522 BOOST_CHECK_CLOSE(curve->data()[0], cd.zeroCouponQuotes[0], cd.tolerance);
523 BOOST_CHECK_CLOSE(curve->data()[1], cd.zeroCouponQuotes[0], cd.tolerance);
524 BOOST_CHECK_CLOSE(curve->data()[2], cd.zeroCouponQuotes[1], cd.tolerance);
525
526 auto volSurface = buildVolSurface(cd, index, startDate);
527
528 BOOST_CHECK_EQUAL(volSurface->baseDate(), Date(1, Jan, 2022));
529
530 double baseCPI = index->fixing(volSurface->baseDate());
531
532 BOOST_CHECK_CLOSE(baseCPI, 100.0, cd.tolerance);
533
534 Matrix cPrices(cd.strikes.size(), cd.tenors.size(), 0.0);
535 Matrix fPrices(cd.strikes.size(), cd.tenors.size(), 0.0);
536
537 for (size_t i = 0; i < cd.strikes.size(); ++i) {
538 for (size_t j = 0; j < cd.tenors.size(); ++j) {
539 double expectedVol = cd.vols[j][i]->value();
540 Date optionFixingDate = volSurface->baseDate() + cd.tenors[j];
541 Date optionPaymentDate = startDate + cd.tenors[j];
542
543 double vol = volSurface->volatility(optionFixingDate, cd.strikes[i], 0 * Days, false);
544 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
545 double ttm = cd.dayCounter.yearFraction(volSurface->baseDate(), optionFixingDate);
546 double atmf = index->fixing(optionFixingDate) / baseCPI;
547 double strike = std::pow(1 + cd.strikes[i], ttm);
548 double discountFactor = cd.discountTS->discount(optionPaymentDate);
549 double volTimeFrom = cd.dayCounter.yearFraction(lastKnownFixing, optionFixingDate);
550 QuantLib::BlackCalculator callPricer(Option::Call, strike, atmf, sqrt(volTimeFrom) * vol, discountFactor);
551 QuantLib::BlackCalculator putPricer(Option::Put, strike, atmf, sqrt(volTimeFrom) * vol, discountFactor);
552
553 cPrices[i][j] = callPricer.value();
554 fPrices[i][j] = putPricer.value();
555 }
556 }
557
558 CPICapFloorPriceData priceData;
559 priceData.tenors = cd.tenors;
560 priceData.cPrices = cPrices;
561 priceData.fPrices = fPrices;
562 priceData.cStrikes = cd.strikes;
563 priceData.fStrikes = cd.strikes;
564
565 auto priceSurface = buildVolSurfaceFromPrices(cd, priceData, index, true, startDate);
566
567 for (size_t i = 0; i < cd.strikes.size(); ++i) {
568 for (size_t j = 0; j < cd.tenors.size(); ++j) {
569 double expectedVol = cd.vols[j][i]->value();
570 Date optionFixingDate = priceSurface->baseDate() + cd.tenors[j];
571 double vol = priceSurface->volatility(optionFixingDate, cd.strikes[i], 0 * Days, false);
572 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
573 }
574 }
575
576
577
578}
579BOOST_AUTO_TEST_SUITE_END()
580
581BOOST_AUTO_TEST_SUITE_END()
CPI cap/floor engine using the Black pricing formula and interpreting the volatility data as lognorma...
some inflation related utilities.
BOOST_AUTO_TEST_CASE(testCPIVolatilitySurface)
zero inflation volatility structure interpolated on a expiry/strike matrix of quotes
QuantLib::Rate guessCurveBaseRate(const bool baseDateLastKnownFixing, const QuantLib::Date &swapStart, const QuantLib::Date &asof, const QuantLib::Period &swapTenor, const QuantLib::DayCounter &swapZCLegDayCounter, const QuantLib::Period &swapObsLag, const QuantLib::Rate zeroCouponRate, const QuantLib::Period &curveObsLag, const QuantLib::DayCounter &curveDayCounter, const QuantLib::ext::shared_ptr< QuantLib::ZeroInflationIndex > &index, const bool interpolated, const QuantLib::ext::shared_ptr< QuantLib::Seasonality > &seasonality)
Definition: inflation.cpp:191
RandomVariable sqrt(RandomVariable x)
Piecewise interpolated zero inflation term structure.
vector< Real > strikes
Fixture that can be used at top level.