19#include <boost/test/unit_test.hpp>
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>
38using namespace boost::unit_test_framework;
48 DayCounter dayCounter;
49 Calendar fixingCalendar;
50 BusinessDayConvention bdc;
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};
56 QuantLib::ext::shared_ptr<SimpleQuote> flatZero = QuantLib::ext::make_shared<SimpleQuote>(0.01);
58 Handle<YieldTermStructure> discountTS;
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}};
76 std::vector<Rate>
strikes{0.02, 0.04, 0.06, 0.08};
77 std::vector<Period> tenors{1 * Years, 2 * Years, 3 * Years};
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))}};
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;
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;
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()) {
113 DayCounter dc = cd.dayCounter;
115 BusinessDayConvention bdc = ModifiedFollowing;
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);
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,
134 curve->setSeasonality(seasonality);
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);
146 auto curve = buildZeroInflationCurve(cd, useLastKnownFixing, curveBuildIndex, isInterpolated,
nullptr, startDate);
148 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
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,
159 surface->enableExtrapolation();
163CPICapFloorPriceData pricesFromVolQuotes(CommonData& cd,
const QuantLib::ext::shared_ptr<ZeroInflationIndex>& index,
164 bool interpolated =
false,
const Date& startDate = Date()) {
166 CPICapFloorPriceData priceData;
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);
174 Date capFloorStartDate = startDate == Date() ? cd.today : startDate;
175 Date capFloorBaseDate = capFloorStartDate - cd.obsLag;
177 capFloorBaseDate = inflationPeriod(capFloorBaseDate, index->frequency()).first;
180 Date lastKnownFixing(1, July, 2022);
182 Handle<ZeroInflationIndex> hindex(index);
184 double baseCPI = index->fixing(capFloorBaseDate);
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);
197 QuantLib::BlackCalculator callPricer(Option::Call, strike, atmf,
sqrt(volTimeFrom) * vol, discountFactor);
198 QuantLib::BlackCalculator putPricer(Option::Put, strike, atmf,
sqrt(volTimeFrom) * vol, discountFactor);
200 priceData.cPrices[i][j] = callPricer.value();
201 priceData.fPrices[i][j] = putPricer.value();
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) {
213 QuantLib::ext::shared_ptr<QuantExt::CPIBlackCapFloorEngine> engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(
214 cd.discountTS, QuantLib::Handle<QuantLib::CPIVolatilitySurface>(), useLastKnownFixing);
216 QuantLib::ext::shared_ptr<QuantExt::CPIPriceVolatilitySurface<QuantLib::Linear, QuantLib::Linear>> cpiCapFloorVolSurface;
217 cpiCapFloorVolSurface = QuantLib::ext::make_shared<QuantExt::CPIPriceVolatilitySurface<QuantLib::Linear, QuantLib::Linear>>(
219 cd.discountTS, priceData.cStrikes, priceData.fStrikes, priceData.tenors, priceData.cPrices, priceData.fPrices,
220 engine, startDate, ignoreMissingQuotes);
222 cpiCapFloorVolSurface->enableExtrapolation();
223 return cpiCapFloorVolSurface;
230BOOST_AUTO_TEST_SUITE(InflationCPIVolatilityTest)
236 Date lastKnownFixing(1, Jul, 2022);
237 Date capFloorBaseDate(1, Jun, 2022);
239 auto index = buildIndexWithForwardTermStructure(cd);
241 Handle<ZeroInflationIndex> hindex(index);
243 BOOST_CHECK_EQUAL(index->zeroInflationTermStructure()->baseDate(), lastKnownFixing);
245 auto volSurface = buildVolSurface(cd, index);
248 BOOST_CHECK_EQUAL(volSurface->baseDate(), capFloorBaseDate);
250 double baseCPI = index->fixing(volSurface->baseDate());
252 BOOST_CHECK_CLOSE(baseCPI, cd.cpiFixings[capFloorBaseDate], cd.tolerance);
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);
269 auto index = buildIndexWithForwardTermStructure(cd);
271 auto volSurface = buildVolSurface(cd, index);
273 double baseCPI = index->fixing(volSurface->baseDate());
275 auto priceData = pricesFromVolQuotes(cd, index,
false, Date());
277 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(
278 cd.discountTS, Handle<CPIVolatilitySurface>(volSurface),
true);
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);
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);
307 Date lastKnownFixing(1, Jul, 2022);
308 Date capFloorBaseDate(1, Jun, 2022);
310 auto index = buildIndexWithForwardTermStructure(cd);
312 auto volSurface = buildVolSurface(cd, index);
314 double baseCPI = index->fixing(volSurface->baseDate());
316 QuantLib::ext::shared_ptr<PricingEngine> engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(
317 cd.discountTS, Handle<CPIVolatilitySurface>(volSurface),
true);
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);
326 double K = pow(1 + seasonedStrike, cd.dayCounter.yearFraction(seasonedBaseFixingDate, seasonedFixingDate));
327 double atm = index->fixing(seasonedFixingDate) / seasonedBaseCPI;
329 double adjustedStrike = std::pow(K * seasonedBaseCPI / baseCPI,
330 1.0 / cd.dayCounter.yearFraction(volSurface->baseDate(), seasonedFixingDate)) -
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);
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);
342 cap.setPricingEngine(engine);
344 BOOST_CHECK_CLOSE(cap.NPV(), callPricer.value(), cd.tolerance);
350 auto index = buildIndexWithForwardTermStructure(cd);
352 auto priceData = pricesFromVolQuotes(cd, index,
false, Date());
354 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index,
true, Date(),
false);
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);
371 auto index = buildIndexWithForwardTermStructure(cd);
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>();
380 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index,
true, Date(),
true);
382 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes[strikeIdx]);
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]);
390 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
399 auto index = buildIndexWithForwardTermStructure(cd);
402 for (Size tenorIdx = 1; tenorIdx < cd.tenors.size() - 1; tenorIdx++) {
404 auto priceData = pricesFromVolQuotes(cd, index,
false, Date());
406 priceData.cPrices[0][tenorIdx] = Null<Real>();
407 priceData.fPrices[0][tenorIdx] = Null<Real>();
409 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index,
true, Date(),
true);
411 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes.front());
413 double expectedVol = cd.vols[tenorIdx][1]->value();
415 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
419 for (Size tenorIdx = 1; tenorIdx < cd.tenors.size() - 1; tenorIdx++) {
421 auto priceData = pricesFromVolQuotes(cd, index,
false, Date());
423 priceData.cPrices[cd.strikes.size()-1][tenorIdx] = Null<Real>();
424 priceData.fPrices[cd.strikes.size() - 1][tenorIdx] = Null<Real>();
426 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index,
true, Date(),
true);
428 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes.back());
430 double expectedVol = cd.vols[tenorIdx][cd.strikes.size() - 2]->value();
432 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
436 for (Size tenorIdx = 1; tenorIdx < cd.tenors.size() - 1; tenorIdx++) {
438 auto priceData = pricesFromVolQuotes(cd, index,
false, Date());
440 priceData.cPrices[0][tenorIdx] = Null<Real>();
441 priceData.fPrices[0][tenorIdx] = Null<Real>();
443 priceData.cPrices[1][tenorIdx] = Null<Real>();
444 priceData.fPrices[1][tenorIdx] = Null<Real>();
446 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index,
true, Date(),
true);
448 double vol = volSurface->volatility(cd.tenors[tenorIdx], cd.strikes.front());
450 double expectedVol = cd.vols[tenorIdx][2]->value();
452 BOOST_CHECK_CLOSE(vol, expectedVol, cd.tolerance);
460 auto index = buildIndexWithForwardTermStructure(cd);
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>();
469 priceData.cPrices[1][0] = priceDataAllQuotes.cPrices[1][0];
470 priceData.fPrices[1][0] = priceDataAllQuotes.fPrices[1][0];
472 auto volSurface = buildVolSurfaceFromPrices(cd, priceData, index,
true, Date(),
true);
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);
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>();
483 priceData.cPrices[1][tenorIdx] = priceDataAllQuotes.cPrices[1][tenorIdx];
484 priceData.fPrices[1][tenorIdx] = priceDataAllQuotes.fPrices[1][tenorIdx];
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);
500 Date today(15, July, 2022);
502 cd.obsLag = 3 * Months;
503 Settings::instance().evaluationDate() = today;
504 std::map<Date, double> fixings{{Date(1, Mar, 2022), 100.0}};
507 Date startDate(15, Jun, 2022);
508 Date lastKnownFixing(1, Jan, 2022);
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);
515 auto curve = buildZeroInflationCurve(cd,
true, curveBuildIndex,
false,
nullptr, startDate);
517 auto index = curveBuildIndex->clone(Handle<ZeroInflationTermStructure>(curve));
519 BOOST_CHECK_EQUAL(curve->baseDate(), lastKnownFixing);
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);
526 auto volSurface = buildVolSurface(cd, index, startDate);
528 BOOST_CHECK_EQUAL(volSurface->baseDate(), Date(1, Jan, 2022));
530 double baseCPI = index->fixing(volSurface->baseDate());
532 BOOST_CHECK_CLOSE(baseCPI, 100.0, cd.tolerance);
534 Matrix cPrices(cd.strikes.size(), cd.tenors.size(), 0.0);
535 Matrix fPrices(cd.strikes.size(), cd.tenors.size(), 0.0);
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];
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);
553 cPrices[i][j] = callPricer.value();
554 fPrices[i][j] = putPricer.value();
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;
565 auto priceSurface = buildVolSurfaceFromPrices(cd, priceData, index,
true, startDate);
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);
579BOOST_AUTO_TEST_SUITE_END()
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)
RandomVariable sqrt(RandomVariable x)
Piecewise interpolated zero inflation term structure.
Fixture that can be used at top level.