| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2003 RiskMap srl |
| 5 | Copyright (C) 2004, 2005, 2006, 2007, 2008 StatPro Italia srl |
| 6 | Copyright (C) 2009 Chris Kenyon |
| 7 | |
| 8 | This file is part of QuantLib, a free-software/open-source library |
| 9 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 10 | |
| 11 | QuantLib is free software: you can redistribute it and/or modify it |
| 12 | under the terms of the QuantLib license. You should have received a |
| 13 | copy of the license along with this program; if not, please email |
| 14 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 15 | <http://quantlib.org/license.shtml>. |
| 16 | |
| 17 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 19 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 20 | */ |
| 21 | |
| 22 | #include "inflationcapfloor.hpp" |
| 23 | #include "utilities.hpp" |
| 24 | #include <ql/cashflows/cashflows.hpp> |
| 25 | #include <ql/cashflows/cashflowvectors.hpp> |
| 26 | #include <ql/cashflows/inflationcouponpricer.hpp> |
| 27 | #include <ql/cashflows/yoyinflationcoupon.hpp> |
| 28 | #include <ql/indexes/inflation/euhicp.hpp> |
| 29 | #include <ql/indexes/inflation/ukrpi.hpp> |
| 30 | #include <ql/instruments/inflationcapfloor.hpp> |
| 31 | #include <ql/instruments/vanillaswap.hpp> |
| 32 | #include <ql/math/matrix.hpp> |
| 33 | #include <ql/models/marketmodels/correlations/expcorrelations.hpp> |
| 34 | #include <ql/models/marketmodels/models/flatvol.hpp> |
| 35 | #include <ql/pricingengines/blackformula.hpp> |
| 36 | #include <ql/pricingengines/inflation/inflationcapfloorengines.hpp> |
| 37 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
| 38 | #include <ql/quotes/simplequote.hpp> |
| 39 | #include <ql/termstructures/inflation/inflationhelpers.hpp> |
| 40 | #include <ql/termstructures/inflation/piecewiseyoyinflationcurve.hpp> |
| 41 | #include <ql/termstructures/volatility/inflation/yoyinflationoptionletvolatilitystructure.hpp> |
| 42 | #include <ql/termstructures/yield/flatforward.hpp> |
| 43 | #include <ql/time/calendars/unitedkingdom.hpp> |
| 44 | #include <ql/time/daycounters/actual360.hpp> |
| 45 | #include <ql/time/daycounters/actualactual.hpp> |
| 46 | #include <ql/time/daycounters/thirty360.hpp> |
| 47 | #include <ql/time/schedule.hpp> |
| 48 | #include <ql/utilities/dataformatters.hpp> |
| 49 | |
| 50 | using namespace QuantLib; |
| 51 | using namespace boost::unit_test_framework; |
| 52 | |
| 53 | using std::fabs; |
| 54 | |
| 55 | namespace inflation_capfloor_test { |
| 56 | |
| 57 | struct Datum { |
| 58 | Date date; |
| 59 | Rate rate; |
| 60 | }; |
| 61 | |
| 62 | template <class T, class U, class I> |
| 63 | std::vector<ext::shared_ptr<BootstrapHelper<T> > > makeHelpers( |
| 64 | const std::vector<Datum>& iiData, |
| 65 | const ext::shared_ptr<I> &ii, const Period &observationLag, |
| 66 | const Calendar &calendar, |
| 67 | const BusinessDayConvention &bdc, |
| 68 | const DayCounter &dc, |
| 69 | const Handle<YieldTermStructure>& discountCurve) { |
| 70 | |
| 71 | std::vector<ext::shared_ptr<BootstrapHelper<T> > > instruments; |
| 72 | for (Datum datum : iiData) { |
| 73 | Date maturity = datum.date; |
| 74 | Handle<Quote> quote(ext::shared_ptr<Quote>( |
| 75 | new SimpleQuote(datum.rate/100.0))); |
| 76 | ext::shared_ptr<BootstrapHelper<T> > anInstrument(new U( |
| 77 | quote, observationLag, maturity, |
| 78 | calendar, bdc, dc, ii, discountCurve)); |
| 79 | instruments.push_back(anInstrument); |
| 80 | } |
| 81 | |
| 82 | return instruments; |
| 83 | } |
| 84 | |
| 85 | |
| 86 | struct CommonVars { |
| 87 | // common data |
| 88 | |
| 89 | Frequency frequency; |
| 90 | std::vector<Real> nominals; |
| 91 | Calendar calendar; |
| 92 | BusinessDayConvention convention; |
| 93 | Natural fixingDays; |
| 94 | Date evaluationDate; |
| 95 | Natural settlementDays; |
| 96 | Date settlement; |
| 97 | Period observationLag; |
| 98 | DayCounter dc; |
| 99 | ext::shared_ptr<YoYInflationIndex> iir; |
| 100 | |
| 101 | RelinkableHandle<YieldTermStructure> nominalTS; |
| 102 | ext::shared_ptr<YoYInflationTermStructure> yoyTS; |
| 103 | RelinkableHandle<YoYInflationTermStructure> hy; |
| 104 | |
| 105 | // setup |
| 106 | CommonVars() |
| 107 | : nominals(1,1000000) { |
| 108 | // option variables |
| 109 | frequency = Annual; |
| 110 | // usual setup |
| 111 | calendar = UnitedKingdom(); |
| 112 | convention = ModifiedFollowing; |
| 113 | Date today(13, August, 2007); |
| 114 | evaluationDate = calendar.adjust(today); |
| 115 | Settings::instance().evaluationDate() = evaluationDate; |
| 116 | settlementDays = 0; |
| 117 | fixingDays = 0; |
| 118 | settlement = calendar.advance(today,n: settlementDays,unit: Days); |
| 119 | dc = Thirty360(Thirty360::BondBasis); |
| 120 | |
| 121 | // yoy index |
| 122 | // fixing data |
| 123 | Date from(1, January, 2005); |
| 124 | Date to(13, August, 2007); |
| 125 | Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to) |
| 126 | .withTenor(1*Months) |
| 127 | .withCalendar(UnitedKingdom()) |
| 128 | .withConvention(ModifiedFollowing); |
| 129 | Real fixData[] = { 189.9, 189.9, 189.6, 190.5, 191.6, 192.0, |
| 130 | 192.2, 192.2, 192.6, 193.1, 193.3, 193.6, |
| 131 | 194.1, 193.4, 194.2, 195.0, 196.5, 197.7, |
| 132 | 198.5, 198.5, 199.2, 200.1, 200.4, 201.1, |
| 133 | 202.7, 201.6, 203.1, 204.4, 205.4, 206.2, |
| 134 | 207.3, -999.0, -999 }; |
| 135 | auto rpi = ext::make_shared<UKRPI>(); |
| 136 | for (Size i=0; i<rpiSchedule.size();i++) { |
| 137 | rpi->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]); |
| 138 | } |
| 139 | // link from yoy index to yoy TS |
| 140 | bool interp = false; |
| 141 | iir = ext::make_shared<YoYInflationIndex>(args&: rpi, args&: interp, args&: hy); |
| 142 | |
| 143 | ext::shared_ptr<YieldTermStructure> nominalFF( |
| 144 | new FlatForward(evaluationDate, 0.05, ActualActual(ActualActual::ISDA))); |
| 145 | nominalTS.linkTo(h: nominalFF); |
| 146 | |
| 147 | // now build the YoY inflation curve |
| 148 | Period observationLag = Period(2,Months); |
| 149 | |
| 150 | std::vector<Datum> yyData = { |
| 151 | { .date: Date(13, August, 2008), .rate: 2.95 }, |
| 152 | { .date: Date(13, August, 2009), .rate: 2.95 }, |
| 153 | { .date: Date(13, August, 2010), .rate: 2.93 }, |
| 154 | { .date: Date(15, August, 2011), .rate: 2.955 }, |
| 155 | { .date: Date(13, August, 2012), .rate: 2.945 }, |
| 156 | { .date: Date(13, August, 2013), .rate: 2.985 }, |
| 157 | { .date: Date(13, August, 2014), .rate: 3.01 }, |
| 158 | { .date: Date(13, August, 2015), .rate: 3.035 }, |
| 159 | { .date: Date(13, August, 2016), .rate: 3.055 }, // note that |
| 160 | { .date: Date(13, August, 2017), .rate: 3.075 }, // some dates will be on |
| 161 | { .date: Date(13, August, 2019), .rate: 3.105 }, // holidays but the payment |
| 162 | { .date: Date(15, August, 2022), .rate: 3.135 }, // calendar will roll them |
| 163 | { .date: Date(13, August, 2027), .rate: 3.155 }, |
| 164 | { .date: Date(13, August, 2032), .rate: 3.145 }, |
| 165 | { .date: Date(13, August, 2037), .rate: 3.145 } |
| 166 | }; |
| 167 | |
| 168 | // now build the helpers ... |
| 169 | std::vector<ext::shared_ptr<BootstrapHelper<YoYInflationTermStructure> > > helpers = |
| 170 | makeHelpers<YoYInflationTermStructure,YearOnYearInflationSwapHelper, |
| 171 | YoYInflationIndex>(iiData: yyData, ii: iir, |
| 172 | observationLag, |
| 173 | calendar, bdc: convention, dc, |
| 174 | discountCurve: Handle<YieldTermStructure>(nominalTS)); |
| 175 | |
| 176 | Rate baseYYRate = yyData[0].rate/100.0; |
| 177 | ext::shared_ptr<PiecewiseYoYInflationCurve<Linear> > pYYTS( |
| 178 | new PiecewiseYoYInflationCurve<Linear>( |
| 179 | evaluationDate, calendar, dc, observationLag, |
| 180 | iir->frequency(),iir->interpolated(), baseYYRate, |
| 181 | helpers)); |
| 182 | pYYTS->recalculate(); |
| 183 | yoyTS = ext::dynamic_pointer_cast<YoYInflationTermStructure>(r: pYYTS); |
| 184 | |
| 185 | |
| 186 | // make sure that the index has the latest yoy term structure |
| 187 | hy.linkTo(h: pYYTS); |
| 188 | } |
| 189 | |
| 190 | // utilities |
| 191 | Leg makeYoYLeg(const Date& startDate, Integer length) const { |
| 192 | ext::shared_ptr<YoYInflationIndex> ii = |
| 193 | ext::dynamic_pointer_cast<YoYInflationIndex>(r: iir); |
| 194 | Date endDate = calendar.advance(date: startDate,period: length*Years,convention: Unadjusted); |
| 195 | Schedule schedule(startDate, endDate, Period(frequency), calendar, |
| 196 | Unadjusted,Unadjusted,// ref periods & acc periods |
| 197 | DateGeneration::Forward, false); |
| 198 | return yoyInflationLeg(schedule, calendar, ii, observationLag) |
| 199 | .withNotionals(notionals: nominals) |
| 200 | .withPaymentDayCounter(dc) |
| 201 | .withPaymentAdjustment(convention); |
| 202 | } |
| 203 | |
| 204 | |
| 205 | ext::shared_ptr<PricingEngine> makeEngine(Volatility volatility, Size which) const { |
| 206 | |
| 207 | ext::shared_ptr<YoYInflationIndex> |
| 208 | yyii = ext::dynamic_pointer_cast<YoYInflationIndex>(r: iir); |
| 209 | |
| 210 | Handle<YoYOptionletVolatilitySurface> |
| 211 | vol(ext::make_shared<ConstantYoYOptionletVolatility>( |
| 212 | args&: volatility, |
| 213 | args: settlementDays, |
| 214 | args: calendar, |
| 215 | args: convention, |
| 216 | args: dc, |
| 217 | args: observationLag, |
| 218 | args: frequency, |
| 219 | args: iir->interpolated())); |
| 220 | |
| 221 | |
| 222 | switch (which) { |
| 223 | case 0: |
| 224 | return ext::shared_ptr<PricingEngine>( |
| 225 | new YoYInflationBlackCapFloorEngine(iir, vol, nominalTS)); |
| 226 | break; |
| 227 | case 1: |
| 228 | return ext::shared_ptr<PricingEngine>( |
| 229 | new YoYInflationUnitDisplacedBlackCapFloorEngine(iir, vol, nominalTS)); |
| 230 | break; |
| 231 | case 2: |
| 232 | return ext::shared_ptr<PricingEngine>( |
| 233 | new YoYInflationBachelierCapFloorEngine(iir, vol, nominalTS)); |
| 234 | break; |
| 235 | default: |
| 236 | BOOST_FAIL("unknown engine request: which = " <<which |
| 237 | <<"should be 0=Black,1=DD,2=Bachelier" ); |
| 238 | break; |
| 239 | } |
| 240 | // make compiler happy |
| 241 | QL_FAIL("never get here - no engine resolution" ); |
| 242 | } |
| 243 | |
| 244 | |
| 245 | ext::shared_ptr<YoYInflationCapFloor> makeYoYCapFloor(YoYInflationCapFloor::Type type, |
| 246 | const Leg& leg, |
| 247 | Rate strike, |
| 248 | Volatility volatility, |
| 249 | Size which) const { |
| 250 | ext::shared_ptr<YoYInflationCapFloor> result; |
| 251 | switch (type) { |
| 252 | case YoYInflationCapFloor::Cap: |
| 253 | result = ext::shared_ptr<YoYInflationCapFloor>( |
| 254 | new YoYInflationCap(leg, std::vector<Rate>(1, strike))); |
| 255 | break; |
| 256 | case YoYInflationCapFloor::Floor: |
| 257 | result = ext::shared_ptr<YoYInflationCapFloor>( |
| 258 | new YoYInflationFloor(leg, std::vector<Rate>(1, strike))); |
| 259 | break; |
| 260 | default: |
| 261 | QL_FAIL("unknown YoYInflation cap/floor type" ); |
| 262 | } |
| 263 | result->setPricingEngine(makeEngine(volatility, which)); |
| 264 | return result; |
| 265 | } |
| 266 | }; |
| 267 | |
| 268 | } |
| 269 | |
| 270 | |
| 271 | |
| 272 | void InflationCapFloorTest::testConsistency() { |
| 273 | |
| 274 | BOOST_TEST_MESSAGE("Testing consistency between yoy inflation cap," |
| 275 | " floor and collar..." ); |
| 276 | |
| 277 | using namespace inflation_capfloor_test; |
| 278 | |
| 279 | CommonVars vars; |
| 280 | |
| 281 | Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 }; |
| 282 | Rate cap_rates[] = { 0.01, 0.025, 0.029, 0.03, 0.031, 0.035, 0.07 }; |
| 283 | Rate floor_rates[] = { 0.01, 0.025, 0.029, 0.03, 0.031, 0.035, 0.07 }; |
| 284 | Volatility vols[] = { 0.001, 0.005, 0.010, 0.015, 0.020 }; |
| 285 | |
| 286 | for (Size whichPricer = 0; whichPricer < 3; whichPricer++) { |
| 287 | for (int& length : lengths) { |
| 288 | for (Real& cap_rate : cap_rates) { |
| 289 | for (Real& floor_rate : floor_rates) { |
| 290 | for (Real vol : vols) { |
| 291 | |
| 292 | Leg leg = vars.makeYoYLeg(startDate: vars.evaluationDate, length); |
| 293 | |
| 294 | ext::shared_ptr<YoYInflationCapFloor> cap = vars.makeYoYCapFloor( |
| 295 | type: YoYInflationCapFloor::Cap, leg, strike: cap_rate, volatility: vol, which: whichPricer); |
| 296 | |
| 297 | ext::shared_ptr<YoYInflationCapFloor> floor = vars.makeYoYCapFloor( |
| 298 | type: YoYInflationCapFloor::Floor, leg, strike: floor_rate, volatility: vol, which: whichPricer); |
| 299 | |
| 300 | YoYInflationCollar collar(leg, std::vector<Rate>(1, cap_rate), |
| 301 | std::vector<Rate>(1, floor_rate)); |
| 302 | collar.setPricingEngine(vars.makeEngine(volatility: vol, which: whichPricer)); |
| 303 | |
| 304 | if (std::fabs(x: (cap->NPV() - floor->NPV()) - collar.NPV()) > 1e-6) { |
| 305 | BOOST_FAIL("inconsistency between cap, floor and collar:\n" |
| 306 | << " length: " << length << " years\n" |
| 307 | << " volatility: " << io::volatility(vol) << "\n" |
| 308 | << " cap value: " << cap->NPV() |
| 309 | << " at strike: " << io::rate(cap_rate) << "\n" |
| 310 | << " floor value: " << floor->NPV() |
| 311 | << " at strike: " << io::rate(floor_rate) << "\n" |
| 312 | << " collar value: " << collar.NPV()); |
| 313 | |
| 314 | |
| 315 | // test re-composition by optionlets, N.B. ONE per year |
| 316 | Real capletsNPV = 0.0; |
| 317 | std::vector<ext::shared_ptr<YoYInflationCapFloor> > caplets; |
| 318 | for (Integer m = 0; m < length * 1; m++) { |
| 319 | caplets.push_back(x: cap->optionlet(n: m)); |
| 320 | caplets[m]->setPricingEngine(vars.makeEngine(volatility: vol, which: whichPricer)); |
| 321 | capletsNPV += caplets[m]->NPV(); |
| 322 | } |
| 323 | |
| 324 | if (std::fabs(x: cap->NPV() - capletsNPV) > 1e-6) { |
| 325 | BOOST_FAIL("sum of caplet NPVs does not equal cap NPV:\n" |
| 326 | << " length: " << length << " years\n" |
| 327 | << " volatility: " << io::volatility(vol) << "\n" |
| 328 | << " cap value: " << cap->NPV() |
| 329 | << " at strike: " << io::rate(cap_rate) << "\n" |
| 330 | << " sum of caplets value: " << capletsNPV |
| 331 | << " at strike (first): " |
| 332 | << io::rate(caplets[0]->capRates()[0]) << "\n" ); |
| 333 | } |
| 334 | |
| 335 | Real floorletsNPV = 0.0; |
| 336 | std::vector<ext::shared_ptr<YoYInflationCapFloor> > floorlets; |
| 337 | for (Integer m = 0; m < length * 1; m++) { |
| 338 | floorlets.push_back(x: floor->optionlet(n: m)); |
| 339 | floorlets[m]->setPricingEngine(vars.makeEngine(volatility: vol, which: whichPricer)); |
| 340 | floorletsNPV += floorlets[m]->NPV(); |
| 341 | } |
| 342 | |
| 343 | if (std::fabs(x: floor->NPV() - floorletsNPV) > 1e-6) { |
| 344 | BOOST_FAIL("sum of floorlet NPVs does not equal floor NPV:\n" |
| 345 | << " length: " << length << " years\n" |
| 346 | << " volatility: " << io::volatility(vol) << "\n" |
| 347 | << " cap value: " << floor->NPV() |
| 348 | << " at strike: " << io::rate(floor_rate) << "\n" |
| 349 | << " sum of floorlets value: " << floorletsNPV |
| 350 | << " at strike (first): " |
| 351 | << io::rate(floorlets[0]->floorRates()[0]) << "\n" ); |
| 352 | } |
| 353 | |
| 354 | Real collarletsNPV = 0.0; |
| 355 | std::vector<ext::shared_ptr<YoYInflationCapFloor> > collarlets; |
| 356 | for (Integer m = 0; m < length * 1; m++) { |
| 357 | collarlets.push_back(x: collar.optionlet(n: m)); |
| 358 | collarlets[m]->setPricingEngine(vars.makeEngine(volatility: vol, which: whichPricer)); |
| 359 | collarletsNPV += collarlets[m]->NPV(); |
| 360 | } |
| 361 | |
| 362 | if (std::fabs(x: collar.NPV() - collarletsNPV) > 1e-6) { |
| 363 | BOOST_FAIL("sum of collarlet NPVs does not equal collar NPV:\n" |
| 364 | << " length: " << length << " years\n" |
| 365 | << " volatility: " << io::volatility(vol) << "\n" |
| 366 | << " cap value: " << collar.NPV() |
| 367 | << " at strike floor: " << io::rate(floor_rate) |
| 368 | << " at strike cap: " << io::rate(cap_rate) << "\n" |
| 369 | << " sum of collarlets value: " << collarletsNPV |
| 370 | << " at strike floor (first): " |
| 371 | << io::rate(collarlets[0]->floorRates()[0]) |
| 372 | << " at strike cap (first): " |
| 373 | << io::rate(collarlets[0]->capRates()[0]) << "\n" ); |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | } |
| 378 | } |
| 379 | } |
| 380 | } // pricer loop |
| 381 | // remove circular refernce |
| 382 | vars.hy.linkTo(h: ext::shared_ptr<YoYInflationTermStructure>()); |
| 383 | } |
| 384 | |
| 385 | |
| 386 | // Test inflation cap/floor parity, i.e. that cap-floor = swap, note that this |
| 387 | // is different from nominal because in nominal world standard cap/floors do |
| 388 | // not have the first optionlet. This is because they set in advance so |
| 389 | // there is no point. However, yoy inflation generally sets in arrears, |
| 390 | // (actually in arrears with a lag of a few months) thus the first optionlet |
| 391 | // is relevant. Hence we can do a parity test without a special definition |
| 392 | // of the YoY cap/floor instrument. |
| 393 | void InflationCapFloorTest::testParity() { |
| 394 | |
| 395 | BOOST_TEST_MESSAGE("Testing yoy inflation cap/floor parity..." ); |
| 396 | |
| 397 | using namespace inflation_capfloor_test; |
| 398 | |
| 399 | CommonVars vars; |
| 400 | |
| 401 | Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 }; |
| 402 | // vol is low ... |
| 403 | Rate strikes[] = { 0., 0.025, 0.029, 0.03, 0.031, 0.035, 0.07 }; |
| 404 | // yoy inflation vol is generally very low |
| 405 | Volatility vols[] = { 0.001, 0.005, 0.010, 0.015, 0.020 }; |
| 406 | |
| 407 | // cap-floor-swap parity is model-independent |
| 408 | for (Size whichPricer = 0; whichPricer < 3; whichPricer++) { |
| 409 | for (int& length : lengths) { |
| 410 | for (Real strike : strikes) { |
| 411 | for (Real vol : vols) { |
| 412 | |
| 413 | Leg leg = vars.makeYoYLeg(startDate: vars.evaluationDate, length); |
| 414 | |
| 415 | ext::shared_ptr<Instrument> cap = vars.makeYoYCapFloor( |
| 416 | type: YoYInflationCapFloor::Cap, leg, strike, volatility: vol, which: whichPricer); |
| 417 | |
| 418 | ext::shared_ptr<Instrument> floor = vars.makeYoYCapFloor( |
| 419 | type: YoYInflationCapFloor::Floor, leg, strike, volatility: vol, which: whichPricer); |
| 420 | |
| 421 | Date from = vars.nominalTS->referenceDate(); |
| 422 | Date to = from + length * Years; |
| 423 | Schedule yoySchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to) |
| 424 | .withTenor(1*Years) |
| 425 | .withCalendar(UnitedKingdom()) |
| 426 | .withConvention(Unadjusted) |
| 427 | .backwards() |
| 428 | ; |
| 429 | |
| 430 | YearOnYearInflationSwap swap(Swap::Payer, 1000000.0, |
| 431 | yoySchedule, // fixed schedule, but same as yoy |
| 432 | strike, vars.dc, yoySchedule, vars.iir, |
| 433 | vars.observationLag, |
| 434 | 0.0, // spread on index |
| 435 | vars.dc, UnitedKingdom()); |
| 436 | |
| 437 | Handle<YieldTermStructure> hTS(vars.nominalTS); |
| 438 | ext::shared_ptr<PricingEngine> sppe(new DiscountingSwapEngine(hTS)); |
| 439 | swap.setPricingEngine(sppe); |
| 440 | |
| 441 | // N.B. nominals are 10e6 |
| 442 | if (std::fabs(x: (cap->NPV()-floor->NPV()) - swap.NPV()) > 1.0e-6) { |
| 443 | BOOST_FAIL("put/call parity violated:\n" |
| 444 | << " length: " << length << " years\n" |
| 445 | << " volatility: " << io::volatility(vol) << "\n" |
| 446 | << " strike: " << io::rate(strike) << "\n" |
| 447 | << " cap value: " << cap->NPV() << "\n" |
| 448 | << " floor value: " << floor->NPV() << "\n" |
| 449 | << " swap value: " << swap.NPV()); |
| 450 | } |
| 451 | } |
| 452 | } |
| 453 | } |
| 454 | } |
| 455 | // remove circular refernce |
| 456 | vars.hy.linkTo(h: ext::shared_ptr<YoYInflationTermStructure>()); |
| 457 | } |
| 458 | |
| 459 | |
| 460 | |
| 461 | |
| 462 | void InflationCapFloorTest::testCachedValue() { |
| 463 | |
| 464 | BOOST_TEST_MESSAGE("Testing Black yoy inflation cap/floor price" |
| 465 | " against cached values..." ); |
| 466 | |
| 467 | using namespace inflation_capfloor_test; |
| 468 | |
| 469 | CommonVars vars; |
| 470 | |
| 471 | Size whichPricer = 0; // black |
| 472 | |
| 473 | Real K = 0.0295; // one centi-point is fair rate error i.e. < 1 cp |
| 474 | Size j = 2; |
| 475 | Leg leg = vars.makeYoYLeg(startDate: vars.evaluationDate,length: j); |
| 476 | ext::shared_ptr<Instrument> cap |
| 477 | = vars.makeYoYCapFloor(type: YoYInflationCapFloor::Cap,leg, strike: K, volatility: 0.01, which: whichPricer); |
| 478 | |
| 479 | ext::shared_ptr<Instrument> floor |
| 480 | = vars.makeYoYCapFloor(type: YoYInflationCapFloor::Floor,leg, strike: K, volatility: 0.01, which: whichPricer); |
| 481 | |
| 482 | |
| 483 | // close to atm prices |
| 484 | Real cachedCapNPVblack = 219.452; |
| 485 | Real cachedFloorNPVblack = 314.641; |
| 486 | // N.B. notionals are 10e6. |
| 487 | BOOST_CHECK_MESSAGE(fabs(cap->NPV()-cachedCapNPVblack)<0.02,"yoy cap cached NPV wrong " |
| 488 | <<cap->NPV()<<" should be " <<cachedCapNPVblack<<" Black pricer" |
| 489 | <<" diff was " <<(fabs(cap->NPV()-cachedCapNPVblack))); |
| 490 | BOOST_CHECK_MESSAGE(fabs(floor->NPV()-cachedFloorNPVblack)<0.02,"yoy floor cached NPV wrong " |
| 491 | <<floor->NPV()<<" should be " <<cachedFloorNPVblack<<" Black pricer" |
| 492 | <<" diff was " <<(fabs(floor->NPV()-cachedFloorNPVblack))); |
| 493 | |
| 494 | whichPricer = 1; // dd |
| 495 | |
| 496 | cap |
| 497 | = vars.makeYoYCapFloor(type: YoYInflationCapFloor::Cap,leg, strike: K, volatility: 0.01, which: whichPricer); |
| 498 | |
| 499 | floor |
| 500 | = vars.makeYoYCapFloor(type: YoYInflationCapFloor::Floor,leg, strike: K, volatility: 0.01, which: whichPricer); |
| 501 | |
| 502 | // close to atm prices |
| 503 | Real cachedCapNPVdd = 9114.61; |
| 504 | Real cachedFloorNPVdd = 9209.8; |
| 505 | // N.B. notionals are 10e6. |
| 506 | BOOST_CHECK_MESSAGE(fabs(cap->NPV()-cachedCapNPVdd)<0.22,"yoy cap cached NPV wrong " |
| 507 | <<cap->NPV()<<" should be " <<cachedCapNPVdd<<" dd Black pricer" |
| 508 | <<" diff was " <<(fabs(cap->NPV()-cachedCapNPVdd))); |
| 509 | BOOST_CHECK_MESSAGE(fabs(floor->NPV()-cachedFloorNPVdd)<0.22,"yoy floor cached NPV wrong " |
| 510 | <<floor->NPV()<<" should be " <<cachedFloorNPVdd<<" dd Black pricer" |
| 511 | <<" diff was " <<(fabs(floor->NPV()-cachedFloorNPVdd))); |
| 512 | |
| 513 | whichPricer = 2; // bachelier |
| 514 | |
| 515 | cap |
| 516 | = vars.makeYoYCapFloor(type: YoYInflationCapFloor::Cap,leg, strike: K, volatility: 0.01, which: whichPricer); |
| 517 | |
| 518 | floor |
| 519 | = vars.makeYoYCapFloor(type: YoYInflationCapFloor::Floor,leg, strike: K, volatility: 0.01, which: whichPricer); |
| 520 | |
| 521 | // close to atm prices |
| 522 | Real cachedCapNPVbac = 8852.4; |
| 523 | Real cachedFloorNPVbac = 8947.59; |
| 524 | // N.B. notionals are 10e6. |
| 525 | BOOST_CHECK_MESSAGE(fabs(cap->NPV()-cachedCapNPVbac)<0.22,"yoy cap cached NPV wrong " |
| 526 | <<cap->NPV()<<" should be " <<cachedCapNPVbac<<" bac Black pricer" |
| 527 | <<" diff was " <<(fabs(cap->NPV()-cachedCapNPVbac))); |
| 528 | BOOST_CHECK_MESSAGE(fabs(floor->NPV()-cachedFloorNPVbac)<0.22,"yoy floor cached NPV wrong " |
| 529 | <<floor->NPV()<<" should be " <<cachedFloorNPVbac<<" bac Black pricer" |
| 530 | <<" diff was " <<(fabs(floor->NPV()-cachedFloorNPVbac))); |
| 531 | |
| 532 | // remove circular refernce |
| 533 | vars.hy.linkTo(h: ext::shared_ptr<YoYInflationTermStructure>()); |
| 534 | } |
| 535 | |
| 536 | |
| 537 | test_suite* InflationCapFloorTest::suite() { |
| 538 | auto* suite = BOOST_TEST_SUITE("Inflation (year-on-year) Cap and floor tests" ); |
| 539 | suite->add(QUANTLIB_TEST_CASE(&InflationCapFloorTest::testConsistency)); |
| 540 | suite->add(QUANTLIB_TEST_CASE(&InflationCapFloorTest::testParity)); |
| 541 | suite->add(QUANTLIB_TEST_CASE(&InflationCapFloorTest::testCachedValue)); |
| 542 | return suite; |
| 543 | } |
| 544 | |
| 545 | |
| 546 | |