| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2006 Cristina Duminuco |
| 5 | Copyright (C) 2008 StatPro Italia srl |
| 6 | |
| 7 | This file is part of QuantLib, a free-software/open-source library |
| 8 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 9 | |
| 10 | QuantLib is free software: you can redistribute it and/or modify it |
| 11 | under the terms of the QuantLib license. You should have received a |
| 12 | copy of the license along with this program; if not, please email |
| 13 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 14 | <http://quantlib.org/license.shtml>. |
| 15 | |
| 16 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 17 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 18 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 19 | */ |
| 20 | |
| 21 | #include "capflooredcoupon.hpp" |
| 22 | #include "utilities.hpp" |
| 23 | #include <ql/instruments/capfloor.hpp> |
| 24 | #include <ql/instruments/vanillaswap.hpp> |
| 25 | #include <ql/cashflows/cashflowvectors.hpp> |
| 26 | #include <ql/termstructures/yield/flatforward.hpp> |
| 27 | #include <ql/indexes/ibor/euribor.hpp> |
| 28 | #include <ql/pricingengines/capfloor/blackcapfloorengine.hpp> |
| 29 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
| 30 | #include <ql/math/matrix.hpp> |
| 31 | #include <ql/termstructures/volatility/optionlet/constantoptionletvol.hpp> |
| 32 | #include <ql/time/daycounters/thirty360.hpp> |
| 33 | #include <ql/time/daycounters/actualactual.hpp> |
| 34 | #include <ql/time/schedule.hpp> |
| 35 | #include <ql/utilities/dataformatters.hpp> |
| 36 | #include <ql/cashflows/cashflows.hpp> |
| 37 | #include <ql/cashflows/couponpricer.hpp> |
| 38 | #include <ql/quotes/simplequote.hpp> |
| 39 | |
| 40 | using namespace QuantLib; |
| 41 | using namespace boost::unit_test_framework; |
| 42 | |
| 43 | namespace capfloored_coupon_test { |
| 44 | |
| 45 | struct CommonVars { |
| 46 | // global data |
| 47 | Date today, settlement, startDate; |
| 48 | Calendar calendar; |
| 49 | Real nominal; |
| 50 | std::vector<Real> nominals; |
| 51 | BusinessDayConvention convention; |
| 52 | Frequency frequency; |
| 53 | ext::shared_ptr<IborIndex> index; |
| 54 | Natural settlementDays, fixingDays; |
| 55 | RelinkableHandle<YieldTermStructure> termStructure; |
| 56 | std::vector<Rate> caps; |
| 57 | std::vector<Rate> floors; |
| 58 | Integer length; |
| 59 | Volatility volatility; |
| 60 | |
| 61 | // setup |
| 62 | CommonVars() { |
| 63 | length = 20; //years |
| 64 | volatility = 0.20; |
| 65 | nominal = 100.; |
| 66 | nominals = std::vector<Real>(length,nominal); |
| 67 | frequency = Annual; |
| 68 | index = ext::shared_ptr<IborIndex>(new Euribor1Y(termStructure)); |
| 69 | calendar = index->fixingCalendar(); |
| 70 | convention = ModifiedFollowing; |
| 71 | today = calendar.adjust(Date::todaysDate()); |
| 72 | Settings::instance().evaluationDate() = today; |
| 73 | settlementDays = 2; |
| 74 | fixingDays = 2; |
| 75 | settlement = calendar.advance(today,n: settlementDays,unit: Days); |
| 76 | startDate = settlement; |
| 77 | termStructure.linkTo(h: flatRate(today: settlement,forward: 0.05, |
| 78 | dc: ActualActual(ActualActual::ISDA))); |
| 79 | } |
| 80 | |
| 81 | // utilities |
| 82 | Leg makeFixedLeg(const Date& startDate, Integer length) const { |
| 83 | |
| 84 | Date endDate = calendar.advance(startDate, n: length, unit: Years, |
| 85 | convention); |
| 86 | Schedule schedule(startDate, endDate, Period(frequency), calendar, |
| 87 | convention, convention, |
| 88 | DateGeneration::Forward, false); |
| 89 | std::vector<Rate> coupons(length, 0.0); |
| 90 | return FixedRateLeg(schedule) |
| 91 | .withNotionals(nominals) |
| 92 | .withCouponRates(coupons, paymentDayCounter: Thirty360(Thirty360::BondBasis)); |
| 93 | } |
| 94 | |
| 95 | Leg makeFloatingLeg(const Date& startDate, |
| 96 | Integer length, |
| 97 | const Rate gearing = 1.0, |
| 98 | const Rate spread = 0.0) const { |
| 99 | |
| 100 | Date endDate = calendar.advance(startDate,n: length,unit: Years,convention); |
| 101 | Schedule schedule(startDate,endDate,Period(frequency),calendar, |
| 102 | convention,convention, |
| 103 | DateGeneration::Forward,false); |
| 104 | std::vector<Real> gearingVector(length, gearing); |
| 105 | std::vector<Spread> spreadVector(length, spread); |
| 106 | return IborLeg(schedule, index) |
| 107 | .withNotionals(notionals: nominals) |
| 108 | .withPaymentDayCounter(index->dayCounter()) |
| 109 | .withPaymentAdjustment(convention) |
| 110 | .withFixingDays(fixingDays) |
| 111 | .withGearings(gearings: gearingVector) |
| 112 | .withSpreads(spreads: spreadVector); |
| 113 | } |
| 114 | |
| 115 | Leg makeCapFlooredLeg(const Date& startDate, |
| 116 | Integer length, |
| 117 | const std::vector<Rate>& caps, |
| 118 | const std::vector<Rate>& floors, |
| 119 | Volatility volatility, |
| 120 | const Rate gearing = 1.0, |
| 121 | const Rate spread = 0.0) const { |
| 122 | |
| 123 | Date endDate = calendar.advance(startDate,n: length,unit: Years,convention); |
| 124 | Schedule schedule(startDate,endDate,Period(frequency),calendar, |
| 125 | convention,convention, |
| 126 | DateGeneration::Forward,false); |
| 127 | Handle<OptionletVolatilityStructure> vol( |
| 128 | ext::shared_ptr<OptionletVolatilityStructure>(new |
| 129 | ConstantOptionletVolatility(0, calendar, Following, |
| 130 | volatility,Actual365Fixed()))); |
| 131 | |
| 132 | ext::shared_ptr<IborCouponPricer> pricer(new |
| 133 | BlackIborCouponPricer(vol)); |
| 134 | std::vector<Rate> gearingVector(length, gearing); |
| 135 | std::vector<Spread> spreadVector(length, spread); |
| 136 | |
| 137 | Leg iborLeg = IborLeg(schedule, index) |
| 138 | .withNotionals(notionals: nominals) |
| 139 | .withPaymentDayCounter(index->dayCounter()) |
| 140 | .withPaymentAdjustment(convention) |
| 141 | .withFixingDays(fixingDays) |
| 142 | .withGearings(gearings: gearingVector) |
| 143 | .withSpreads(spreads: spreadVector) |
| 144 | .withCaps(caps) |
| 145 | .withFloors(floors); |
| 146 | setCouponPricer(leg: iborLeg, pricer); |
| 147 | return iborLeg; |
| 148 | } |
| 149 | |
| 150 | ext::shared_ptr<PricingEngine> makeEngine(Volatility volatility) const { |
| 151 | Handle<Quote> vol(ext::shared_ptr<Quote>( |
| 152 | new SimpleQuote(volatility))); |
| 153 | return ext::shared_ptr<PricingEngine>( |
| 154 | new BlackCapFloorEngine(termStructure, vol)); |
| 155 | } |
| 156 | |
| 157 | ext::shared_ptr<CapFloor> makeCapFloor(CapFloor::Type type, |
| 158 | const Leg& leg, |
| 159 | Rate capStrike, |
| 160 | Rate floorStrike, |
| 161 | Volatility volatility) const { |
| 162 | ext::shared_ptr<CapFloor> result; |
| 163 | switch (type) { |
| 164 | case CapFloor::Cap: |
| 165 | result = ext::shared_ptr<CapFloor>( |
| 166 | new Cap(leg, std::vector<Rate>(1, capStrike))); |
| 167 | break; |
| 168 | case CapFloor::Floor: |
| 169 | result = ext::shared_ptr<CapFloor>( |
| 170 | new Floor(leg, std::vector<Rate>(1, floorStrike))); |
| 171 | break; |
| 172 | case CapFloor::Collar: |
| 173 | result = ext::shared_ptr<CapFloor>( |
| 174 | new Collar(leg, |
| 175 | std::vector<Rate>(1, capStrike), |
| 176 | std::vector<Rate>(1, floorStrike))); |
| 177 | break; |
| 178 | default: |
| 179 | QL_FAIL("unknown cap/floor type" ); |
| 180 | } |
| 181 | result->setPricingEngine(makeEngine(volatility)); |
| 182 | return result; |
| 183 | } |
| 184 | }; |
| 185 | |
| 186 | } |
| 187 | |
| 188 | |
| 189 | void CapFlooredCouponTest::testLargeRates() { |
| 190 | |
| 191 | BOOST_TEST_MESSAGE("Testing degenerate collared coupon..." ); |
| 192 | |
| 193 | using namespace capfloored_coupon_test; |
| 194 | |
| 195 | CommonVars vars; |
| 196 | |
| 197 | /* A vanilla floating leg and a capped floating leg with strike |
| 198 | equal to 100 and floor equal to 0 must have (about) the same NPV |
| 199 | (depending on variance: option expiry and volatility) |
| 200 | */ |
| 201 | |
| 202 | std::vector<Rate> caps(vars.length,100.0); |
| 203 | std::vector<Rate> floors(vars.length,0.0); |
| 204 | Real tolerance = 1e-10; |
| 205 | |
| 206 | // fixed leg with zero rate |
| 207 | Leg fixedLeg = |
| 208 | vars.makeFixedLeg(startDate: vars.startDate,length: vars.length); |
| 209 | Leg floatLeg = |
| 210 | vars.makeFloatingLeg(startDate: vars.startDate,length: vars.length); |
| 211 | Leg collaredLeg = |
| 212 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length, |
| 213 | caps,floors,volatility: vars.volatility); |
| 214 | |
| 215 | ext::shared_ptr<PricingEngine> engine( |
| 216 | new DiscountingSwapEngine(vars.termStructure)); |
| 217 | Swap vanillaLeg(fixedLeg,floatLeg); |
| 218 | Swap collarLeg(fixedLeg,collaredLeg); |
| 219 | vanillaLeg.setPricingEngine(engine); |
| 220 | collarLeg.setPricingEngine(engine); |
| 221 | |
| 222 | if (std::abs(x: vanillaLeg.NPV()-collarLeg.NPV())>tolerance) { |
| 223 | BOOST_ERROR("Length: " << vars.length << " y" << "\n" << |
| 224 | "Volatility: " << vars.volatility*100 << "%\n" << |
| 225 | "Notional: " << vars.nominal << "\n" << |
| 226 | "Vanilla floating leg NPV: " << vanillaLeg.NPV() |
| 227 | << "\n" << |
| 228 | "Collared floating leg NPV (strikes 0 and 100): " |
| 229 | << collarLeg.NPV() |
| 230 | << "\n" << |
| 231 | "Diff: " << std::abs(vanillaLeg.NPV()-collarLeg.NPV())); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | void CapFlooredCouponTest::testDecomposition() { |
| 236 | |
| 237 | BOOST_TEST_MESSAGE("Testing collared coupon against its decomposition..." ); |
| 238 | |
| 239 | using namespace capfloored_coupon_test; |
| 240 | |
| 241 | CommonVars vars; |
| 242 | |
| 243 | Real tolerance = 1e-12; |
| 244 | Real npvVanilla,npvCappedLeg,npvFlooredLeg,npvCollaredLeg,npvCap,npvFloor,npvCollar; |
| 245 | Real error; |
| 246 | Rate floorstrike = 0.05; |
| 247 | Rate capstrike = 0.10; |
| 248 | std::vector<Rate> caps(vars.length,capstrike); |
| 249 | std::vector<Rate> caps0 = std::vector<Rate>(); |
| 250 | std::vector<Rate> floors(vars.length,floorstrike); |
| 251 | std::vector<Rate> floors0 = std::vector<Rate>(); |
| 252 | Rate gearing_p = Rate(0.5); |
| 253 | auto spread_p = Spread(0.002); |
| 254 | Rate gearing_n = Rate(-1.5); |
| 255 | auto spread_n = Spread(0.12); |
| 256 | // fixed leg with zero rate |
| 257 | Leg fixedLeg = |
| 258 | vars.makeFixedLeg(startDate: vars.startDate,length: vars.length); |
| 259 | // floating leg with gearing=1 and spread=0 |
| 260 | Leg floatLeg = |
| 261 | vars.makeFloatingLeg(startDate: vars.startDate,length: vars.length); |
| 262 | // floating leg with positive gearing (gearing_p) and spread<>0 |
| 263 | Leg floatLeg_p = |
| 264 | vars.makeFloatingLeg(startDate: vars.startDate,length: vars.length,gearing: gearing_p,spread: spread_p); |
| 265 | // floating leg with negative gearing (gearing_n) and spread<>0 |
| 266 | Leg floatLeg_n = |
| 267 | vars.makeFloatingLeg(startDate: vars.startDate,length: vars.length,gearing: gearing_n,spread: spread_n); |
| 268 | // Swap with null fixed leg and floating leg with gearing=1 and spread=0 |
| 269 | Swap vanillaLeg(fixedLeg,floatLeg); |
| 270 | // Swap with null fixed leg and floating leg with positive gearing and spread<>0 |
| 271 | Swap vanillaLeg_p(fixedLeg,floatLeg_p); |
| 272 | // Swap with null fixed leg and floating leg with negative gearing and spread<>0 |
| 273 | Swap vanillaLeg_n(fixedLeg,floatLeg_n); |
| 274 | |
| 275 | ext::shared_ptr<PricingEngine> engine( |
| 276 | new DiscountingSwapEngine(vars.termStructure)); |
| 277 | vanillaLeg.setPricingEngine(engine); |
| 278 | vanillaLeg_p.setPricingEngine(engine); |
| 279 | vanillaLeg_n.setPricingEngine(engine); |
| 280 | |
| 281 | /* CAPPED coupon - Decomposition of payoff |
| 282 | Payoff = Nom * Min(rate,strike) * accrualperiod = |
| 283 | = Nom * [rate + Min(0,strike-rate)] * accrualperiod = |
| 284 | = Nom * rate * accrualperiod - Nom * Max(rate-strike,0) * accrualperiod = |
| 285 | = VanillaFloatingLeg - Call |
| 286 | */ |
| 287 | |
| 288 | // Case gearing = 1 and spread = 0 |
| 289 | Leg cappedLeg = |
| 290 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length, |
| 291 | caps,floors: floors0,volatility: vars.volatility); |
| 292 | Swap capLeg(fixedLeg,cappedLeg); |
| 293 | capLeg.setPricingEngine(engine); |
| 294 | Cap cap(floatLeg, std::vector<Rate>(1, capstrike)); |
| 295 | cap.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 296 | npvVanilla = vanillaLeg.NPV(); |
| 297 | npvCappedLeg = capLeg.NPV(); |
| 298 | npvCap = cap.NPV(); |
| 299 | error = std::abs(x: npvCappedLeg - (npvVanilla-npvCap)); |
| 300 | if (error>tolerance) { |
| 301 | BOOST_ERROR("\nCapped Leg: gearing=1, spread=0%, strike=" << capstrike*100 << |
| 302 | "%\n" << |
| 303 | " Capped Floating Leg NPV: " << npvCappedLeg << "\n" << |
| 304 | " Floating Leg NPV - Cap NPV: " << npvVanilla - npvCap << "\n" << |
| 305 | " Diff: " << error ); |
| 306 | } |
| 307 | |
| 308 | /* gearing = 1 and spread = 0 |
| 309 | FLOORED coupon - Decomposition of payoff |
| 310 | Payoff = Nom * Max(rate,strike) * accrualperiod = |
| 311 | = Nom * [rate + Max(0,strike-rate)] * accrualperiod = |
| 312 | = Nom * rate * accrualperiod + Nom * Max(strike-rate,0) * accrualperiod = |
| 313 | = VanillaFloatingLeg + Put |
| 314 | */ |
| 315 | |
| 316 | Leg flooredLeg = |
| 317 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length, |
| 318 | caps: caps0,floors,volatility: vars.volatility); |
| 319 | Swap floorLeg(fixedLeg,flooredLeg); |
| 320 | floorLeg.setPricingEngine(engine); |
| 321 | Floor floor(floatLeg, std::vector<Rate>(1, floorstrike)); |
| 322 | floor.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 323 | npvFlooredLeg = floorLeg.NPV(); |
| 324 | npvFloor = floor.NPV(); |
| 325 | error = std::abs(x: npvFlooredLeg-(npvVanilla + npvFloor)); |
| 326 | if (error>tolerance) { |
| 327 | BOOST_ERROR("Floored Leg: gearing=1, spread=0%, strike=" << floorstrike *100 << |
| 328 | "%\n" << |
| 329 | " Floored Floating Leg NPV: " << npvFlooredLeg << "\n" << |
| 330 | " Floating Leg NPV + Floor NPV: " << npvVanilla + npvFloor << "\n" << |
| 331 | " Diff: " << error ); |
| 332 | } |
| 333 | |
| 334 | /* gearing = 1 and spread = 0 |
| 335 | COLLARED coupon - Decomposition of payoff |
| 336 | Payoff = Nom * Min(strikem,Max(rate,strikeM)) * accrualperiod = |
| 337 | = VanillaFloatingLeg - Collar |
| 338 | */ |
| 339 | |
| 340 | Leg collaredLeg = |
| 341 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length, |
| 342 | caps,floors,volatility: vars.volatility); |
| 343 | Swap collarLeg(fixedLeg,collaredLeg); |
| 344 | collarLeg.setPricingEngine(engine); |
| 345 | Collar collar(floatLeg, |
| 346 | std::vector<Rate>(1, capstrike), |
| 347 | std::vector<Rate>(1, floorstrike)); |
| 348 | collar.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 349 | npvCollaredLeg = collarLeg.NPV(); |
| 350 | npvCollar = collar.NPV(); |
| 351 | error = std::abs(x: npvCollaredLeg -(npvVanilla - npvCollar)); |
| 352 | if (error>tolerance) { |
| 353 | BOOST_ERROR("\nCollared Leg: gearing=1, spread=0%, strike=" << |
| 354 | floorstrike*100 << "% and " << capstrike*100 << "%\n" << |
| 355 | " Collared Floating Leg NPV: " << npvCollaredLeg << "\n" << |
| 356 | " Floating Leg NPV - Collar NPV: " << npvVanilla - npvCollar << "\n" << |
| 357 | " Diff: " << error ); |
| 358 | } |
| 359 | |
| 360 | /* gearing = a and spread = b |
| 361 | CAPPED coupon - Decomposition of payoff |
| 362 | Payoff |
| 363 | = Nom * Min(a*rate+b,strike) * accrualperiod = |
| 364 | = Nom * [a*rate+b + Min(0,strike-a*rate-b)] * accrualperiod = |
| 365 | = Nom * a*rate+b * accrualperiod + Nom * Min(strike-b-a*rate,0) * accrualperiod |
| 366 | --> If a>0 (assuming positive effective strike): |
| 367 | Payoff = VanillaFloatingLeg - Call(a*rate+b,strike) |
| 368 | --> If a<0 (assuming positive effective strike): |
| 369 | Payoff = VanillaFloatingLeg + Nom * Min(strike-b+|a|*rate+,0) * accrualperiod = |
| 370 | = VanillaFloatingLeg + Put(|a|*rate+b,strike) |
| 371 | */ |
| 372 | |
| 373 | // Positive gearing |
| 374 | Leg cappedLeg_p = |
| 375 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length,caps,floors: floors0, |
| 376 | volatility: vars.volatility,gearing: gearing_p,spread: spread_p); |
| 377 | Swap capLeg_p(fixedLeg,cappedLeg_p); |
| 378 | capLeg_p.setPricingEngine(engine); |
| 379 | Cap cap_p(floatLeg_p,std::vector<Rate>(1,capstrike)); |
| 380 | cap_p.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 381 | npvVanilla = vanillaLeg_p.NPV(); |
| 382 | npvCappedLeg = capLeg_p.NPV(); |
| 383 | npvCap = cap_p.NPV(); |
| 384 | error = std::abs(x: npvCappedLeg - (npvVanilla-npvCap)); |
| 385 | if (error>tolerance) { |
| 386 | BOOST_ERROR("\nCapped Leg: gearing=" << gearing_p << ", " << |
| 387 | "spread= " << spread_p *100 << |
| 388 | "%, strike=" << capstrike*100 << "%, " << |
| 389 | "effective strike= " << (capstrike-spread_p)/gearing_p*100 << |
| 390 | "%\n" << |
| 391 | " Capped Floating Leg NPV: " << npvCappedLeg << "\n" << |
| 392 | " Vanilla Leg NPV: " << npvVanilla << "\n" << |
| 393 | " Cap NPV: " << npvCap << "\n" << |
| 394 | " Floating Leg NPV - Cap NPV: " << npvVanilla - npvCap << "\n" << |
| 395 | " Diff: " << error ); |
| 396 | } |
| 397 | |
| 398 | // Negative gearing |
| 399 | Leg cappedLeg_n = |
| 400 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length,caps,floors: floors0, |
| 401 | volatility: vars.volatility,gearing: gearing_n,spread: spread_n); |
| 402 | Swap capLeg_n(fixedLeg,cappedLeg_n); |
| 403 | capLeg_n.setPricingEngine(engine); |
| 404 | Floor floor_n(floatLeg,std::vector<Rate>(1,(capstrike-spread_n)/gearing_n)); |
| 405 | floor_n.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 406 | npvVanilla = vanillaLeg_n.NPV(); |
| 407 | npvCappedLeg = capLeg_n.NPV(); |
| 408 | npvFloor = floor_n.NPV(); |
| 409 | error = std::abs(x: npvCappedLeg - (npvVanilla+ gearing_n*npvFloor)); |
| 410 | if (error>tolerance) { |
| 411 | BOOST_ERROR("\nCapped Leg: gearing=" << gearing_n << ", " << |
| 412 | "spread= " << spread_n *100 << |
| 413 | "%, strike=" << capstrike*100 << "%, " << |
| 414 | "effective strike= " << (capstrike-spread_n)/gearing_n*100 << |
| 415 | "%\n" << |
| 416 | " Capped Floating Leg NPV: " << npvCappedLeg << "\n" << |
| 417 | " npv Vanilla: " << npvVanilla << "\n" << |
| 418 | " npvFloor: " << npvFloor << "\n" << |
| 419 | " Floating Leg NPV - Cap NPV: " << npvVanilla + gearing_n*npvFloor << "\n" << |
| 420 | " Diff: " << error ); |
| 421 | } |
| 422 | |
| 423 | /* gearing = a and spread = b |
| 424 | FLOORED coupon - Decomposition of payoff |
| 425 | Payoff |
| 426 | = Nom * Max(a*rate+b,strike) * accrualperiod = |
| 427 | = Nom * [a*rate+b + Max(0,strike-a*rate-b)] * accrualperiod = |
| 428 | = Nom * a*rate+b * accrualperiod + Nom * Max(strike-b-a*rate,0) * accrualperiod |
| 429 | --> If a>0 (assuming positive effective strike): |
| 430 | Payoff = VanillaFloatingLeg + Put(a*rate+b,strike) |
| 431 | --> If a<0 (assuming positive effective strike): |
| 432 | Payoff = VanillaFloatingLeg + Nom * Max(strike-b+|a|*rate+,0) * accrualperiod = |
| 433 | = VanillaFloatingLeg - Call(|a|*rate+b,strike) |
| 434 | */ |
| 435 | |
| 436 | // Positive gearing |
| 437 | Leg flooredLeg_p1 = |
| 438 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length,caps: caps0,floors, |
| 439 | volatility: vars.volatility,gearing: gearing_p,spread: spread_p); |
| 440 | Swap floorLeg_p1(fixedLeg,flooredLeg_p1); |
| 441 | floorLeg_p1.setPricingEngine(engine); |
| 442 | Floor floor_p1(floatLeg_p,std::vector<Rate>(1,floorstrike)); |
| 443 | floor_p1.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 444 | npvVanilla = vanillaLeg_p.NPV(); |
| 445 | npvFlooredLeg = floorLeg_p1.NPV(); |
| 446 | npvFloor = floor_p1.NPV(); |
| 447 | error = std::abs(x: npvFlooredLeg - (npvVanilla+npvFloor)); |
| 448 | if (error>tolerance) { |
| 449 | BOOST_ERROR("\nFloored Leg: gearing=" << gearing_p << ", " |
| 450 | << "spread= " << spread_p *100<< "%, strike=" << floorstrike *100 << "%, " |
| 451 | << "effective strike= " << (floorstrike-spread_p)/gearing_p*100 |
| 452 | << "%\n" << |
| 453 | " Floored Floating Leg NPV: " << npvFlooredLeg |
| 454 | << "\n" << |
| 455 | " Floating Leg NPV + Floor NPV: " << npvVanilla + npvFloor |
| 456 | << "\n" << |
| 457 | " Diff: " << error ); |
| 458 | } |
| 459 | // Negative gearing |
| 460 | Leg flooredLeg_n = |
| 461 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length,caps: caps0,floors, |
| 462 | volatility: vars.volatility,gearing: gearing_n,spread: spread_n); |
| 463 | Swap floorLeg_n(fixedLeg,flooredLeg_n); |
| 464 | floorLeg_n.setPricingEngine(engine); |
| 465 | Cap cap_n(floatLeg,std::vector<Rate>(1,(floorstrike-spread_n)/gearing_n)); |
| 466 | cap_n.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 467 | npvVanilla = vanillaLeg_n.NPV(); |
| 468 | npvFlooredLeg = floorLeg_n.NPV(); |
| 469 | npvCap = cap_n.NPV(); |
| 470 | error = std::abs(x: npvFlooredLeg - (npvVanilla - gearing_n*npvCap)); |
| 471 | if (error>tolerance) { |
| 472 | BOOST_ERROR("\nCapped Leg: gearing=" << gearing_n << ", " << |
| 473 | "spread= " << spread_n *100 << |
| 474 | "%, strike=" << floorstrike*100 << "%, " << |
| 475 | "effective strike= " << (floorstrike-spread_n)/gearing_n*100 << |
| 476 | "%\n" << |
| 477 | " Capped Floating Leg NPV: " << npvFlooredLeg << "\n" << |
| 478 | " Floating Leg NPV - Cap NPV: " << npvVanilla - gearing_n*npvCap << "\n" << |
| 479 | " Diff: " << error ); |
| 480 | } |
| 481 | /* gearing = a and spread = b |
| 482 | COLLARED coupon - Decomposition of payoff |
| 483 | Payoff = Nom * Min(caprate,Max(a*rate+b,floorrate)) * accrualperiod |
| 484 | --> If a>0 (assuming positive effective strike): |
| 485 | Payoff = VanillaFloatingLeg - Collar(a*rate+b, floorrate, caprate) |
| 486 | --> If a<0 (assuming positive effective strike): |
| 487 | Payoff = VanillaFloatingLeg + Collar(|a|*rate+b, caprate, floorrate) |
| 488 | */ |
| 489 | // Positive gearing |
| 490 | Leg collaredLeg_p = |
| 491 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length,caps,floors, |
| 492 | volatility: vars.volatility,gearing: gearing_p,spread: spread_p); |
| 493 | Swap collarLeg_p1(fixedLeg,collaredLeg_p); |
| 494 | collarLeg_p1.setPricingEngine(engine); |
| 495 | Collar collar_p(floatLeg_p, |
| 496 | std::vector<Rate>(1,capstrike), |
| 497 | std::vector<Rate>(1,floorstrike)); |
| 498 | collar_p.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 499 | npvVanilla = vanillaLeg_p.NPV(); |
| 500 | npvCollaredLeg = collarLeg_p1.NPV(); |
| 501 | npvCollar = collar_p.NPV(); |
| 502 | error = std::abs(x: npvCollaredLeg - (npvVanilla - npvCollar)); |
| 503 | if (error>tolerance) { |
| 504 | BOOST_ERROR("\nCollared Leg: gearing=" << gearing_p << ", " |
| 505 | << "spread= " << spread_p*100 << "%, strike=" |
| 506 | << floorstrike*100 << "% and " << capstrike*100 |
| 507 | << "%, " |
| 508 | << "effective strike=" << (floorstrike-spread_p)/gearing_p*100 |
| 509 | << "% and " << (capstrike-spread_p)/gearing_p*100 |
| 510 | << "%\n" << |
| 511 | " Collared Floating Leg NPV: " << npvCollaredLeg |
| 512 | << "\n" << |
| 513 | " Floating Leg NPV - Collar NPV: " << npvVanilla - npvCollar |
| 514 | << "\n" << |
| 515 | " Diff: " << error ); |
| 516 | } |
| 517 | // Negative gearing |
| 518 | Leg collaredLeg_n = |
| 519 | vars.makeCapFlooredLeg(startDate: vars.startDate,length: vars.length,caps,floors, |
| 520 | volatility: vars.volatility,gearing: gearing_n,spread: spread_n); |
| 521 | Swap collarLeg_n1(fixedLeg,collaredLeg_n); |
| 522 | collarLeg_n1.setPricingEngine(engine); |
| 523 | Collar collar_n(floatLeg, |
| 524 | std::vector<Rate>(1,(floorstrike-spread_n)/gearing_n), |
| 525 | std::vector<Rate>(1,(capstrike-spread_n)/gearing_n)); |
| 526 | collar_n.setPricingEngine(vars.makeEngine(volatility: vars.volatility)); |
| 527 | npvVanilla = vanillaLeg_n.NPV(); |
| 528 | npvCollaredLeg = collarLeg_n1.NPV(); |
| 529 | npvCollar = collar_n.NPV(); |
| 530 | error = std::abs(x: npvCollaredLeg - (npvVanilla - gearing_n*npvCollar)); |
| 531 | if (error>tolerance) { |
| 532 | BOOST_ERROR("\nCollared Leg: gearing=" << gearing_n << ", " |
| 533 | << "spread= " << spread_n*100 << "%, strike=" |
| 534 | << floorstrike*100 << "% and " << capstrike*100 |
| 535 | << "%, " |
| 536 | << "effective strike=" << (floorstrike-spread_n)/gearing_n*100 |
| 537 | << "% and " << (capstrike-spread_n)/gearing_n*100 |
| 538 | << "%\n" << |
| 539 | " Collared Floating Leg NPV: " << npvCollaredLeg |
| 540 | << "\n" << |
| 541 | " Floating Leg NPV - Collar NPV: " << npvVanilla - gearing_n*npvCollar |
| 542 | << "\n" << |
| 543 | " Diff: " << error ); |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | test_suite* CapFlooredCouponTest::suite() { |
| 548 | auto* suite = BOOST_TEST_SUITE("Capped and floored coupon tests" ); |
| 549 | suite->add(QUANTLIB_TEST_CASE(&CapFlooredCouponTest::testLargeRates)); |
| 550 | suite->add(QUANTLIB_TEST_CASE(&CapFlooredCouponTest::testDecomposition)); |
| 551 | return suite; |
| 552 | } |
| 553 | |
| 554 | |