| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2007 Ferdinando Ametrano |
| 5 | Copyright (C) 2006 Giorgio Facchinetti |
| 6 | Copyright (C) 2006 Mario Pucci |
| 7 | Copyright (C) 2014 Peter Caspers |
| 8 | |
| 9 | This file is part of QuantLib, a free-software/open-source library |
| 10 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 11 | |
| 12 | QuantLib is free software: you can redistribute it and/or modify it |
| 13 | under the terms of the QuantLib license. You should have received a |
| 14 | copy of the license along with this program; if not, please email |
| 15 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 16 | <http://quantlib.org/license.shtml>. |
| 17 | |
| 18 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 19 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 20 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 21 | */ |
| 22 | |
| 23 | #include "cms.hpp" |
| 24 | #include "utilities.hpp" |
| 25 | #include <ql/instruments/swap.hpp> |
| 26 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
| 27 | #include <ql/indexes/ibor/euribor.hpp> |
| 28 | #include <ql/indexes/swap/euriborswap.hpp> |
| 29 | #include <ql/cashflows/capflooredcoupon.hpp> |
| 30 | #include <ql/cashflows/conundrumpricer.hpp> |
| 31 | #include <ql/cashflows/cashflowvectors.hpp> |
| 32 | #include <ql/cashflows/lineartsrpricer.hpp> |
| 33 | #include <ql/quotes/simplequote.hpp> |
| 34 | #include <ql/termstructures/volatility/swaption/swaptionvolmatrix.hpp> |
| 35 | #include <ql/termstructures/volatility/swaption/interpolatedswaptionvolatilitycube.hpp> |
| 36 | #include <ql/termstructures/volatility/swaption/sabrswaptionvolatilitycube.hpp> |
| 37 | #include <ql/time/calendars/target.hpp> |
| 38 | #include <ql/time/daycounters/thirty360.hpp> |
| 39 | #include <ql/time/schedule.hpp> |
| 40 | #include <ql/utilities/dataformatters.hpp> |
| 41 | #include <ql/instruments/makecms.hpp> |
| 42 | |
| 43 | using namespace QuantLib; |
| 44 | using namespace boost::unit_test_framework; |
| 45 | |
| 46 | namespace cms_test { |
| 47 | |
| 48 | struct CommonVars { |
| 49 | // global data |
| 50 | RelinkableHandle<YieldTermStructure> termStructure; |
| 51 | |
| 52 | ext::shared_ptr<IborIndex> iborIndex; |
| 53 | |
| 54 | Handle<SwaptionVolatilityStructure> atmVol; |
| 55 | Handle<SwaptionVolatilityStructure> SabrVolCube1; |
| 56 | Handle<SwaptionVolatilityStructure> SabrVolCube2; |
| 57 | |
| 58 | std::vector<GFunctionFactory::YieldCurveModel> yieldCurveModels; |
| 59 | std::vector<ext::shared_ptr<CmsCouponPricer> > numericalPricers; |
| 60 | std::vector<ext::shared_ptr<CmsCouponPricer> > analyticPricers; |
| 61 | |
| 62 | // setup |
| 63 | CommonVars() { |
| 64 | |
| 65 | Calendar calendar = TARGET(); |
| 66 | |
| 67 | Date referenceDate = calendar.adjust(Date::todaysDate()); |
| 68 | Settings::instance().evaluationDate() = referenceDate; |
| 69 | |
| 70 | termStructure.linkTo(h: flatRate(today: referenceDate, forward: 0.05, |
| 71 | dc: Actual365Fixed())); |
| 72 | |
| 73 | // ATM Volatility structure |
| 74 | std::vector<Period> atmOptionTenors = {1 * Months, 6 * Months, 1 * Years, |
| 75 | 5 * Years, 10 * Years, 30 * Years}; |
| 76 | |
| 77 | std::vector<Period> atmSwapTenors = {1 * Years, 5 * Years, 10 * Years, 30 * Years}; |
| 78 | |
| 79 | Matrix m(atmOptionTenors.size(), atmSwapTenors.size()); |
| 80 | m[0][0]=0.1300; m[0][1]=0.1560; m[0][2]=0.1390; m[0][3]=0.1220; |
| 81 | m[1][0]=0.1440; m[1][1]=0.1580; m[1][2]=0.1460; m[1][3]=0.1260; |
| 82 | m[2][0]=0.1600; m[2][1]=0.1590; m[2][2]=0.1470; m[2][3]=0.1290; |
| 83 | m[3][0]=0.1640; m[3][1]=0.1470; m[3][2]=0.1370; m[3][3]=0.1220; |
| 84 | m[4][0]=0.1400; m[4][1]=0.1300; m[4][2]=0.1250; m[4][3]=0.1100; |
| 85 | m[5][0]=0.1130; m[5][1]=0.1090; m[5][2]=0.1070; m[5][3]=0.0930; |
| 86 | |
| 87 | atmVol = Handle<SwaptionVolatilityStructure>( |
| 88 | ext::shared_ptr<SwaptionVolatilityStructure>(new |
| 89 | SwaptionVolatilityMatrix(calendar, |
| 90 | Following, |
| 91 | atmOptionTenors, |
| 92 | atmSwapTenors, |
| 93 | m, |
| 94 | Actual365Fixed()))); |
| 95 | |
| 96 | // Vol cubes |
| 97 | std::vector<Period> optionTenors = {{1, Years}, {10, Years}, {30, Years}}; |
| 98 | std::vector<Period> swapTenors = {{2, Years}, {10, Years}, {30, Years}}; |
| 99 | std::vector<Spread> strikeSpreads = {-0.020, -0.005, 0.000, 0.005, 0.020}; |
| 100 | |
| 101 | Size nRows = optionTenors.size()*swapTenors.size(); |
| 102 | Size nCols = strikeSpreads.size(); |
| 103 | Matrix volSpreadsMatrix(nRows, nCols); |
| 104 | volSpreadsMatrix[0][0] = 0.0599; |
| 105 | volSpreadsMatrix[0][1] = 0.0049; |
| 106 | volSpreadsMatrix[0][2] = 0.0000; |
| 107 | volSpreadsMatrix[0][3] = -0.0001; |
| 108 | volSpreadsMatrix[0][4] = 0.0127; |
| 109 | |
| 110 | volSpreadsMatrix[1][0] = 0.0729; |
| 111 | volSpreadsMatrix[1][1] = 0.0086; |
| 112 | volSpreadsMatrix[1][2] = 0.0000; |
| 113 | volSpreadsMatrix[1][3] = -0.0024; |
| 114 | volSpreadsMatrix[1][4] = 0.0098; |
| 115 | |
| 116 | volSpreadsMatrix[2][0] = 0.0738; |
| 117 | volSpreadsMatrix[2][1] = 0.0102; |
| 118 | volSpreadsMatrix[2][2] = 0.0000; |
| 119 | volSpreadsMatrix[2][3] = -0.0039; |
| 120 | volSpreadsMatrix[2][4] = 0.0065; |
| 121 | |
| 122 | volSpreadsMatrix[3][0] = 0.0465; |
| 123 | volSpreadsMatrix[3][1] = 0.0063; |
| 124 | volSpreadsMatrix[3][2] = 0.0000; |
| 125 | volSpreadsMatrix[3][3] = -0.0032; |
| 126 | volSpreadsMatrix[3][4] = -0.0010; |
| 127 | |
| 128 | volSpreadsMatrix[4][0] = 0.0558; |
| 129 | volSpreadsMatrix[4][1] = 0.0084; |
| 130 | volSpreadsMatrix[4][2] = 0.0000; |
| 131 | volSpreadsMatrix[4][3] = -0.0050; |
| 132 | volSpreadsMatrix[4][4] = -0.0057; |
| 133 | |
| 134 | volSpreadsMatrix[5][0] = 0.0576; |
| 135 | volSpreadsMatrix[5][1] = 0.0083; |
| 136 | volSpreadsMatrix[5][2] = 0.0000; |
| 137 | volSpreadsMatrix[5][3] = -0.0043; |
| 138 | volSpreadsMatrix[5][4] = -0.0014; |
| 139 | |
| 140 | volSpreadsMatrix[6][0] = 0.0437; |
| 141 | volSpreadsMatrix[6][1] = 0.0059; |
| 142 | volSpreadsMatrix[6][2] = 0.0000; |
| 143 | volSpreadsMatrix[6][3] = -0.0030; |
| 144 | volSpreadsMatrix[6][4] = -0.0006; |
| 145 | |
| 146 | volSpreadsMatrix[7][0] = 0.0533; |
| 147 | volSpreadsMatrix[7][1] = 0.0078; |
| 148 | volSpreadsMatrix[7][2] = 0.0000; |
| 149 | volSpreadsMatrix[7][3] = -0.0045; |
| 150 | volSpreadsMatrix[7][4] = -0.0046; |
| 151 | |
| 152 | volSpreadsMatrix[8][0] = 0.0545; |
| 153 | volSpreadsMatrix[8][1] = 0.0079; |
| 154 | volSpreadsMatrix[8][2] = 0.0000; |
| 155 | volSpreadsMatrix[8][3] = -0.0042; |
| 156 | volSpreadsMatrix[8][4] = -0.0020; |
| 157 | |
| 158 | std::vector<std::vector<Handle<Quote> > > volSpreads(nRows); |
| 159 | for (Size i=0; i<nRows; ++i){ |
| 160 | volSpreads[i] = std::vector<Handle<Quote> >(nCols); |
| 161 | for (Size j=0; j<nCols; ++j) { |
| 162 | volSpreads[i][j] = Handle<Quote>(ext::shared_ptr<Quote>( |
| 163 | new SimpleQuote(volSpreadsMatrix[i][j]))); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | iborIndex = ext::shared_ptr<IborIndex>(new Euribor6M(termStructure)); |
| 168 | ext::shared_ptr<SwapIndex> swapIndexBase(new |
| 169 | EuriborSwapIsdaFixA(10*Years, termStructure)); |
| 170 | ext::shared_ptr<SwapIndex> shortSwapIndexBase(new |
| 171 | EuriborSwapIsdaFixA(2*Years, termStructure)); |
| 172 | |
| 173 | bool vegaWeightedSmileFit = false; |
| 174 | |
| 175 | SabrVolCube2 = Handle<SwaptionVolatilityStructure>( |
| 176 | ext::make_shared<InterpolatedSwaptionVolatilityCube>(args&: atmVol, |
| 177 | args&: optionTenors, |
| 178 | args&: swapTenors, |
| 179 | args&: strikeSpreads, |
| 180 | args&: volSpreads, |
| 181 | args&: swapIndexBase, |
| 182 | args&: shortSwapIndexBase, |
| 183 | args&: vegaWeightedSmileFit)); |
| 184 | SabrVolCube2->enableExtrapolation(); |
| 185 | |
| 186 | std::vector<std::vector<Handle<Quote> > > guess(nRows); |
| 187 | for (Size i=0; i<nRows; ++i) { |
| 188 | guess[i] = std::vector<Handle<Quote> >(4); |
| 189 | guess[i][0] = |
| 190 | Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.2))); |
| 191 | guess[i][1] = |
| 192 | Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.5))); |
| 193 | guess[i][2] = |
| 194 | Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.4))); |
| 195 | guess[i][3] = |
| 196 | Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.0))); |
| 197 | } |
| 198 | std::vector<bool> isParameterFixed(4, false); |
| 199 | isParameterFixed[1] = true; |
| 200 | |
| 201 | // FIXME |
| 202 | bool isAtmCalibrated = false; |
| 203 | |
| 204 | SabrVolCube1 = Handle<SwaptionVolatilityStructure>( |
| 205 | ext::make_shared<SabrSwaptionVolatilityCube>(args&: atmVol, |
| 206 | args&: optionTenors, |
| 207 | args&: swapTenors, |
| 208 | args&: strikeSpreads, |
| 209 | args&: volSpreads, |
| 210 | args&: swapIndexBase, |
| 211 | args&: shortSwapIndexBase, |
| 212 | args&: vegaWeightedSmileFit, |
| 213 | args&: guess, |
| 214 | args&: isParameterFixed, |
| 215 | args&: isAtmCalibrated)); |
| 216 | SabrVolCube1->enableExtrapolation(); |
| 217 | |
| 218 | yieldCurveModels = {GFunctionFactory::Standard, |
| 219 | GFunctionFactory::ExactYield, |
| 220 | GFunctionFactory::ParallelShifts, |
| 221 | GFunctionFactory::NonParallelShifts, |
| 222 | GFunctionFactory::NonParallelShifts}; |
| 223 | |
| 224 | Handle<Quote> zeroMeanRev(ext::make_shared<SimpleQuote>(args: 0.0)); |
| 225 | |
| 226 | numericalPricers.clear(); |
| 227 | analyticPricers.clear(); |
| 228 | for (Size j = 0; j < yieldCurveModels.size(); ++j) { |
| 229 | if (j < yieldCurveModels.size() - 1) |
| 230 | numericalPricers.push_back( |
| 231 | x: ext::shared_ptr<CmsCouponPricer>(new NumericHaganPricer( |
| 232 | atmVol, yieldCurveModels[j], zeroMeanRev))); |
| 233 | else |
| 234 | numericalPricers.push_back(x: ext::shared_ptr<CmsCouponPricer>( |
| 235 | new LinearTsrPricer(atmVol, zeroMeanRev))); |
| 236 | |
| 237 | analyticPricers.push_back(x: ext::shared_ptr<CmsCouponPricer>(new |
| 238 | AnalyticHaganPricer(atmVol, yieldCurveModels[j], |
| 239 | zeroMeanRev))); |
| 240 | } |
| 241 | } |
| 242 | }; |
| 243 | |
| 244 | } |
| 245 | |
| 246 | |
| 247 | void CmsTest::testFairRate() { |
| 248 | |
| 249 | BOOST_TEST_MESSAGE("Testing Hagan-pricer flat-vol equivalence for coupons (lognormal case)..." ); |
| 250 | |
| 251 | using namespace cms_test; |
| 252 | |
| 253 | CommonVars vars; |
| 254 | |
| 255 | ext::shared_ptr<SwapIndex> swapIndex(new SwapIndex("EuriborSwapIsdaFixA" , |
| 256 | 10*Years, |
| 257 | vars.iborIndex->fixingDays(), |
| 258 | vars.iborIndex->currency(), |
| 259 | vars.iborIndex->fixingCalendar(), |
| 260 | 1*Years, |
| 261 | Unadjusted, |
| 262 | vars.iborIndex->dayCounter(),//?? |
| 263 | vars.iborIndex)); |
| 264 | // FIXME |
| 265 | //ext::shared_ptr<SwapIndex> swapIndex(new |
| 266 | // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure())); |
| 267 | Date startDate = vars.termStructure->referenceDate() + 20*Years; |
| 268 | Date paymentDate = startDate + 1*Years; |
| 269 | Date endDate = paymentDate; |
| 270 | Real nominal = 1.0; |
| 271 | Rate infiniteCap = Null<Real>(); |
| 272 | Rate infiniteFloor = Null<Real>(); |
| 273 | Real gearing = 1.0; |
| 274 | Spread spread = 0.0; |
| 275 | CappedFlooredCmsCoupon coupon(paymentDate, nominal, |
| 276 | startDate, endDate, |
| 277 | swapIndex->fixingDays(), swapIndex, |
| 278 | gearing, spread, |
| 279 | infiniteCap, infiniteFloor, |
| 280 | startDate, endDate, |
| 281 | vars.iborIndex->dayCounter()); |
| 282 | for (Size j=0; j<vars.yieldCurveModels.size(); ++j) { |
| 283 | vars.numericalPricers[j]->setSwaptionVolatility(vars.atmVol); |
| 284 | coupon.setPricer(vars.numericalPricers[j]); |
| 285 | Rate rate0 = coupon.rate(); |
| 286 | |
| 287 | vars.analyticPricers[j]->setSwaptionVolatility(vars.atmVol); |
| 288 | coupon.setPricer(vars.analyticPricers[j]); |
| 289 | Rate rate1 = coupon.rate(); |
| 290 | |
| 291 | Spread difference = std::fabs(x: rate1-rate0); |
| 292 | Spread tol = 2.0e-4; |
| 293 | bool linearTsr = j==vars.yieldCurveModels.size()-1; |
| 294 | |
| 295 | if (difference > tol) |
| 296 | BOOST_FAIL("\nCoupon payment date: " << paymentDate << |
| 297 | "\nCoupon start date: " << startDate << |
| 298 | "\nCoupon floor: " << io::rate(infiniteFloor) << |
| 299 | "\nCoupon gearing: " << io::rate(gearing) << |
| 300 | "\nCoupon swap index: " << swapIndex->name() << |
| 301 | "\nCoupon spread: " << io::rate(spread) << |
| 302 | "\nCoupon cap: " << io::rate(infiniteCap) << |
| 303 | "\nCoupon DayCounter: " << vars.iborIndex->dayCounter()<< |
| 304 | "\nYieldCurve Model: " << vars.yieldCurveModels[j] << |
| 305 | "\nNumerical Pricer: " << io::rate(rate0) << |
| 306 | (linearTsr ? " (Linear TSR Model)" : "" ) << |
| 307 | "\nAnalytic Pricer: " << io::rate(rate1) << |
| 308 | "\ndifference: " << io::rate(difference) << |
| 309 | "\ntolerance: " << io::rate(tol)); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | void CmsTest::testCmsSwap() { |
| 314 | |
| 315 | BOOST_TEST_MESSAGE("Testing Hagan-pricer flat-vol equivalence for swaps (lognormal case)..." ); |
| 316 | |
| 317 | using namespace cms_test; |
| 318 | |
| 319 | CommonVars vars; |
| 320 | |
| 321 | ext::shared_ptr<SwapIndex> swapIndex(new SwapIndex("EuriborSwapIsdaFixA" , |
| 322 | 10*Years, |
| 323 | vars.iborIndex->fixingDays(), |
| 324 | vars.iborIndex->currency(), |
| 325 | vars.iborIndex->fixingCalendar(), |
| 326 | 1*Years, |
| 327 | Unadjusted, |
| 328 | vars.iborIndex->dayCounter(),//?? |
| 329 | vars.iborIndex)); |
| 330 | // FIXME |
| 331 | //ext::shared_ptr<SwapIndex> swapIndex(new |
| 332 | // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure())); |
| 333 | Spread spread = 0.0; |
| 334 | std::vector<Size> swapLengths = {1, 5, 6, 10}; |
| 335 | Size n = swapLengths.size(); |
| 336 | std::vector<ext::shared_ptr<Swap> > cms(n); |
| 337 | for (Size i=0; i<n; ++i) |
| 338 | // no cap, floor |
| 339 | // no gearing, spread |
| 340 | cms[i] = MakeCms(Period(swapLengths[i], Years), |
| 341 | swapIndex, |
| 342 | vars.iborIndex, spread, |
| 343 | 10*Days); |
| 344 | |
| 345 | for (Size j=0; j<vars.yieldCurveModels.size(); ++j) { |
| 346 | vars.numericalPricers[j]->setSwaptionVolatility(vars.atmVol); |
| 347 | vars.analyticPricers[j]->setSwaptionVolatility(vars.atmVol); |
| 348 | for (Size sl=0; sl<n; ++sl) { |
| 349 | setCouponPricer(leg: cms[sl]->leg(j: 0), vars.numericalPricers[j]); |
| 350 | Real priceNum = cms[sl]->NPV(); |
| 351 | setCouponPricer(leg: cms[sl]->leg(j: 0), vars.analyticPricers[j]); |
| 352 | Real priceAn = cms[sl]->NPV(); |
| 353 | |
| 354 | Real difference = std::fabs(x: priceNum-priceAn); |
| 355 | Real tol = 2.0e-4; |
| 356 | bool linearTsr = j==vars.yieldCurveModels.size()-1; |
| 357 | if (difference > tol) |
| 358 | BOOST_FAIL("\nLength in Years: " << swapLengths[sl] << |
| 359 | //"\nfloor: " << io::rate(infiniteFloor) << |
| 360 | //"\ngearing: " << io::rate(gearing) << |
| 361 | "\nswap index: " << swapIndex->name() << |
| 362 | "\nibor index: " << vars.iborIndex->name() << |
| 363 | "\nspread: " << io::rate(spread) << |
| 364 | //"\ncap: " << io::rate(infiniteCap) << |
| 365 | "\nYieldCurve Model: " << vars.yieldCurveModels[j] << |
| 366 | "\nNumerical Pricer: " << io::rate(priceNum) << |
| 367 | (linearTsr ? " (Linear TSR Model)" : "" ) << |
| 368 | "\nAnalytic Pricer: " << io::rate(priceAn) << |
| 369 | "\ndifference: " << io::rate(difference) << |
| 370 | "\ntolerance: " << io::rate(tol)); |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | } |
| 375 | |
| 376 | void CmsTest::testParity() { |
| 377 | |
| 378 | BOOST_TEST_MESSAGE("Testing put-call parity for capped-floored CMS coupons (lognormal case)..." ); |
| 379 | |
| 380 | using namespace cms_test; |
| 381 | |
| 382 | CommonVars vars; |
| 383 | |
| 384 | std::vector<Handle<SwaptionVolatilityStructure> > swaptionVols = { |
| 385 | vars.atmVol, vars.SabrVolCube1, vars.SabrVolCube2}; |
| 386 | |
| 387 | ext::shared_ptr<SwapIndex> swapIndex(new |
| 388 | EuriborSwapIsdaFixA(10*Years, |
| 389 | vars.iborIndex->forwardingTermStructure())); |
| 390 | Date startDate = vars.termStructure->referenceDate() + 20*Years; |
| 391 | Date paymentDate = startDate + 1*Years; |
| 392 | Date endDate = paymentDate; |
| 393 | Real nominal = 1.0; |
| 394 | Rate infiniteCap = Null<Real>(); |
| 395 | Rate infiniteFloor = Null<Real>(); |
| 396 | Real gearing = 1.0; |
| 397 | Spread spread = 0.0; |
| 398 | DiscountFactor discount = vars.termStructure->discount(d: paymentDate); |
| 399 | CappedFlooredCmsCoupon swaplet(paymentDate, nominal, |
| 400 | startDate, endDate, |
| 401 | swapIndex->fixingDays(), |
| 402 | swapIndex, |
| 403 | gearing, spread, |
| 404 | infiniteCap, infiniteFloor, |
| 405 | startDate, endDate, |
| 406 | vars.iborIndex->dayCounter()); |
| 407 | for (Rate strike = .02; strike<.12; strike+=0.05) { |
| 408 | CappedFlooredCmsCoupon caplet(paymentDate, nominal, |
| 409 | startDate, endDate, |
| 410 | swapIndex->fixingDays(), |
| 411 | swapIndex, |
| 412 | gearing, spread, |
| 413 | strike, infiniteFloor, |
| 414 | startDate, endDate, |
| 415 | vars.iborIndex->dayCounter()); |
| 416 | CappedFlooredCmsCoupon floorlet(paymentDate, nominal, |
| 417 | startDate, endDate, |
| 418 | swapIndex->fixingDays(), |
| 419 | swapIndex, |
| 420 | gearing, spread, |
| 421 | infiniteCap, strike, |
| 422 | startDate, endDate, |
| 423 | vars.iborIndex->dayCounter()); |
| 424 | |
| 425 | for (auto& swaptionVol : swaptionVols) { |
| 426 | for (Size j=0; j<vars.yieldCurveModels.size(); ++j) { |
| 427 | vars.numericalPricers[j]->setSwaptionVolatility(swaptionVol); |
| 428 | vars.analyticPricers[j]->setSwaptionVolatility(swaptionVol); |
| 429 | std::vector<ext::shared_ptr<CmsCouponPricer> > pricers(2); |
| 430 | pricers[0] = vars.numericalPricers[j]; |
| 431 | pricers[1] = vars.analyticPricers[j]; |
| 432 | for (Size k=0; k<pricers.size(); ++k) { |
| 433 | swaplet.setPricer(pricers[k]); |
| 434 | caplet.setPricer(pricers[k]); |
| 435 | floorlet.setPricer(pricers[k]); |
| 436 | Real swapletPrice = swaplet.price(discountingCurve: vars.termStructure) + |
| 437 | nominal * swaplet.accrualPeriod() * strike * discount; |
| 438 | Real capletPrice = caplet.price(discountingCurve: vars.termStructure); |
| 439 | Real floorletPrice = floorlet.price(discountingCurve: vars.termStructure); |
| 440 | Real difference = std::fabs(x: capletPrice + floorletPrice - |
| 441 | swapletPrice); |
| 442 | Real tol = 2.0e-5; |
| 443 | bool linearTsr = k==0 && j==vars.yieldCurveModels.size()-1; |
| 444 | if(linearTsr) |
| 445 | tol = 1.0e-7; |
| 446 | if (difference > tol) |
| 447 | BOOST_FAIL("\nCoupon payment date: " << paymentDate << |
| 448 | "\nCoupon start date: " << startDate << |
| 449 | "\nCoupon gearing: " << io::rate(gearing) << |
| 450 | "\nCoupon swap index: " << swapIndex->name() << |
| 451 | "\nCoupon spread: " << io::rate(spread) << |
| 452 | "\nstrike: " << io::rate(strike) << |
| 453 | "\nCoupon DayCounter: " << vars.iborIndex->dayCounter() << |
| 454 | "\nYieldCurve Model: " << vars.yieldCurveModels[j] << |
| 455 | (k==0 ? "\nNumerical Pricer" : "\nAnalytic Pricer" ) << |
| 456 | (linearTsr ? " (Linear TSR Model)" : "" ) << |
| 457 | "\nSwaplet price: " << io::rate(swapletPrice) << |
| 458 | "\nCaplet price: " << io::rate(capletPrice) << |
| 459 | "\nFloorlet price: " << io::rate(floorletPrice) << |
| 460 | "\ndifference: " << difference << |
| 461 | "\ntolerance: " << io::rate(tol)); |
| 462 | } |
| 463 | } |
| 464 | } |
| 465 | } |
| 466 | } |
| 467 | |
| 468 | test_suite* CmsTest::suite() { |
| 469 | auto* suite = BOOST_TEST_SUITE("Cms tests" ); |
| 470 | suite->add(QUANTLIB_TEST_CASE(&CmsTest::testFairRate)); |
| 471 | suite->add(QUANTLIB_TEST_CASE(&CmsTest::testCmsSwap)); |
| 472 | suite->add(QUANTLIB_TEST_CASE(&CmsTest::testParity)); |
| 473 | return suite; |
| 474 | } |
| 475 | |