| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2011 Chris Kenyon |
| 5 | |
| 6 | This file is part of QuantLib, a free-software/open-source library |
| 7 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 8 | |
| 9 | QuantLib is free software: you can redistribute it and/or modify it |
| 10 | under the terms of the QuantLib license. You should have received a |
| 11 | copy of the license along with this program; if not, please email |
| 12 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 13 | <http://quantlib.org/license.shtml>. |
| 14 | |
| 15 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 16 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 17 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 18 | */ |
| 19 | |
| 20 | #include "utilities.hpp" |
| 21 | #include "inflationcpiswap.hpp" |
| 22 | #include <ql/types.hpp> |
| 23 | #include <ql/indexes/inflation/ukrpi.hpp> |
| 24 | #include <ql/termstructures/bootstraphelper.hpp> |
| 25 | #include <ql/time/calendars/unitedkingdom.hpp> |
| 26 | #include <ql/time/daycounters/actualactual.hpp> |
| 27 | #include <ql/time/daycounters/actual365fixed.hpp> |
| 28 | #include <ql/termstructures/yield/zerocurve.hpp> |
| 29 | #include <ql/indexes/ibor/gbplibor.hpp> |
| 30 | #include <ql/termstructures/inflation/inflationhelpers.hpp> |
| 31 | #include <ql/termstructures/inflation/piecewisezeroinflationcurve.hpp> |
| 32 | #include <ql/cashflows/iborcoupon.hpp> |
| 33 | #include <ql/cashflows/indexedcashflow.hpp> |
| 34 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
| 35 | #include <ql/instruments/zerocouponinflationswap.hpp> |
| 36 | #include <ql/pricingengines/bond/discountingbondengine.hpp> |
| 37 | #include <ql/cashflows/cpicoupon.hpp> |
| 38 | #include <ql/cashflows/cpicouponpricer.hpp> |
| 39 | #include <ql/instruments/cpiswap.hpp> |
| 40 | #include <ql/instruments/bonds/cpibond.hpp> |
| 41 | |
| 42 | using namespace QuantLib; |
| 43 | using namespace boost::unit_test_framework; |
| 44 | |
| 45 | #include <iostream> |
| 46 | |
| 47 | using std::fabs; |
| 48 | |
| 49 | namespace inflation_cpi_swap_test { |
| 50 | struct Datum { |
| 51 | Date date; |
| 52 | Rate rate; |
| 53 | }; |
| 54 | |
| 55 | template <class T, class U, class I> |
| 56 | std::vector<ext::shared_ptr<BootstrapHelper<T> > > makeHelpers( |
| 57 | Datum iiData[], Size N, |
| 58 | const ext::shared_ptr<I> &ii, const Period &observationLag, |
| 59 | const Calendar &calendar, |
| 60 | const BusinessDayConvention &bdc, |
| 61 | const DayCounter &dc, |
| 62 | const Handle<YieldTermStructure>& discountCurve) { |
| 63 | |
| 64 | std::vector<ext::shared_ptr<BootstrapHelper<T> > > instruments; |
| 65 | for (Size i=0; i<N; i++) { |
| 66 | Date maturity = iiData[i].date; |
| 67 | Handle<Quote> quote(ext::shared_ptr<Quote>( |
| 68 | new SimpleQuote(iiData[i].rate/100.0))); |
| 69 | ext::shared_ptr<BootstrapHelper<T> > anInstrument(new U(quote, observationLag, maturity, |
| 70 | calendar, bdc, dc, ii, |
| 71 | CPI::AsIndex, discountCurve)); |
| 72 | instruments.push_back(anInstrument); |
| 73 | } |
| 74 | |
| 75 | return instruments; |
| 76 | } |
| 77 | |
| 78 | |
| 79 | struct CommonVars { |
| 80 | // common data |
| 81 | |
| 82 | Size length; |
| 83 | Date startDate; |
| 84 | Real volatility; |
| 85 | |
| 86 | Frequency frequency; |
| 87 | std::vector<Real> nominals; |
| 88 | Calendar calendar; |
| 89 | BusinessDayConvention convention; |
| 90 | Natural fixingDays; |
| 91 | Date evaluationDate; |
| 92 | Natural settlementDays; |
| 93 | Date settlement; |
| 94 | Period observationLag, contractObservationLag; |
| 95 | CPI::InterpolationType contractObservationInterpolation; |
| 96 | DayCounter dcZCIIS,dcNominal; |
| 97 | std::vector<Date> zciisD; |
| 98 | std::vector<Rate> zciisR; |
| 99 | ext::shared_ptr<UKRPI> ii; |
| 100 | Size zciisDataLength; |
| 101 | |
| 102 | RelinkableHandle<YieldTermStructure> nominalTS; |
| 103 | ext::shared_ptr<ZeroInflationTermStructure> cpiTS; |
| 104 | RelinkableHandle<ZeroInflationTermStructure> hcpi; |
| 105 | |
| 106 | // setup |
| 107 | CommonVars() |
| 108 | : nominals(1,1000000) { |
| 109 | |
| 110 | // option variables |
| 111 | frequency = Annual; |
| 112 | // usual setup |
| 113 | volatility = 0.01; |
| 114 | length = 7; |
| 115 | calendar = UnitedKingdom(); |
| 116 | convention = ModifiedFollowing; |
| 117 | Date today(25, November, 2009); |
| 118 | evaluationDate = calendar.adjust(today); |
| 119 | Settings::instance().evaluationDate() = evaluationDate; |
| 120 | settlementDays = 0; |
| 121 | fixingDays = 0; |
| 122 | settlement = calendar.advance(today,n: settlementDays,unit: Days); |
| 123 | startDate = settlement; |
| 124 | dcZCIIS = ActualActual(ActualActual::ISDA); |
| 125 | dcNominal = ActualActual(ActualActual::ISDA); |
| 126 | |
| 127 | // uk rpi index |
| 128 | // fixing data |
| 129 | Date from(20, July, 2007); |
| 130 | //Date from(20, July, 2008); |
| 131 | Date to(20, November, 2009); |
| 132 | Schedule rpiSchedule = MakeSchedule().from(effectiveDate: from).to(terminationDate: to) |
| 133 | .withTenor(1*Months) |
| 134 | .withCalendar(UnitedKingdom()) |
| 135 | .withConvention(ModifiedFollowing); |
| 136 | Real fixData[] = { |
| 137 | 206.1, 207.3, 208.0, 208.9, 209.7, 210.9, |
| 138 | 209.8, 211.4, 212.1, 214.0, 215.1, 216.8, |
| 139 | 216.5, 217.2, 218.4, 217.7, 216, |
| 140 | 212.9, 210.1, 211.4, 211.3, 211.5, |
| 141 | 212.8, 213.4, 213.4, 213.4, 214.4, |
| 142 | -999.0, -999.0 }; |
| 143 | |
| 144 | // link from cpi index to cpi TS |
| 145 | ii = ext::make_shared<UKRPI>(args&: hcpi); |
| 146 | for (Size i=0; i<rpiSchedule.size();i++) { |
| 147 | ii->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i], forceOverwrite: true);// force overwrite in case multiple use |
| 148 | }; |
| 149 | |
| 150 | |
| 151 | Datum nominalData[] = { |
| 152 | { .date: Date(26, November, 2009), .rate: 0.475 }, |
| 153 | { .date: Date(2, December, 2009), .rate: 0.47498 }, |
| 154 | { .date: Date(29, December, 2009), .rate: 0.49988 }, |
| 155 | { .date: Date(25, February, 2010), .rate: 0.59955 }, |
| 156 | { .date: Date(18, March, 2010), .rate: 0.65361 }, |
| 157 | { .date: Date(25, May, 2010), .rate: 0.82830 }, |
| 158 | // { Date(17, June, 2010), 0.7 }, // can't bootstrap with this data point |
| 159 | { .date: Date(16, September, 2010), .rate: 0.78960 }, |
| 160 | { .date: Date(16, December, 2010), .rate: 0.93762 }, |
| 161 | { .date: Date(17, March, 2011), .rate: 1.12037 }, |
| 162 | { .date: Date(16, June, 2011), .rate: 1.31308 }, |
| 163 | { .date: Date(22, September, 2011),.rate: 1.52011 }, |
| 164 | { .date: Date(25, November, 2011), .rate: 1.78399 }, |
| 165 | { .date: Date(26, November, 2012), .rate: 2.41170 }, |
| 166 | { .date: Date(25, November, 2013), .rate: 2.83935 }, |
| 167 | { .date: Date(25, November, 2014), .rate: 3.12888 }, |
| 168 | { .date: Date(25, November, 2015), .rate: 3.34298 }, |
| 169 | { .date: Date(25, November, 2016), .rate: 3.50632 }, |
| 170 | { .date: Date(27, November, 2017), .rate: 3.63666 }, |
| 171 | { .date: Date(26, November, 2018), .rate: 3.74723 }, |
| 172 | { .date: Date(25, November, 2019), .rate: 3.83988 }, |
| 173 | { .date: Date(25, November, 2021), .rate: 4.00508 }, |
| 174 | { .date: Date(25, November, 2024), .rate: 4.16042 }, |
| 175 | { .date: Date(26, November, 2029), .rate: 4.15577 }, |
| 176 | { .date: Date(27, November, 2034), .rate: 4.04933 }, |
| 177 | { .date: Date(25, November, 2039), .rate: 3.95217 }, |
| 178 | { .date: Date(25, November, 2049), .rate: 3.80932 }, |
| 179 | { .date: Date(25, November, 2059), .rate: 3.80849 }, |
| 180 | { .date: Date(25, November, 2069), .rate: 3.72677 }, |
| 181 | { .date: Date(27, November, 2079), .rate: 3.63082 } |
| 182 | }; |
| 183 | |
| 184 | std::vector<Date> nomD; |
| 185 | std::vector<Rate> nomR; |
| 186 | for (auto& i : nominalData) { |
| 187 | nomD.push_back(x: i.date); |
| 188 | nomR.push_back(x: i.rate / 100.0); |
| 189 | } |
| 190 | ext::shared_ptr<YieldTermStructure> nominal = |
| 191 | ext::make_shared<InterpolatedZeroCurve<Linear>>(args&: nomD,args&: nomR,args&: dcNominal); |
| 192 | |
| 193 | nominalTS.linkTo(h: nominal); |
| 194 | |
| 195 | // now build the zero inflation curve |
| 196 | observationLag = Period(2,Months); |
| 197 | contractObservationLag = Period(3,Months); |
| 198 | contractObservationInterpolation = CPI::Flat; |
| 199 | |
| 200 | Datum zciisData[] = { |
| 201 | { .date: Date(25, November, 2010), .rate: 3.0495 }, |
| 202 | { .date: Date(25, November, 2011), .rate: 2.93 }, |
| 203 | { .date: Date(26, November, 2012), .rate: 2.9795 }, |
| 204 | { .date: Date(25, November, 2013), .rate: 3.029 }, |
| 205 | { .date: Date(25, November, 2014), .rate: 3.1425 }, |
| 206 | { .date: Date(25, November, 2015), .rate: 3.211 }, |
| 207 | { .date: Date(25, November, 2016), .rate: 3.2675 }, |
| 208 | { .date: Date(25, November, 2017), .rate: 3.3625 }, |
| 209 | { .date: Date(25, November, 2018), .rate: 3.405 }, |
| 210 | { .date: Date(25, November, 2019), .rate: 3.48 }, |
| 211 | { .date: Date(25, November, 2021), .rate: 3.576 }, |
| 212 | { .date: Date(25, November, 2024), .rate: 3.649 }, |
| 213 | { .date: Date(26, November, 2029), .rate: 3.751 }, |
| 214 | { .date: Date(27, November, 2034), .rate: 3.77225 }, |
| 215 | { .date: Date(25, November, 2039), .rate: 3.77 }, |
| 216 | { .date: Date(25, November, 2049), .rate: 3.734 }, |
| 217 | { .date: Date(25, November, 2059), .rate: 3.714 }, |
| 218 | }; |
| 219 | zciisDataLength = 17; |
| 220 | for (Size i = 0; i < zciisDataLength; i++) { |
| 221 | zciisD.push_back(x: zciisData[i].date); |
| 222 | zciisR.push_back(x: zciisData[i].rate); |
| 223 | } |
| 224 | |
| 225 | // now build the helpers ... |
| 226 | std::vector<ext::shared_ptr<BootstrapHelper<ZeroInflationTermStructure> > > helpers = |
| 227 | makeHelpers<ZeroInflationTermStructure,ZeroCouponInflationSwapHelper, |
| 228 | ZeroInflationIndex>(iiData: zciisData, N: zciisDataLength, ii, |
| 229 | observationLag, |
| 230 | calendar, bdc: convention, dc: dcZCIIS, |
| 231 | discountCurve: Handle<YieldTermStructure>(nominalTS)); |
| 232 | |
| 233 | // we can use historical or first ZCIIS for this |
| 234 | // we know historical is WAY off market-implied, so use market implied flat. |
| 235 | Rate baseZeroRate = zciisData[0].rate/100.0; |
| 236 | ext::shared_ptr<PiecewiseZeroInflationCurve<Linear> > pCPIts( |
| 237 | new PiecewiseZeroInflationCurve<Linear>( |
| 238 | evaluationDate, calendar, dcZCIIS, observationLag, |
| 239 | ii->frequency(), baseZeroRate, helpers)); |
| 240 | pCPIts->recalculate(); |
| 241 | cpiTS = ext::dynamic_pointer_cast<ZeroInflationTermStructure>(r: pCPIts); |
| 242 | |
| 243 | |
| 244 | // make sure that the index has the latest zero inflation term structure |
| 245 | hcpi.linkTo(h: pCPIts); |
| 246 | } |
| 247 | }; |
| 248 | |
| 249 | } |
| 250 | |
| 251 | |
| 252 | |
| 253 | void CPISwapTest::consistency() { |
| 254 | BOOST_TEST_MESSAGE("Checking CPI swap against inflation term structure..." ); |
| 255 | |
| 256 | using namespace inflation_cpi_swap_test; |
| 257 | |
| 258 | bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons(); |
| 259 | |
| 260 | // check inflation leg vs calculation directly from inflation TS |
| 261 | CommonVars common; |
| 262 | |
| 263 | // ZeroInflationSwap aka CPISwap |
| 264 | |
| 265 | Swap::Type type = Swap::Payer; |
| 266 | Real nominal = 1000000.0; |
| 267 | bool subtractInflationNominal = true; |
| 268 | // float+spread leg |
| 269 | Spread spread = 0.0; |
| 270 | DayCounter floatDayCount = Actual365Fixed(); |
| 271 | BusinessDayConvention floatPaymentConvention = ModifiedFollowing; |
| 272 | Natural fixingDays = 0; |
| 273 | ext::shared_ptr<IborIndex> floatIndex(new GBPLibor(Period(6,Months), |
| 274 | common.nominalTS)); |
| 275 | |
| 276 | // fixed x inflation leg |
| 277 | Rate fixedRate = 0.1;//1% would be 0.01 |
| 278 | Real baseCPI = 206.1; // would be 206.13871 if we were interpolating |
| 279 | DayCounter fixedDayCount = Actual365Fixed(); |
| 280 | BusinessDayConvention fixedPaymentConvention = ModifiedFollowing; |
| 281 | Calendar fixedPaymentCalendar = UnitedKingdom(); |
| 282 | ext::shared_ptr<ZeroInflationIndex> fixedIndex = common.ii; |
| 283 | Period contractObservationLag = common.contractObservationLag; |
| 284 | CPI::InterpolationType observationInterpolation = common.contractObservationInterpolation; |
| 285 | |
| 286 | // set the schedules |
| 287 | Date startDate(2, October, 2007); |
| 288 | Date endDate(2, October, 2052); |
| 289 | Schedule floatSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate) |
| 290 | .withTenor(Period(6,Months)) |
| 291 | .withCalendar(UnitedKingdom()) |
| 292 | .withConvention(floatPaymentConvention) |
| 293 | .backwards() |
| 294 | ; |
| 295 | Schedule fixedSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate) |
| 296 | .withTenor(Period(6,Months)) |
| 297 | .withCalendar(UnitedKingdom()) |
| 298 | .withConvention(Unadjusted) |
| 299 | .backwards() |
| 300 | ; |
| 301 | |
| 302 | |
| 303 | CPISwap zisV(type, nominal, subtractInflationNominal, |
| 304 | spread, floatDayCount, floatSchedule, |
| 305 | floatPaymentConvention, fixingDays, floatIndex, |
| 306 | fixedRate, baseCPI, fixedDayCount, fixedSchedule, |
| 307 | fixedPaymentConvention, contractObservationLag, |
| 308 | fixedIndex, observationInterpolation); |
| 309 | Date asofDate = Settings::instance().evaluationDate(); |
| 310 | |
| 311 | Real floatFix[] = {0.06255,0.05975,0.0637,0.018425,0.0073438,-1,-1}; |
| 312 | Real cpiFix[] = {211.4,217.2,211.4,213.4,-2,-2}; |
| 313 | for(Size i=0;i<floatSchedule.size(); i++){ |
| 314 | if (floatSchedule[i] < common.evaluationDate) { |
| 315 | floatIndex->addFixing(fixingDate: floatSchedule[i], fixing: floatFix[i],forceOverwrite: true);//true=overwrite |
| 316 | } |
| 317 | |
| 318 | ext::shared_ptr<CPICoupon> |
| 319 | zic = ext::dynamic_pointer_cast<CPICoupon>(r: zisV.cpiLeg()[i]); |
| 320 | if (zic != nullptr) { |
| 321 | if (zic->fixingDate() < (common.evaluationDate - Period(1,Months))) { |
| 322 | fixedIndex->addFixing(fixingDate: zic->fixingDate(), fixing: cpiFix[i],forceOverwrite: true); |
| 323 | } |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | // simple structure so simple pricing engine - most work done by index |
| 328 | ext::shared_ptr<DiscountingSwapEngine> dse(new DiscountingSwapEngine(common.nominalTS)); |
| 329 | zisV.setPricingEngine(dse); |
| 330 | |
| 331 | // get float+spread & fixed*inflation leg prices separately |
| 332 | Real testInfLegNPV = 0.0; |
| 333 | for(Size i=0;i<zisV.leg(j: 0).size(); i++){ |
| 334 | |
| 335 | Date zicPayDate = (zisV.leg(j: 0))[i]->date(); |
| 336 | if(zicPayDate > asofDate) { |
| 337 | testInfLegNPV += (zisV.leg(j: 0))[i]->amount()*common.nominalTS->discount(d: zicPayDate); |
| 338 | } |
| 339 | |
| 340 | ext::shared_ptr<CPICoupon> |
| 341 | zicV = ext::dynamic_pointer_cast<CPICoupon>(r: zisV.cpiLeg()[i]); |
| 342 | if (zicV != nullptr) { |
| 343 | Real diff = fabs( x: zicV->rate() - (fixedRate*(zicV->indexFixing()/baseCPI)) ); |
| 344 | QL_REQUIRE(diff<1e-8,"failed " <<i<<"th coupon reconstruction as " |
| 345 | << (fixedRate*(zicV->indexFixing()/baseCPI)) << " vs rate = " |
| 346 | <<zicV->rate() << ", with difference: " << diff); |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | Real error = fabs(x: testInfLegNPV - zisV.legNPV(j: 0)); |
| 351 | QL_REQUIRE(error<1e-5, |
| 352 | "failed manual inf leg NPV calc vs pricing engine: " << |
| 353 | testInfLegNPV << " vs " << zisV.legNPV(0)); |
| 354 | |
| 355 | Real diff = fabs(x: 1-zisV.NPV()/4191660.0); |
| 356 | |
| 357 | Real max_diff = usingAtParCoupons ? 1e-5 : 3e-5; |
| 358 | |
| 359 | QL_REQUIRE(diff<max_diff, |
| 360 | "failed stored consistency value test, ratio = " << diff); |
| 361 | |
| 362 | // remove circular refernce |
| 363 | common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>()); |
| 364 | } |
| 365 | |
| 366 | |
| 367 | void CPISwapTest::zciisconsistency() { |
| 368 | BOOST_TEST_MESSAGE("Checking CPI swap against zero-coupon inflation swap..." ); |
| 369 | |
| 370 | using namespace inflation_cpi_swap_test; |
| 371 | |
| 372 | CommonVars common; |
| 373 | |
| 374 | Swap::Type ztype = Swap::Payer; |
| 375 | Real nominal = 1000000.0; |
| 376 | Date startDate(common.evaluationDate); |
| 377 | Date endDate(25, November, 2059); |
| 378 | Calendar cal = UnitedKingdom(); |
| 379 | BusinessDayConvention paymentConvention = ModifiedFollowing; |
| 380 | DayCounter dummyDC, dc = ActualActual(ActualActual::ISDA); |
| 381 | Period observationLag(2,Months); |
| 382 | |
| 383 | Rate quote = 0.03714; |
| 384 | ZeroCouponInflationSwap zciis(ztype, nominal, startDate, endDate, cal, paymentConvention, dc, |
| 385 | quote, common.ii, observationLag, CPI::AsIndex); |
| 386 | |
| 387 | // simple structure so simple pricing engine - most work done by index |
| 388 | ext::shared_ptr<DiscountingSwapEngine> |
| 389 | dse(new DiscountingSwapEngine(common.nominalTS)); |
| 390 | |
| 391 | zciis.setPricingEngine(dse); |
| 392 | QL_REQUIRE(fabs(zciis.NPV())<1e-3,"zciis does not reprice to zero" ); |
| 393 | |
| 394 | std::vector<Date> oneDate = {endDate}; |
| 395 | Schedule schOneDate(oneDate, cal, paymentConvention); |
| 396 | |
| 397 | Swap::Type stype = Swap::Payer; |
| 398 | Real inflationNominal = nominal; |
| 399 | Real floatNominal = inflationNominal * std::pow(x: 1.0+quote,y: 50); |
| 400 | bool subtractInflationNominal = true; |
| 401 | Real dummySpread=0.0, dummyFixedRate=0.0; |
| 402 | Natural fixingDays = 0; |
| 403 | Date baseDate = startDate - observationLag; |
| 404 | Real baseCPI = common.ii->fixing(fixingDate: baseDate); |
| 405 | |
| 406 | ext::shared_ptr<IborIndex> dummyFloatIndex; |
| 407 | |
| 408 | CPISwap cS(stype, floatNominal, subtractInflationNominal, dummySpread, dummyDC, schOneDate, |
| 409 | paymentConvention, fixingDays, dummyFloatIndex, |
| 410 | dummyFixedRate, baseCPI, dummyDC, schOneDate, paymentConvention, observationLag, |
| 411 | common.ii, CPI::AsIndex, inflationNominal); |
| 412 | |
| 413 | cS.setPricingEngine(dse); |
| 414 | QL_REQUIRE(fabs(cS.NPV())<1e-3,"CPISwap as ZCIIS does not reprice to zero" ); |
| 415 | |
| 416 | for (Size i=0; i<2; i++) { |
| 417 | QL_REQUIRE(fabs(cS.legNPV(i)-zciis.legNPV(i))<1e-3,"zciis leg does not equal CPISwap leg" ); |
| 418 | } |
| 419 | // remove circular refernce |
| 420 | common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>()); |
| 421 | } |
| 422 | |
| 423 | |
| 424 | void CPISwapTest::cpibondconsistency() { |
| 425 | BOOST_TEST_MESSAGE("Checking CPI swap against CPI bond..." ); |
| 426 | |
| 427 | using namespace inflation_cpi_swap_test; |
| 428 | |
| 429 | CommonVars common; |
| 430 | |
| 431 | // ZeroInflationSwap aka CPISwap |
| 432 | |
| 433 | Swap::Type type = Swap::Payer; |
| 434 | Real nominal = 1000000.0; |
| 435 | bool subtractInflationNominal = true; |
| 436 | // float+spread leg |
| 437 | Spread spread = 0.0; |
| 438 | DayCounter floatDayCount = Actual365Fixed(); |
| 439 | BusinessDayConvention floatPaymentConvention = ModifiedFollowing; |
| 440 | Natural fixingDays = 0; |
| 441 | ext::shared_ptr<IborIndex> floatIndex(new GBPLibor(Period(6,Months), |
| 442 | common.nominalTS)); |
| 443 | |
| 444 | // fixed x inflation leg |
| 445 | Rate fixedRate = 0.1;//1% would be 0.01 |
| 446 | Real baseCPI = 206.1; // would be 206.13871 if we were interpolating |
| 447 | DayCounter fixedDayCount = Actual365Fixed(); |
| 448 | BusinessDayConvention fixedPaymentConvention = ModifiedFollowing; |
| 449 | Calendar fixedPaymentCalendar = UnitedKingdom(); |
| 450 | ext::shared_ptr<ZeroInflationIndex> fixedIndex = common.ii; |
| 451 | Period contractObservationLag = common.contractObservationLag; |
| 452 | CPI::InterpolationType observationInterpolation = common.contractObservationInterpolation; |
| 453 | |
| 454 | // set the schedules |
| 455 | Date startDate(2, October, 2007); |
| 456 | Date endDate(2, October, 2052); |
| 457 | Schedule floatSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate) |
| 458 | .withTenor(Period(6,Months)) |
| 459 | .withCalendar(UnitedKingdom()) |
| 460 | .withConvention(floatPaymentConvention) |
| 461 | .backwards() |
| 462 | ; |
| 463 | Schedule fixedSchedule = MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate) |
| 464 | .withTenor(Period(6,Months)) |
| 465 | .withCalendar(UnitedKingdom()) |
| 466 | .withConvention(Unadjusted) |
| 467 | .backwards() |
| 468 | ; |
| 469 | |
| 470 | |
| 471 | CPISwap zisV(type, nominal, subtractInflationNominal, |
| 472 | spread, floatDayCount, floatSchedule, |
| 473 | floatPaymentConvention, fixingDays, floatIndex, |
| 474 | fixedRate, baseCPI, fixedDayCount, fixedSchedule, |
| 475 | fixedPaymentConvention, contractObservationLag, |
| 476 | fixedIndex, observationInterpolation); |
| 477 | |
| 478 | Real floatFix[] = {0.06255,0.05975,0.0637,0.018425,0.0073438,-1,-1}; |
| 479 | Real cpiFix[] = {211.4,217.2,211.4,213.4,-2,-2}; |
| 480 | for(Size i=0;i<floatSchedule.size(); i++){ |
| 481 | if (floatSchedule[i] < common.evaluationDate) { |
| 482 | floatIndex->addFixing(fixingDate: floatSchedule[i], fixing: floatFix[i],forceOverwrite: true);//true=overwrite |
| 483 | } |
| 484 | |
| 485 | ext::shared_ptr<CPICoupon> |
| 486 | zic = ext::dynamic_pointer_cast<CPICoupon>(r: zisV.cpiLeg()[i]); |
| 487 | if (zic != nullptr) { |
| 488 | if (zic->fixingDate() < (common.evaluationDate - Period(1,Months))) { |
| 489 | fixedIndex->addFixing(fixingDate: zic->fixingDate(), fixing: cpiFix[i],forceOverwrite: true); |
| 490 | } |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | |
| 495 | // simple structure so simple pricing engine - most work done by index |
| 496 | ext::shared_ptr<DiscountingSwapEngine> dse(new DiscountingSwapEngine(common.nominalTS)); |
| 497 | zisV.setPricingEngine(dse); |
| 498 | |
| 499 | // now do the bond equivalent |
| 500 | std::vector<Rate> fixedRates(1,fixedRate); |
| 501 | Natural settlementDays = 1;// cannot be zero! |
| 502 | bool growthOnly = true; |
| 503 | CPIBond cpiB(settlementDays, nominal, growthOnly, |
| 504 | baseCPI, contractObservationLag, fixedIndex, |
| 505 | observationInterpolation, fixedSchedule, |
| 506 | fixedRates, fixedDayCount, fixedPaymentConvention); |
| 507 | |
| 508 | ext::shared_ptr<DiscountingBondEngine> dbe(new DiscountingBondEngine(common.nominalTS)); |
| 509 | cpiB.setPricingEngine(dbe); |
| 510 | |
| 511 | QL_REQUIRE(fabs(cpiB.NPV() - zisV.legNPV(0))<1e-5,"cpi bond does not equal equivalent cpi swap leg" ); |
| 512 | // remove circular reference |
| 513 | common.hcpi.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>()); |
| 514 | } |
| 515 | |
| 516 | |
| 517 | test_suite* CPISwapTest::suite() { |
| 518 | auto* suite = BOOST_TEST_SUITE("CPISwap tests" ); |
| 519 | |
| 520 | suite->add(QUANTLIB_TEST_CASE(&CPISwapTest::consistency)); |
| 521 | suite->add(QUANTLIB_TEST_CASE(&CPISwapTest::zciisconsistency)); |
| 522 | suite->add(QUANTLIB_TEST_CASE(&CPISwapTest::cpibondconsistency)); |
| 523 | |
| 524 | return suite; |
| 525 | } |
| 526 | |
| 527 | |