void InflationCapFlooredCouponTest::testInstrumentEquality() { BOOST_MESSAGE("Testing inflation capped/floored coupon against inflation capfloor instrument..."); CommonVars vars; Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 }; // vol is low ... Rate strikes[] = { 0.01, 0.025, 0.029, 0.03, 0.031, 0.035, 0.07 }; // yoy inflation vol is generally very low Volatility vols[] = { 0.001, 0.005, 0.010, 0.015, 0.020 }; // this is model independent // capped coupon = fwd - cap, and fwd = swap(0) // floored coupon = fwd + floor for (Size whichPricer = 0; whichPricer < 3; whichPricer++) { for (Size i=0; i<LENGTH(lengths); i++) { for (Size j=0; j<LENGTH(strikes); j++) { for (Size k=0; k<LENGTH(vols); k++) { Leg leg = vars.makeYoYLeg(vars.evaluationDate,lengths[i]); boost::shared_ptr<Instrument> cap = vars.makeYoYCapFloor(YoYInflationCapFloor::Cap, leg, strikes[j], vols[k], whichPricer); boost::shared_ptr<Instrument> floor = vars.makeYoYCapFloor(YoYInflationCapFloor::Floor, leg, strikes[j], vols[k], whichPricer); Date from = vars.nominalTS->referenceDate(); Date to = from+lengths[i]*Years; Schedule yoySchedule = MakeSchedule().from(from).to(to) .withTenor(1*Years) .withCalendar(UnitedKingdom()) .withConvention(Unadjusted) .backwards() ; YearOnYearInflationSwap swap(YearOnYearInflationSwap::Payer, 1000000.0, yoySchedule,//fixed schedule, but same as yoy 0.0,//strikes[j], vars.dc, yoySchedule, vars.iir, vars.observationLag, 0.0, //spread on index vars.dc, UnitedKingdom()); Handle<YieldTermStructure> hTS(vars.nominalTS); boost::shared_ptr<PricingEngine> sppe(new DiscountingSwapEngine(hTS)); swap.setPricingEngine(sppe); Leg leg2 = vars.makeYoYCapFlooredLeg(whichPricer, from, lengths[i], std::vector<Rate>(lengths[i],strikes[j]),//cap std::vector<Rate>(),//floor vols[k], 1.0, // gearing 0.0);// spread Leg leg3 = vars.makeYoYCapFlooredLeg(whichPricer, from, lengths[i], std::vector<Rate>(),// cap std::vector<Rate>(lengths[i],strikes[j]),//floor vols[k], 1.0, // gearing 0.0);// spread // N.B. nominals are 10e6 Real capped = CashFlows::npv(leg2,(**vars.nominalTS),false); if ( fabs(capped - (swap.NPV() - cap->NPV())) > 1.0e-6) { BOOST_FAIL( "capped coupon != swap(0) - cap:\n" << " length: " << lengths[i] << " years\n" << " volatility: " << io::volatility(vols[k]) << "\n" << " strike: " << io::rate(strikes[j]) << "\n" << " cap value: " << cap->NPV() << "\n" << " swap value: " << swap.NPV() << "\n" << " capped coupon " << capped); } // N.B. nominals are 10e6 Real floored = CashFlows::npv(leg3,(**vars.nominalTS),false); if ( fabs(floored - (swap.NPV() + floor->NPV())) > 1.0e-6) { BOOST_FAIL( "floored coupon != swap(0) + floor :\n" << " length: " << lengths[i] << " years\n" << " volatility: " << io::volatility(vols[k]) << "\n" << " strike: " << io::rate(strikes[j]) << "\n" << " floor value: " << floor->NPV() << "\n" << " swap value: " << swap.NPV() << "\n" << " floored coupon " << floored); } } } } } }
// Test inflation cap/floor parity, i.e. that cap-floor = swap, note that this // is different from nominal because in nominal world standard cap/floors do // not have the first optionlet. This is because they set in advance so // there is no point. However, yoy inflation generally sets in arrears, // (actually in arrears with a lag of a few months) thus the first optionlet // is relevant. Hence we can do a parity test without a special definition // of the YoY cap/floor instrument. void InflationCapFloorTest::testParity() { BOOST_TEST_MESSAGE("Testing yoy inflation cap/floor parity..."); CommonVars vars; Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 }; // vol is low ... Rate strikes[] = { 0., 0.025, 0.029, 0.03, 0.031, 0.035, 0.07 }; // yoy inflation vol is generally very low Volatility vols[] = { 0.001, 0.005, 0.010, 0.015, 0.020 }; // cap-floor-swap parity is model-independent for (Size whichPricer = 0; whichPricer < 3; whichPricer++) { for (Size i=0; i<LENGTH(lengths); i++) { for (Size j=0; j<LENGTH(strikes); j++) { for (Size k=0; k<LENGTH(vols); k++) { Leg leg = vars.makeYoYLeg(vars.evaluationDate,lengths[i]); ext::shared_ptr<Instrument> cap = vars.makeYoYCapFloor(YoYInflationCapFloor::Cap, leg, strikes[j], vols[k], whichPricer); ext::shared_ptr<Instrument> floor = vars.makeYoYCapFloor(YoYInflationCapFloor::Floor, leg, strikes[j], vols[k], whichPricer); Date from = vars.nominalTS->referenceDate(); Date to = from+lengths[i]*Years; Schedule yoySchedule = MakeSchedule().from(from).to(to) .withTenor(1*Years) .withCalendar(UnitedKingdom()) .withConvention(Unadjusted) .backwards() ; YearOnYearInflationSwap swap(YearOnYearInflationSwap::Payer, 1000000.0, yoySchedule,//fixed schedule, but same as yoy strikes[j], vars.dc, yoySchedule, vars.iir, vars.observationLag, 0.0, //spread on index vars.dc, UnitedKingdom()); Handle<YieldTermStructure> hTS(vars.nominalTS); ext::shared_ptr<PricingEngine> sppe(new DiscountingSwapEngine(hTS)); swap.setPricingEngine(sppe); // N.B. nominals are 10e6 if (std::fabs((cap->NPV()-floor->NPV()) - swap.NPV()) > 1.0e-6) { BOOST_FAIL( "put/call parity violated:\n" << " length: " << lengths[i] << " years\n" << " volatility: " << io::volatility(vols[k]) << "\n" << " strike: " << io::rate(strikes[j]) << "\n" << " cap value: " << cap->NPV() << "\n" << " floor value: " << floor->NPV() << "\n" << " swap value: " << swap.NPV()); } } } } } // remove circular refernce vars.hy.linkTo(ext::shared_ptr<YoYInflationTermStructure>()); }
void InflationCapFloorTest::testCachedValue() { BOOST_TEST_MESSAGE("Testing Black yoy inflation cap/floor price" " against cached values..."); CommonVars vars; Size whichPricer = 0; // black Real K = 0.0295; // one centi-point is fair rate error i.e. < 1 cp Size j = 2; Leg leg = vars.makeYoYLeg(vars.evaluationDate,j); ext::shared_ptr<Instrument> cap = vars.makeYoYCapFloor(YoYInflationCapFloor::Cap,leg, K, 0.01, whichPricer); ext::shared_ptr<Instrument> floor = vars.makeYoYCapFloor(YoYInflationCapFloor::Floor,leg, K, 0.01, whichPricer); // close to atm prices Real cachedCapNPVblack = 219.452; Real cachedFloorNPVblack = 314.641; // N.B. notionals are 10e6. BOOST_CHECK_MESSAGE(fabs(cap->NPV()-cachedCapNPVblack)<0.02,"yoy cap cached NPV wrong " <<cap->NPV()<<" should be "<<cachedCapNPVblack<<" Black pricer" <<" diff was "<<(fabs(cap->NPV()-cachedCapNPVblack))); BOOST_CHECK_MESSAGE(fabs(floor->NPV()-cachedFloorNPVblack)<0.02,"yoy floor cached NPV wrong " <<floor->NPV()<<" should be "<<cachedFloorNPVblack<<" Black pricer" <<" diff was "<<(fabs(floor->NPV()-cachedFloorNPVblack))); whichPricer = 1; // dd cap = vars.makeYoYCapFloor(YoYInflationCapFloor::Cap,leg, K, 0.01, whichPricer); floor = vars.makeYoYCapFloor(YoYInflationCapFloor::Floor,leg, K, 0.01, whichPricer); // close to atm prices Real cachedCapNPVdd = 9114.61; Real cachedFloorNPVdd = 9209.8; // N.B. notionals are 10e6. BOOST_CHECK_MESSAGE(fabs(cap->NPV()-cachedCapNPVdd)<0.22,"yoy cap cached NPV wrong " <<cap->NPV()<<" should be "<<cachedCapNPVdd<<" dd Black pricer" <<" diff was "<<(fabs(cap->NPV()-cachedCapNPVdd))); BOOST_CHECK_MESSAGE(fabs(floor->NPV()-cachedFloorNPVdd)<0.22,"yoy floor cached NPV wrong " <<floor->NPV()<<" should be "<<cachedFloorNPVdd<<" dd Black pricer" <<" diff was "<<(fabs(floor->NPV()-cachedFloorNPVdd))); whichPricer = 2; // bachelier cap = vars.makeYoYCapFloor(YoYInflationCapFloor::Cap,leg, K, 0.01, whichPricer); floor = vars.makeYoYCapFloor(YoYInflationCapFloor::Floor,leg, K, 0.01, whichPricer); // close to atm prices Real cachedCapNPVbac = 8852.4; Real cachedFloorNPVbac = 8947.59; // N.B. notionals are 10e6. BOOST_CHECK_MESSAGE(fabs(cap->NPV()-cachedCapNPVbac)<0.22,"yoy cap cached NPV wrong " <<cap->NPV()<<" should be "<<cachedCapNPVbac<<" bac Black pricer" <<" diff was "<<(fabs(cap->NPV()-cachedCapNPVbac))); BOOST_CHECK_MESSAGE(fabs(floor->NPV()-cachedFloorNPVbac)<0.22,"yoy floor cached NPV wrong " <<floor->NPV()<<" should be "<<cachedFloorNPVbac<<" bac Black pricer" <<" diff was "<<(fabs(floor->NPV()-cachedFloorNPVbac))); // remove circular refernce vars.hy.linkTo(ext::shared_ptr<YoYInflationTermStructure>()); }
void InflationCapFloorTest::testConsistency() { BOOST_TEST_MESSAGE("Testing consistency between yoy inflation cap," " floor and collar..."); CommonVars vars; Integer lengths[] = { 1, 2, 3, 5, 7, 10, 15, 20 }; Rate cap_rates[] = { 0.01, 0.025, 0.029, 0.03, 0.031, 0.035, 0.07 }; Rate floor_rates[] = { 0.01, 0.025, 0.029, 0.03, 0.031, 0.035, 0.07 }; Volatility vols[] = { 0.001, 0.005, 0.010, 0.015, 0.020 }; for (Size whichPricer = 0; whichPricer < 3; whichPricer++) { for (Size i=0; i<LENGTH(lengths); i++) { for (Size j=0; j<LENGTH(cap_rates); j++) { for (Size k=0; k<LENGTH(floor_rates); k++) { for (Size l=0; l<LENGTH(vols); l++) { Leg leg = vars.makeYoYLeg(vars.evaluationDate,lengths[i]); ext::shared_ptr<YoYInflationCapFloor> cap = vars.makeYoYCapFloor(YoYInflationCapFloor::Cap, leg, cap_rates[j], vols[l], whichPricer); ext::shared_ptr<YoYInflationCapFloor> floor = vars.makeYoYCapFloor(YoYInflationCapFloor::Floor, leg, floor_rates[k], vols[l], whichPricer); YoYInflationCollar collar(leg,std::vector<Rate>(1,cap_rates[j]), std::vector<Rate>(1,floor_rates[k])); collar.setPricingEngine(vars.makeEngine(vols[l], whichPricer)); if (std::fabs((cap->NPV()-floor->NPV())-collar.NPV()) > 1e-6) { BOOST_FAIL( "inconsistency between cap, floor and collar:\n" << " length: " << lengths[i] << " years\n" << " volatility: " << io::volatility(vols[l]) << "\n" << " cap value: " << cap->NPV() << " at strike: " << io::rate(cap_rates[j]) << "\n" << " floor value: " << floor->NPV() << " at strike: " << io::rate(floor_rates[k]) << "\n" << " collar value: " << collar.NPV()); // test re-composition by optionlets, N.B. ONE per year Real capletsNPV = 0.0; std::vector<ext::shared_ptr<YoYInflationCapFloor> > caplets; for (Integer m=0; m<lengths[i]*1; m++) { caplets.push_back(cap->optionlet(m)); caplets[m]->setPricingEngine(vars.makeEngine(vols[l], whichPricer)); capletsNPV += caplets[m]->NPV(); } if (std::fabs(cap->NPV() - capletsNPV) > 1e-6) { BOOST_FAIL( "sum of caplet NPVs does not equal cap NPV:\n" << " length: " << lengths[i] << " years\n" << " volatility: " << io::volatility(vols[l]) << "\n" << " cap value: " << cap->NPV() << " at strike: " << io::rate(cap_rates[j]) << "\n" << " sum of caplets value: " << capletsNPV << " at strike (first): " << io::rate(caplets[0]->capRates()[0]) << "\n" ); } Real floorletsNPV = 0.0; std::vector<ext::shared_ptr<YoYInflationCapFloor> > floorlets; for (Integer m=0; m<lengths[i]*1; m++) { floorlets.push_back(floor->optionlet(m)); floorlets[m]->setPricingEngine(vars.makeEngine(vols[l], whichPricer)); floorletsNPV += floorlets[m]->NPV(); } if (std::fabs(floor->NPV() - floorletsNPV) > 1e-6) { BOOST_FAIL( "sum of floorlet NPVs does not equal floor NPV:\n" << " length: " << lengths[i] << " years\n" << " volatility: " << io::volatility(vols[l]) << "\n" << " cap value: " << floor->NPV() << " at strike: " << io::rate(floor_rates[j]) << "\n" << " sum of floorlets value: " << floorletsNPV << " at strike (first): " << io::rate(floorlets[0]->floorRates()[0]) << "\n" ); } Real collarletsNPV = 0.0; std::vector<ext::shared_ptr<YoYInflationCapFloor> > collarlets; for (Integer m=0; m<lengths[i]*1; m++) { collarlets.push_back(collar.optionlet(m)); collarlets[m]->setPricingEngine(vars.makeEngine(vols[l], whichPricer)); collarletsNPV += collarlets[m]->NPV(); } if (std::fabs(collar.NPV() - collarletsNPV) > 1e-6) { BOOST_FAIL( "sum of collarlet NPVs does not equal floor NPV:\n" << " length: " << lengths[i] << " years\n" << " volatility: " << io::volatility(vols[l]) << "\n" << " cap value: " << collar.NPV() << " at strike floor: " << io::rate(floor_rates[j]) << " at strike cap: " << io::rate(cap_rates[j]) << "\n" << " sum of collarlets value: " << collarletsNPV << " at strike floor (first): " << io::rate(collarlets[0]->floorRates()[0]) << " at strike cap (first): " << io::rate(collarlets[0]->capRates()[0]) << "\n" ); } } } } } } } // pricer loop // remove circular refernce vars.hy.linkTo(ext::shared_ptr<YoYInflationTermStructure>()); }