| 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) 2006, 2007 Ferdinando Ametrano |
| 6 | Copyright (C) 2006 Marco Bianchetti |
| 7 | Copyright (C) 2006 Cristina Duminuco |
| 8 | Copyright (C) 2007, 2008 StatPro Italia srl |
| 9 | Copyright (C) 2020 Marcin Rybacki |
| 10 | |
| 11 | This file is part of QuantLib, a free-software/open-source library |
| 12 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 13 | |
| 14 | QuantLib is free software: you can redistribute it and/or modify it |
| 15 | under the terms of the QuantLib license. You should have received a |
| 16 | copy of the license along with this program; if not, please email |
| 17 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 18 | <http://quantlib.org/license.shtml>. |
| 19 | |
| 20 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 21 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 22 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 23 | */ |
| 24 | |
| 25 | #include "swaption.hpp" |
| 26 | #include "utilities.hpp" |
| 27 | #include <ql/cashflows/iborcoupon.hpp> |
| 28 | #include <ql/instruments/swaption.hpp> |
| 29 | #include <ql/instruments/makevanillaswap.hpp> |
| 30 | #include <ql/termstructures/yield/flatforward.hpp> |
| 31 | #include <ql/indexes/ibor/euribor.hpp> |
| 32 | #include <ql/time/daycounters/actual365fixed.hpp> |
| 33 | #include <ql/time/daycounters/thirty360.hpp> |
| 34 | #include <ql/time/schedule.hpp> |
| 35 | #include <ql/pricingengines/swaption/blackswaptionengine.hpp> |
| 36 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
| 37 | #include <ql/utilities/dataformatters.hpp> |
| 38 | #include <ql/quotes/simplequote.hpp> |
| 39 | |
| 40 | using namespace QuantLib; |
| 41 | using namespace boost::unit_test_framework; |
| 42 | |
| 43 | namespace swaption_test { |
| 44 | |
| 45 | Period exercises[] = { 1*Years, 2*Years, 3*Years, |
| 46 | 5*Years, 7*Years, 10*Years }; |
| 47 | Period lengths[] = { 1*Years, 2*Years, 3*Years, |
| 48 | 5*Years, 7*Years, 10*Years, |
| 49 | 15*Years, 20*Years }; |
| 50 | Swap::Type type[] = { Swap::Receiver, Swap::Payer }; |
| 51 | |
| 52 | struct CommonVars { |
| 53 | // global data |
| 54 | Date today, settlement; |
| 55 | Real nominal; |
| 56 | Calendar calendar; |
| 57 | |
| 58 | BusinessDayConvention fixedConvention; |
| 59 | Frequency fixedFrequency; |
| 60 | DayCounter fixedDayCount; |
| 61 | |
| 62 | BusinessDayConvention floatingConvention; |
| 63 | Period floatingTenor; |
| 64 | ext::shared_ptr<IborIndex> index; |
| 65 | |
| 66 | Natural settlementDays; |
| 67 | RelinkableHandle<YieldTermStructure> termStructure; |
| 68 | |
| 69 | // utilities |
| 70 | ext::shared_ptr<Swaption> makeSwaption( |
| 71 | const ext::shared_ptr<VanillaSwap>& swap, |
| 72 | const Date& exercise, |
| 73 | Volatility volatility, |
| 74 | Settlement::Type settlementType = Settlement::Physical, |
| 75 | Settlement::Method settlementMethod = Settlement::PhysicalOTC, |
| 76 | BlackSwaptionEngine::CashAnnuityModel model = BlackSwaptionEngine::SwapRate) const { |
| 77 | Handle<Quote> vol(ext::shared_ptr<Quote>( |
| 78 | new SimpleQuote(volatility))); |
| 79 | ext::shared_ptr<PricingEngine> engine(new BlackSwaptionEngine( |
| 80 | termStructure, vol, Actual365Fixed(), 0.0, model)); |
| 81 | |
| 82 | ext::shared_ptr<Swaption> result(new |
| 83 | Swaption(swap, |
| 84 | ext::shared_ptr<Exercise>( |
| 85 | new EuropeanExercise(exercise)), |
| 86 | settlementType, settlementMethod)); |
| 87 | result->setPricingEngine(engine); |
| 88 | return result; |
| 89 | } |
| 90 | |
| 91 | ext::shared_ptr<PricingEngine> makeEngine( |
| 92 | Volatility volatility, |
| 93 | BlackSwaptionEngine::CashAnnuityModel model = BlackSwaptionEngine::SwapRate) const { |
| 94 | Handle<Quote> h(ext::shared_ptr<Quote>(new SimpleQuote(volatility))); |
| 95 | return ext::shared_ptr<PricingEngine>( |
| 96 | new BlackSwaptionEngine(termStructure, h, Actual365Fixed(), 0.0, model)); |
| 97 | } |
| 98 | |
| 99 | CommonVars() { |
| 100 | settlementDays = 2; |
| 101 | nominal = 1000000.0; |
| 102 | fixedConvention = Unadjusted; |
| 103 | fixedFrequency = Annual; |
| 104 | fixedDayCount = Thirty360(Thirty360::BondBasis); |
| 105 | |
| 106 | index = ext::shared_ptr<IborIndex>(new Euribor6M(termStructure)); |
| 107 | floatingConvention = index->businessDayConvention(); |
| 108 | floatingTenor = index->tenor(); |
| 109 | calendar = index->fixingCalendar(); |
| 110 | today = calendar.adjust(Date::todaysDate()); |
| 111 | Settings::instance().evaluationDate() = today; |
| 112 | settlement = calendar.advance(today,n: settlementDays,unit: Days); |
| 113 | termStructure.linkTo(h: flatRate(today: settlement,forward: 0.05,dc: Actual365Fixed())); |
| 114 | } |
| 115 | }; |
| 116 | |
| 117 | } |
| 118 | |
| 119 | |
| 120 | void SwaptionTest::testStrikeDependency() { |
| 121 | |
| 122 | BOOST_TEST_MESSAGE("Testing swaption dependency on strike..." ); |
| 123 | |
| 124 | using namespace swaption_test; |
| 125 | |
| 126 | CommonVars vars; |
| 127 | |
| 128 | Rate strikes[] = { 0.03, 0.04, 0.05, 0.06, 0.07 }; |
| 129 | |
| 130 | for (auto& exercise : exercises) { |
| 131 | for (auto& length : lengths) { |
| 132 | for (auto& k : type) { |
| 133 | Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise); |
| 134 | Date startDate = |
| 135 | vars.calendar.advance(exerciseDate, |
| 136 | n: vars.settlementDays,unit: Days); |
| 137 | // store the results for different rates... |
| 138 | std::vector<Real> values; |
| 139 | std::vector<Real> values_cash; |
| 140 | Volatility vol = 0.20; |
| 141 | for (Real strike : strikes) { |
| 142 | ext::shared_ptr<VanillaSwap> swap = |
| 143 | MakeVanillaSwap(length, vars.index, strike) |
| 144 | .withEffectiveDate(startDate) |
| 145 | .withFixedLegTenor(t: 1 * Years) |
| 146 | .withFixedLegDayCount(dc: vars.fixedDayCount) |
| 147 | .withFloatingLegSpread(sp: 0.0) |
| 148 | .withType(type: k); |
| 149 | ext::shared_ptr<Swaption> swaption = |
| 150 | vars.makeSwaption(swap,exercise: exerciseDate,volatility: vol); |
| 151 | values.push_back(x: swaption->NPV()); |
| 152 | ext::shared_ptr<Swaption> swaption_cash = |
| 153 | vars.makeSwaption(swap,exercise: exerciseDate,volatility: vol, |
| 154 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 155 | values_cash.push_back(x: swaption_cash->NPV()); |
| 156 | } |
| 157 | // and check that they go the right way |
| 158 | if (k == Swap::Payer) { |
| 159 | auto it = std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::less<>()); |
| 160 | if (it != values.end()) { |
| 161 | Size n = it - values.begin(); |
| 162 | BOOST_ERROR("NPV of Payer swaption with delivery settlement" |
| 163 | "is increasing with the strike:" |
| 164 | << "\noption tenor: " << exercise << "\noption date: " |
| 165 | << exerciseDate << "\nvolatility: " << io::rate(vol) |
| 166 | << "\nswap tenor: " << length << "\nvalue: " |
| 167 | << values[n] << " at strike: " << io::rate(strikes[n]) |
| 168 | << "\nvalue: " << values[n + 1] |
| 169 | << " at strike: " << io::rate(strikes[n + 1])); |
| 170 | } |
| 171 | auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::less<>()); |
| 172 | if (it_cash != values_cash.end()) { |
| 173 | Size n = it_cash - values_cash.begin(); |
| 174 | BOOST_ERROR("NPV of Payer swaption with cash settlement" |
| 175 | "is increasing with the strike:" |
| 176 | << "\noption tenor: " << exercise << "\noption date: " |
| 177 | << exerciseDate << "\nvolatility: " << io::rate(vol) |
| 178 | << "\nswap tenor: " << length << "\nvalue: " |
| 179 | << values_cash[n] << " at strike: " << io::rate(strikes[n]) |
| 180 | << "\nvalue: " << values_cash[n + 1] |
| 181 | << " at strike: " << io::rate(strikes[n + 1])); |
| 182 | } |
| 183 | } else { |
| 184 | auto it = |
| 185 | std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::greater<>()); |
| 186 | if (it != values.end()) { |
| 187 | Size n = it - values.begin(); |
| 188 | BOOST_ERROR("NPV of Receiver swaption with delivery settlement" |
| 189 | "is increasing with the strike:" |
| 190 | << "\noption tenor: " << exercise << "\noption date: " |
| 191 | << exerciseDate << "\nvolatility: " << io::rate(vol) |
| 192 | << "\nswap tenor: " << length << "\nvalue: " |
| 193 | << values[n] << " at strike: " << io::rate(strikes[n]) |
| 194 | << "\nvalue: " << values[n + 1] |
| 195 | << " at strike: " << io::rate(strikes[n + 1])); |
| 196 | } |
| 197 | auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::greater<>()); |
| 198 | if (it_cash != values_cash.end()) { |
| 199 | Size n = it_cash - values_cash.begin(); |
| 200 | BOOST_ERROR("NPV of Receiver swaption with cash settlement" |
| 201 | "is increasing with the strike:" |
| 202 | << "\noption tenor: " << exercise << "\noption date: " |
| 203 | << exerciseDate << "\nvolatility: " << io::rate(vol) |
| 204 | << "\nswap tenor: " << length << "\nvalue: " |
| 205 | << values_cash[n] << " at strike: " << io::rate(strikes[n]) |
| 206 | << "\nvalue: " << values_cash[n + 1] |
| 207 | << " at strike: " << io::rate(strikes[n + 1])); |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | void SwaptionTest::testSpreadDependency() { |
| 216 | |
| 217 | BOOST_TEST_MESSAGE("Testing swaption dependency on spread..." ); |
| 218 | |
| 219 | using namespace swaption_test; |
| 220 | |
| 221 | CommonVars vars; |
| 222 | |
| 223 | Spread spreads[] = { -0.002, -0.001, 0.0, 0.001, 0.002 }; |
| 224 | |
| 225 | for (auto exercise : exercises) { |
| 226 | for (auto& length : lengths) { |
| 227 | for (auto& k : type) { |
| 228 | Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise); |
| 229 | Date startDate = |
| 230 | vars.calendar.advance(exerciseDate, |
| 231 | n: vars.settlementDays,unit: Days); |
| 232 | // store the results for different rates... |
| 233 | std::vector<Real> values; |
| 234 | std::vector<Real> values_cash; |
| 235 | for (Real spread : spreads) { |
| 236 | ext::shared_ptr<VanillaSwap> swap = |
| 237 | MakeVanillaSwap(length, vars.index, 0.06) |
| 238 | .withFixedLegTenor(t: 1 * Years) |
| 239 | .withFixedLegDayCount(dc: vars.fixedDayCount) |
| 240 | .withEffectiveDate(startDate) |
| 241 | .withFloatingLegSpread(sp: spread) |
| 242 | .withType(type: k); |
| 243 | ext::shared_ptr<Swaption> swaption = |
| 244 | vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20); |
| 245 | values.push_back(x: swaption->NPV()); |
| 246 | ext::shared_ptr<Swaption> swaption_cash = |
| 247 | vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20, |
| 248 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 249 | values_cash.push_back(x: swaption_cash->NPV()); |
| 250 | } |
| 251 | // and check that they go the right way |
| 252 | if (k == Swap::Payer) { |
| 253 | auto it = |
| 254 | std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::greater<>()); |
| 255 | if (it != values.end()) { |
| 256 | Size n = it - values.begin(); |
| 257 | BOOST_ERROR("NPV is decreasing with the spread " |
| 258 | << "in a payer swaption (physical delivered):" |
| 259 | << "\nexercise date: " << exerciseDate << "\nlength: " |
| 260 | << length << "\nvalue: " << values[n] << " for spread: " |
| 261 | << io::rate(spreads[n]) << "\nvalue: " << values[n + 1] |
| 262 | << " for spread: " << io::rate(spreads[n + 1])); |
| 263 | } |
| 264 | auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::greater<>()); |
| 265 | if (it_cash != values_cash.end()) { |
| 266 | Size n = it_cash - values_cash.begin(); |
| 267 | BOOST_ERROR("NPV is decreasing with the spread " |
| 268 | << "in a payer swaption (cash delivered):" |
| 269 | << "\nexercise date: " << exerciseDate << "\nlength: " << length |
| 270 | << "\nvalue: " << values_cash[n] << " for spread: " |
| 271 | << io::rate(spreads[n]) << "\nvalue: " << values_cash[n + 1] |
| 272 | << " for spread: " << io::rate(spreads[n + 1])); |
| 273 | } |
| 274 | } else { |
| 275 | auto it = std::adjacent_find(first: values.begin(), last: values.end(), binary_pred: std::less<>()); |
| 276 | if (it != values.end()) { |
| 277 | Size n = it - values.begin(); |
| 278 | BOOST_ERROR("NPV is increasing with the spread " |
| 279 | << "in a receiver swaption (physical delivered):" |
| 280 | "\nexercise date: " |
| 281 | << exerciseDate << "\nlength: " << length << "\nvalue: " |
| 282 | << values[n] << " for spread: " << io::rate(spreads[n]) |
| 283 | << "\nvalue: " << values[n + 1] |
| 284 | << " for spread: " << io::rate(spreads[n + 1])); |
| 285 | } |
| 286 | auto it_cash = std::adjacent_find(first: values_cash.begin(), last: values_cash.end(), binary_pred: std::less<>()); |
| 287 | if (it_cash != values_cash.end()) { |
| 288 | Size n = it_cash - values_cash.begin(); |
| 289 | BOOST_ERROR("NPV is increasing with the spread " |
| 290 | << "in a receiver swaption (cash delivered):" |
| 291 | "\nexercise date: " |
| 292 | << exerciseDate << "\nlength: " << length << "\nvalue: " |
| 293 | << values_cash[n] << " for spread: " << io::rate(spreads[n]) |
| 294 | << "\nvalue: " << values_cash[n + 1] |
| 295 | << " for spread: " << io::rate(spreads[n + 1])); |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | void SwaptionTest::testSpreadTreatment() { |
| 304 | |
| 305 | BOOST_TEST_MESSAGE("Testing swaption treatment of spread..." ); |
| 306 | |
| 307 | using namespace swaption_test; |
| 308 | |
| 309 | CommonVars vars; |
| 310 | |
| 311 | Spread spreads[] = { -0.002, -0.001, 0.0, 0.001, 0.002 }; |
| 312 | |
| 313 | for (auto exercise : exercises) { |
| 314 | for (auto& length : lengths) { |
| 315 | for (auto& k : type) { |
| 316 | Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise); |
| 317 | Date startDate = |
| 318 | vars.calendar.advance(exerciseDate, |
| 319 | n: vars.settlementDays,unit: Days); |
| 320 | for (Real spread : spreads) { |
| 321 | ext::shared_ptr<VanillaSwap> swap = |
| 322 | MakeVanillaSwap(length, vars.index, 0.06) |
| 323 | .withFixedLegTenor(t: 1 * Years) |
| 324 | .withFixedLegDayCount(dc: vars.fixedDayCount) |
| 325 | .withEffectiveDate(startDate) |
| 326 | .withFloatingLegSpread(sp: spread) |
| 327 | .withType(type: k); |
| 328 | Spread correction = spread * swap->floatingLegBPS() / swap->fixedLegBPS(); |
| 329 | ext::shared_ptr<VanillaSwap> equivalentSwap = |
| 330 | MakeVanillaSwap(length, vars.index, 0.06 + correction) |
| 331 | .withFixedLegTenor(t: 1 * Years) |
| 332 | .withFixedLegDayCount(dc: vars.fixedDayCount) |
| 333 | .withEffectiveDate(startDate) |
| 334 | .withFloatingLegSpread(sp: 0.0) |
| 335 | .withType(type: k); |
| 336 | ext::shared_ptr<Swaption> swaption1 = |
| 337 | vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20); |
| 338 | ext::shared_ptr<Swaption> swaption2 = |
| 339 | vars.makeSwaption(swap: equivalentSwap,exercise: exerciseDate,volatility: 0.20); |
| 340 | ext::shared_ptr<Swaption> swaption1_cash = |
| 341 | vars.makeSwaption(swap,exercise: exerciseDate,volatility: 0.20, |
| 342 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 343 | ext::shared_ptr<Swaption> swaption2_cash = |
| 344 | vars.makeSwaption(swap: equivalentSwap,exercise: exerciseDate,volatility: 0.20, |
| 345 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 346 | if (std::fabs(x: swaption1->NPV()-swaption2->NPV()) > 1.0e-6) |
| 347 | BOOST_ERROR("wrong spread treatment:" |
| 348 | << "\nexercise: " << exerciseDate << "\nlength: " << length |
| 349 | << "\ntype " << k << "\nspread: " << io::rate(spread) |
| 350 | << "\noriginal swaption value: " << swaption1->NPV() |
| 351 | << "\nequivalent swaption value: " << swaption2->NPV()); |
| 352 | |
| 353 | if (std::fabs(x: swaption1_cash->NPV()-swaption2_cash->NPV()) > 1.0e-6) |
| 354 | BOOST_ERROR("wrong spread treatment:" |
| 355 | << "\nexercise date: " << exerciseDate << "\nlength: " << length |
| 356 | << "\npay " << (k ? "fixed" : "floating" ) |
| 357 | << "\nspread: " << io::rate(spread) |
| 358 | << "\nvalue of original swaption: " << swaption1_cash->NPV() |
| 359 | << "\nvalue of equivalent swaption: " << swaption2_cash->NPV()); |
| 360 | } |
| 361 | } |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | void SwaptionTest::testCachedValue() { |
| 367 | |
| 368 | BOOST_TEST_MESSAGE("Testing swaption value against cached value..." ); |
| 369 | |
| 370 | using namespace swaption_test; |
| 371 | |
| 372 | bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons(); |
| 373 | |
| 374 | CommonVars vars; |
| 375 | |
| 376 | vars.today = Date(13, March, 2002); |
| 377 | vars.settlement = Date(15, March, 2002); |
| 378 | Settings::instance().evaluationDate() = vars.today; |
| 379 | vars.termStructure.linkTo(h: flatRate(today: vars.settlement, forward: 0.05, dc: Actual365Fixed())); |
| 380 | Date exerciseDate = vars.calendar.advance(date: vars.settlement, period: 5*Years); |
| 381 | Date startDate = vars.calendar.advance(exerciseDate, |
| 382 | n: vars.settlementDays, unit: Days); |
| 383 | ext::shared_ptr<VanillaSwap> swap = |
| 384 | MakeVanillaSwap(10*Years, vars.index, 0.06) |
| 385 | .withEffectiveDate(startDate) |
| 386 | .withFixedLegTenor(t: 1*Years) |
| 387 | .withFixedLegDayCount(dc: vars.fixedDayCount); |
| 388 | |
| 389 | ext::shared_ptr<Swaption> swaption = |
| 390 | vars.makeSwaption(swap, exercise: exerciseDate, volatility: 0.20); |
| 391 | |
| 392 | Real cachedNPV = usingAtParCoupons ? 0.036418158579 : 0.036421429684; |
| 393 | |
| 394 | if (std::fabs(x: swaption->NPV()-cachedNPV) > 1.0e-12) |
| 395 | BOOST_ERROR("failed to reproduce cached swaption value:\n" << |
| 396 | std::fixed << std::setprecision(12) << |
| 397 | "\ncalculated: " << swaption->NPV() << |
| 398 | "\nexpected: " << cachedNPV); |
| 399 | } |
| 400 | |
| 401 | void SwaptionTest::testVega() { |
| 402 | |
| 403 | BOOST_TEST_MESSAGE("Testing swaption vega..." ); |
| 404 | |
| 405 | using namespace swaption_test; |
| 406 | |
| 407 | CommonVars vars; |
| 408 | |
| 409 | Settlement::Type types[] = { Settlement::Physical, Settlement::Cash }; |
| 410 | Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve }; |
| 411 | Rate strikes[] = { 0.03, 0.04, 0.05, 0.06, 0.07 }; |
| 412 | Volatility vols[] = { 0.01, 0.20, 0.30, 0.70, 0.90 }; |
| 413 | Volatility shift = 1e-8; |
| 414 | for (auto& exercise : exercises) { |
| 415 | Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise); |
| 416 | Date startDate = vars.calendar.advance(date: exerciseDate, |
| 417 | period: vars.settlementDays*Days); |
| 418 | for (auto& length : lengths) { |
| 419 | for (Real strike : strikes) { |
| 420 | for (Size h=0; h<LENGTH(type); h++) { |
| 421 | ext::shared_ptr<VanillaSwap> swap = |
| 422 | MakeVanillaSwap(length, vars.index, strike) |
| 423 | .withEffectiveDate(startDate) |
| 424 | .withFixedLegTenor(t: 1 * Years) |
| 425 | .withFixedLegDayCount(dc: vars.fixedDayCount) |
| 426 | .withFloatingLegSpread(sp: 0.0) |
| 427 | .withType(type: type[h]); |
| 428 | for (Real vol : vols) { |
| 429 | ext::shared_ptr<Swaption> swaption = |
| 430 | vars.makeSwaption(swap, exercise: exerciseDate, volatility: vol, settlementType: types[h], settlementMethod: methods[h]); |
| 431 | ext::shared_ptr<Swaption> swaption1 = vars.makeSwaption( |
| 432 | swap, exercise: exerciseDate, volatility: vol - shift, settlementType: types[h], settlementMethod: methods[h]); |
| 433 | ext::shared_ptr<Swaption> swaption2 = vars.makeSwaption( |
| 434 | swap, exercise: exerciseDate, volatility: vol + shift, settlementType: types[h], settlementMethod: methods[h]); |
| 435 | |
| 436 | Real swaptionNPV = swaption->NPV(); |
| 437 | Real numericalVegaPerPoint = |
| 438 | (swaption2->NPV()-swaption1->NPV())/(200.0*shift); |
| 439 | // check only relevant vega |
| 440 | if (numericalVegaPerPoint/swaptionNPV>1.0e-7) { |
| 441 | Real analyticalVegaPerPoint = |
| 442 | swaption->result<Real>(tag: "vega" )/100.0; |
| 443 | Real discrepancy = std::fabs(x: analyticalVegaPerPoint |
| 444 | - numericalVegaPerPoint); |
| 445 | discrepancy /= numericalVegaPerPoint; |
| 446 | Real tolerance = 0.015; |
| 447 | if (discrepancy > tolerance) |
| 448 | BOOST_FAIL("failed to compute swaption vega:" |
| 449 | << "\n option tenor: " << exercise |
| 450 | << "\n volatility: " << io::rate(vol) |
| 451 | << "\n option type: " << swaption->type() |
| 452 | << "\n swap tenor: " << length |
| 453 | << "\n strike: " << io::rate(strike) |
| 454 | << "\n settlement: " << types[h] |
| 455 | << "\n nominal: " |
| 456 | << swaption->underlyingSwap()->nominal() |
| 457 | << "\n npv: " << swaptionNPV |
| 458 | << "\n calculated vega: " << analyticalVegaPerPoint |
| 459 | << "\n expected vega: " << numericalVegaPerPoint |
| 460 | << "\n discrepancy: " << io::rate(discrepancy) |
| 461 | << "\n tolerance: " << io::rate(tolerance)); |
| 462 | } |
| 463 | } |
| 464 | } |
| 465 | } |
| 466 | } |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | |
| 471 | |
| 472 | void SwaptionTest::testCashSettledSwaptions() { |
| 473 | |
| 474 | BOOST_TEST_MESSAGE("Testing cash settled swaptions modified annuity..." ); |
| 475 | |
| 476 | using namespace swaption_test; |
| 477 | |
| 478 | CommonVars vars; |
| 479 | |
| 480 | Rate strike = 0.05; |
| 481 | |
| 482 | for (auto exercise : exercises) { |
| 483 | for (auto length : lengths) { |
| 484 | |
| 485 | Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise); |
| 486 | Date startDate = vars.calendar.advance(exerciseDate, |
| 487 | n: vars.settlementDays,unit: Days); |
| 488 | Date maturity = vars.calendar.advance(date: startDate, period: length, convention: vars.floatingConvention); |
| 489 | Schedule floatSchedule(startDate, maturity, vars.floatingTenor, |
| 490 | vars.calendar,vars.floatingConvention, |
| 491 | vars.floatingConvention, |
| 492 | DateGeneration::Forward, false); |
| 493 | // Swap with fixed leg conventions: Business Days = Unadjusted, DayCount = 30/360 |
| 494 | Schedule fixedSchedule_u(startDate, maturity, |
| 495 | Period(vars.fixedFrequency), |
| 496 | vars.calendar, Unadjusted, Unadjusted, |
| 497 | DateGeneration::Forward, true); |
| 498 | ext::shared_ptr<VanillaSwap> swap_u360( |
| 499 | new VanillaSwap(type[0], vars.nominal, |
| 500 | fixedSchedule_u,strike,Thirty360(Thirty360::BondBasis), |
| 501 | floatSchedule,vars.index,0.0, |
| 502 | vars.index->dayCounter())); |
| 503 | |
| 504 | // Swap with fixed leg conventions: Business Days = Unadjusted, DayCount = Act/365 |
| 505 | ext::shared_ptr<VanillaSwap> swap_u365( |
| 506 | new VanillaSwap(type[0],vars.nominal, |
| 507 | fixedSchedule_u,strike,Actual365Fixed(), |
| 508 | floatSchedule,vars.index,0.0, |
| 509 | vars.index->dayCounter())); |
| 510 | |
| 511 | // Swap with fixed leg conventions: Business Days = Modified Following, DayCount = 30/360 |
| 512 | Schedule fixedSchedule_a(startDate,maturity, |
| 513 | Period(vars.fixedFrequency), |
| 514 | vars.calendar,ModifiedFollowing, |
| 515 | ModifiedFollowing, |
| 516 | DateGeneration::Forward, true); |
| 517 | ext::shared_ptr<VanillaSwap> swap_a360( |
| 518 | new VanillaSwap(type[0],vars.nominal, |
| 519 | fixedSchedule_a,strike,Thirty360(Thirty360::BondBasis), |
| 520 | floatSchedule,vars.index,0.0, |
| 521 | vars.index->dayCounter())); |
| 522 | |
| 523 | // Swap with fixed leg conventions: Business Days = Modified Following, DayCount = Act/365 |
| 524 | ext::shared_ptr<VanillaSwap> swap_a365( |
| 525 | new VanillaSwap(type[0],vars.nominal, |
| 526 | fixedSchedule_a,strike,Actual365Fixed(), |
| 527 | floatSchedule,vars.index,0.0, |
| 528 | vars.index->dayCounter())); |
| 529 | |
| 530 | ext::shared_ptr<PricingEngine> swapEngine( |
| 531 | new DiscountingSwapEngine(vars.termStructure)); |
| 532 | |
| 533 | swap_u360->setPricingEngine(swapEngine); |
| 534 | swap_a360->setPricingEngine(swapEngine); |
| 535 | swap_u365->setPricingEngine(swapEngine); |
| 536 | swap_a365->setPricingEngine(swapEngine); |
| 537 | |
| 538 | const Leg& swapFixedLeg_u360 = swap_u360->fixedLeg(); |
| 539 | const Leg& swapFixedLeg_a360 = swap_a360->fixedLeg(); |
| 540 | const Leg& swapFixedLeg_u365 = swap_u365->fixedLeg(); |
| 541 | const Leg& swapFixedLeg_a365 = swap_a365->fixedLeg(); |
| 542 | |
| 543 | // FlatForward curves |
| 544 | Handle<YieldTermStructure> termStructure_u360( |
| 545 | ext::shared_ptr<YieldTermStructure>( |
| 546 | new FlatForward(vars.settlement,swap_u360->fairRate(), |
| 547 | Thirty360(Thirty360::BondBasis),Compounded, |
| 548 | vars.fixedFrequency))); |
| 549 | Handle<YieldTermStructure> termStructure_a360( |
| 550 | ext::shared_ptr<YieldTermStructure>( |
| 551 | new FlatForward(vars.settlement,swap_a360->fairRate(), |
| 552 | Thirty360(Thirty360::BondBasis),Compounded, |
| 553 | vars.fixedFrequency))); |
| 554 | Handle<YieldTermStructure> termStructure_u365( |
| 555 | ext::shared_ptr<YieldTermStructure>( |
| 556 | new FlatForward(vars.settlement,swap_u365->fairRate(), |
| 557 | Actual365Fixed(),Compounded, |
| 558 | vars.fixedFrequency))); |
| 559 | Handle<YieldTermStructure> termStructure_a365( |
| 560 | ext::shared_ptr<YieldTermStructure>( |
| 561 | new FlatForward(vars.settlement,swap_a365->fairRate(), |
| 562 | Actual365Fixed(),Compounded, |
| 563 | vars.fixedFrequency))); |
| 564 | |
| 565 | // Annuity calculated by swap method fixedLegBPS(). |
| 566 | // Fixed leg conventions: Unadjusted, 30/360 |
| 567 | Real annuity_u360 = swap_u360->fixedLegBPS() / 0.0001; |
| 568 | annuity_u360 = swap_u360->type()==Swap::Payer ? |
| 569 | -annuity_u360 : annuity_u360; |
| 570 | // Fixed leg conventions: ModifiedFollowing, act/365 |
| 571 | Real annuity_a365 = swap_a365->fixedLegBPS() / 0.0001; |
| 572 | annuity_a365 = swap_a365->type()==Swap::Payer ? |
| 573 | -annuity_a365 : annuity_a365; |
| 574 | // Fixed leg conventions: ModifiedFollowing, 30/360 |
| 575 | Real annuity_a360 = swap_a360->fixedLegBPS() / 0.0001; |
| 576 | annuity_a360 = swap_a360->type()==Swap::Payer ? |
| 577 | -annuity_a360 : annuity_a360; |
| 578 | // Fixed leg conventions: Unadjusted, act/365 |
| 579 | Real annuity_u365 = swap_u365->fixedLegBPS() / 0.0001; |
| 580 | annuity_u365 = swap_u365->type()==Swap::Payer ? |
| 581 | -annuity_u365 : annuity_u365; |
| 582 | |
| 583 | // Calculation of Modified Annuity (cash settlement) |
| 584 | // Fixed leg conventions of swap: unadjusted, 30/360 |
| 585 | Real cashannuity_u360 = 0.; |
| 586 | Size i; |
| 587 | for (i=0; i<swapFixedLeg_u360.size(); i++) { |
| 588 | cashannuity_u360 += swapFixedLeg_u360[i]->amount()/strike |
| 589 | * termStructure_u360->discount( |
| 590 | d: swapFixedLeg_u360[i]->date()); |
| 591 | } |
| 592 | // Fixed leg conventions of swap: unadjusted, act/365 |
| 593 | Real cashannuity_u365 = 0.; |
| 594 | for (i=0; i<swapFixedLeg_u365.size(); i++) { |
| 595 | cashannuity_u365 += swapFixedLeg_u365[i]->amount()/strike |
| 596 | * termStructure_u365->discount( |
| 597 | d: swapFixedLeg_u365[i]->date()); |
| 598 | } |
| 599 | // Fixed leg conventions of swap: modified following, 30/360 |
| 600 | Real cashannuity_a360 = 0.; |
| 601 | for (i=0; i<swapFixedLeg_a360.size(); i++) { |
| 602 | cashannuity_a360 += swapFixedLeg_a360[i]->amount()/strike |
| 603 | * termStructure_a360->discount( |
| 604 | d: swapFixedLeg_a360[i]->date()); |
| 605 | } |
| 606 | // Fixed leg conventions of swap: modified following, act/365 |
| 607 | Real cashannuity_a365 = 0.; |
| 608 | for (i=0; i<swapFixedLeg_a365.size(); i++) { |
| 609 | cashannuity_a365 += swapFixedLeg_a365[i]->amount()/strike |
| 610 | * termStructure_a365->discount( |
| 611 | d: swapFixedLeg_a365[i]->date()); |
| 612 | } |
| 613 | |
| 614 | // Swaptions: underlying swap fixed leg conventions: |
| 615 | // unadjusted, 30/360 |
| 616 | |
| 617 | // Physical settled swaption |
| 618 | ext::shared_ptr<Swaption> swaption_p_u360 = |
| 619 | vars.makeSwaption(swap: swap_u360,exercise: exerciseDate,volatility: 0.20); |
| 620 | Real value_p_u360 = swaption_p_u360->NPV(); |
| 621 | // Cash settled swaption |
| 622 | ext::shared_ptr<Swaption> swaption_c_u360 = |
| 623 | vars.makeSwaption(swap: swap_u360,exercise: exerciseDate,volatility: 0.20, |
| 624 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 625 | Real value_c_u360 = swaption_c_u360->NPV(); |
| 626 | // the NPV's ratio must be equal to annuities ratio |
| 627 | Real npv_ratio_u360 = value_c_u360 / value_p_u360; |
| 628 | Real annuity_ratio_u360 = cashannuity_u360 / annuity_u360; |
| 629 | |
| 630 | // Swaptions: underlying swap fixed leg conventions: |
| 631 | // modified following, act/365 |
| 632 | |
| 633 | // Physical settled swaption |
| 634 | ext::shared_ptr<Swaption> swaption_p_a365 = |
| 635 | vars.makeSwaption(swap: swap_a365,exercise: exerciseDate,volatility: 0.20); |
| 636 | Real value_p_a365 = swaption_p_a365->NPV(); |
| 637 | // Cash settled swaption |
| 638 | ext::shared_ptr<Swaption> swaption_c_a365 = |
| 639 | vars.makeSwaption(swap: swap_a365,exercise: exerciseDate,volatility: 0.20, |
| 640 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 641 | Real value_c_a365 = swaption_c_a365->NPV(); |
| 642 | // the NPV's ratio must be equal to annuities ratio |
| 643 | Real npv_ratio_a365 = value_c_a365 / value_p_a365; |
| 644 | Real annuity_ratio_a365 = cashannuity_a365 / annuity_a365; |
| 645 | |
| 646 | // Swaptions: underlying swap fixed leg conventions: |
| 647 | // modified following, 30/360 |
| 648 | |
| 649 | // Physical settled swaption |
| 650 | ext::shared_ptr<Swaption> swaption_p_a360 = |
| 651 | vars.makeSwaption(swap: swap_a360,exercise: exerciseDate,volatility: 0.20); |
| 652 | Real value_p_a360 = swaption_p_a360->NPV(); |
| 653 | // Cash settled swaption |
| 654 | ext::shared_ptr<Swaption> swaption_c_a360 = |
| 655 | vars.makeSwaption(swap: swap_a360,exercise: exerciseDate,volatility: 0.20, |
| 656 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 657 | Real value_c_a360 = swaption_c_a360->NPV(); |
| 658 | // the NPV's ratio must be equal to annuities ratio |
| 659 | Real npv_ratio_a360 = value_c_a360 / value_p_a360; |
| 660 | Real annuity_ratio_a360 = cashannuity_a360 / annuity_a360; |
| 661 | |
| 662 | // Swaptions: underlying swap fixed leg conventions: |
| 663 | // unadjusted, act/365 |
| 664 | |
| 665 | // Physical settled swaption |
| 666 | ext::shared_ptr<Swaption> swaption_p_u365 = |
| 667 | vars.makeSwaption(swap: swap_u365,exercise: exerciseDate,volatility: 0.20); |
| 668 | Real value_p_u365 = swaption_p_u365->NPV(); |
| 669 | // Cash settled swaption |
| 670 | ext::shared_ptr<Swaption> swaption_c_u365 = |
| 671 | vars.makeSwaption(swap: swap_u365,exercise: exerciseDate,volatility: 0.20, |
| 672 | settlementType: Settlement::Cash, settlementMethod: Settlement::ParYieldCurve); |
| 673 | Real value_c_u365 = swaption_c_u365->NPV(); |
| 674 | // the NPV's ratio must be equal to annuities ratio |
| 675 | Real npv_ratio_u365 = value_c_u365 / value_p_u365; |
| 676 | Real annuity_ratio_u365 = cashannuity_u365 / annuity_u365; |
| 677 | |
| 678 | if (std::fabs(x: annuity_ratio_u360-npv_ratio_u360)>1e-10 ) { |
| 679 | BOOST_ERROR("\n" |
| 680 | << " The npv's ratio must be equal to " |
| 681 | << " annuities ratio" |
| 682 | << "\n" |
| 683 | " Swaption " |
| 684 | << exercises[i].units() << "y x " << length.units() << "y" |
| 685 | << " (underlying swap fixed leg Unadjusted, 30/360)" |
| 686 | << "\n" |
| 687 | << " Today : " << vars.today << "\n" |
| 688 | << " Settlement date : " << vars.settlement << "\n" |
| 689 | << " Exercise date : " << exerciseDate << "\n" |
| 690 | << " Swap start date : " << startDate << "\n" |
| 691 | << " Swap end date : " << maturity << "\n" |
| 692 | << " physical delivered swaption npv : " << value_p_u360 << "\t\t\t" |
| 693 | << " annuity : " << annuity_u360 << "\n" |
| 694 | << " cash delivered swaption npv : " << value_c_u360 << "\t\t\t" |
| 695 | << " annuity : " << cashannuity_u360 << "\n" |
| 696 | << " npv ratio : " << npv_ratio_u360 << "\n" |
| 697 | << " annuity ratio : " << annuity_ratio_u360 << "\n" |
| 698 | << " difference : " << (annuity_ratio_u360 - npv_ratio_u360)); |
| 699 | } |
| 700 | if (std::fabs(x: annuity_ratio_a365-npv_ratio_a365)>1e-10) { |
| 701 | BOOST_ERROR("\n" |
| 702 | << " The npv's ratio must be equal to " |
| 703 | << " annuities ratio" |
| 704 | << "\n" |
| 705 | " Swaption " |
| 706 | << exercises[i].units() << "y x " << length.units() << "y" |
| 707 | << " (underlying swap fixed leg Modified Following, act/365" |
| 708 | << "\n" |
| 709 | << " Today : " << vars.today << "\n" |
| 710 | << " Settlement date : " << vars.settlement << "\n" |
| 711 | << " Exercise date : " << exerciseDate << "\n" |
| 712 | << " Swap start date : " << startDate << "\n" |
| 713 | << " Swap end date : " << maturity << "\n" |
| 714 | << " physical delivered swaption npv : " << value_p_a365 << "\t\t\t" |
| 715 | << " annuity : " << annuity_a365 << "\n" |
| 716 | << " cash delivered swaption npv : " << value_c_a365 << "\t\t\t" |
| 717 | << " annuity : " << cashannuity_a365 << "\n" |
| 718 | << " npv ratio : " << npv_ratio_a365 << "\n" |
| 719 | << " annuity ratio : " << annuity_ratio_a365 << "\n" |
| 720 | << " difference : " << (annuity_ratio_a365 - npv_ratio_a365)); |
| 721 | } |
| 722 | if (std::fabs(x: annuity_ratio_a360-npv_ratio_a360)>1e-10) { |
| 723 | BOOST_ERROR("\n" |
| 724 | << " The npv's ratio must be equal to " |
| 725 | << " annuities ratio" |
| 726 | << "\n" |
| 727 | " Swaption " |
| 728 | << exercises[i].units() << "y x " << length.units() << "y" |
| 729 | << " (underlying swap fixed leg Unadjusted, 30/360)" |
| 730 | << "\n" |
| 731 | << " Today : " << vars.today << "\n" |
| 732 | << " Settlement date : " << vars.settlement << "\n" |
| 733 | << " Exercise date : " << exerciseDate << "\n" |
| 734 | << " Swap start date : " << startDate << "\n" |
| 735 | << " Swap end date : " << maturity << "\n" |
| 736 | << " physical delivered swaption npv : " << value_p_a360 << "\t\t\t" |
| 737 | << " annuity : " << annuity_a360 << "\n" |
| 738 | << " cash delivered swaption npv : " << value_c_a360 << "\t\t\t" |
| 739 | << " annuity : " << cashannuity_a360 << "\n" |
| 740 | << " npv ratio : " << npv_ratio_a360 << "\n" |
| 741 | << " annuity ratio : " << annuity_ratio_a360 << "\n" |
| 742 | << " difference : " << (annuity_ratio_a360 - npv_ratio_a360)); |
| 743 | } |
| 744 | if (std::fabs(x: annuity_ratio_u365-npv_ratio_u365)>1e-10) { |
| 745 | BOOST_ERROR("\n" |
| 746 | << " The npv's ratio must be equal to " |
| 747 | << " annuities ratio" |
| 748 | << "\n" |
| 749 | " Swaption " |
| 750 | << exercises[i].units() << "y x " << length.units() << "y" |
| 751 | << " (underlying swap fixed leg Unadjusted, act/365)" |
| 752 | << "\n" |
| 753 | << " Today : " << vars.today << "\n" |
| 754 | << " Settlement date : " << vars.settlement << "\n" |
| 755 | << " Exercise date : " << exerciseDate << "\n" |
| 756 | << " Swap start date : " << startDate << "\n" |
| 757 | << " Swap end date : " << maturity << "\n" |
| 758 | << " physical delivered swaption npv : " << value_p_u365 << "\t\t\t" |
| 759 | << " annuity : " << annuity_u365 << "\n" |
| 760 | << " cash delivered swaption npv : " << value_c_u365 << "\t\t\t" |
| 761 | << " annuity : " << cashannuity_u365 << "\n" |
| 762 | << " npv ratio : " << npv_ratio_u365 << "\n" |
| 763 | << " annuity ratio : " << annuity_ratio_u365 << "\n" |
| 764 | << " difference : " << (annuity_ratio_u365 - npv_ratio_u365)); |
| 765 | } |
| 766 | } |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | |
| 771 | |
| 772 | void SwaptionTest::testImpliedVolatility() { |
| 773 | |
| 774 | BOOST_TEST_MESSAGE("Testing implied volatility for swaptions..." ); |
| 775 | |
| 776 | using namespace swaption_test; |
| 777 | |
| 778 | CommonVars vars; |
| 779 | |
| 780 | Size maxEvaluations = 100; |
| 781 | Real tolerance = 1.0e-08; |
| 782 | |
| 783 | Settlement::Type types[] = { Settlement::Physical, Settlement::Cash }; |
| 784 | Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve }; |
| 785 | // test data |
| 786 | Rate strikes[] = { 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 }; |
| 787 | Volatility vols[] = { 0.01, 0.05, 0.10, 0.20, 0.30, 0.70, 0.90 }; |
| 788 | |
| 789 | for (auto& exercise : exercises) { |
| 790 | for (auto& length : lengths) { |
| 791 | Date exerciseDate = vars.calendar.advance(date: vars.today, period: exercise); |
| 792 | Date startDate = vars.calendar.advance(exerciseDate, |
| 793 | n: vars.settlementDays, unit: Days); |
| 794 | |
| 795 | for (Real& strike : strikes) { |
| 796 | for (auto& k : type) { |
| 797 | ext::shared_ptr<VanillaSwap> swap = |
| 798 | MakeVanillaSwap(length, vars.index, strike) |
| 799 | .withEffectiveDate(startDate) |
| 800 | .withFixedLegTenor(t: 1 * Years) |
| 801 | .withFixedLegDayCount(dc: vars.fixedDayCount) |
| 802 | .withFloatingLegSpread(sp: 0.0) |
| 803 | .withType(type: k); |
| 804 | for (Size h=0; h<LENGTH(types); h++) { |
| 805 | for (Real vol : vols) { |
| 806 | ext::shared_ptr<Swaption> swaption = |
| 807 | vars.makeSwaption(swap, exercise: exerciseDate, volatility: vol, settlementType: types[h], settlementMethod: methods[h], |
| 808 | model: BlackSwaptionEngine::DiscountCurve); |
| 809 | // Black price |
| 810 | Real value = swaption->NPV(); |
| 811 | Volatility implVol = 0.0; |
| 812 | try { |
| 813 | implVol = |
| 814 | swaption->impliedVolatility(price: value, |
| 815 | discountCurve: vars.termStructure, |
| 816 | guess: 0.10, |
| 817 | accuracy: tolerance, |
| 818 | maxEvaluations, |
| 819 | minVol: 1.0e-7, |
| 820 | maxVol: 4.0, |
| 821 | type: ShiftedLognormal, |
| 822 | displacement: 0.0); |
| 823 | } catch (std::exception& e) { |
| 824 | // couldn't bracket? |
| 825 | swaption->setPricingEngine(vars.makeEngine(volatility: 0.0, model: BlackSwaptionEngine::DiscountCurve)); |
| 826 | Real value2 = swaption->NPV(); |
| 827 | if (std::fabs(x: value-value2) < tolerance) { |
| 828 | // ok, just skip: |
| 829 | continue; |
| 830 | } |
| 831 | // otherwise, report error |
| 832 | BOOST_ERROR("implied vol failure: " |
| 833 | << exercise << "x" << length << " " << k |
| 834 | << "\nsettlement: " << types[h] << "\nstrike " |
| 835 | << strike |
| 836 | << "\natm level: " << io::rate(swap->fairRate()) |
| 837 | << "\nvol: " << io::volatility(vol) |
| 838 | << "\nprice: " << value << "\n" |
| 839 | << e.what()); |
| 840 | } |
| 841 | if (std::fabs(x: implVol - vol) > tolerance) { |
| 842 | // the difference might not matter |
| 843 | swaption->setPricingEngine(vars.makeEngine(volatility: implVol, model: BlackSwaptionEngine::DiscountCurve)); |
| 844 | Real value2 = swaption->NPV(); |
| 845 | if (std::fabs(x: value-value2) > tolerance) { |
| 846 | BOOST_ERROR("implied vol failure: " |
| 847 | << exercise << "x" << length << " " << k |
| 848 | << "\nsettlement: " << types[h] |
| 849 | << "\nstrike " << strike |
| 850 | << "\natm level: " << io::rate(swap->fairRate()) |
| 851 | << "\nvol: " << io::volatility(vol) |
| 852 | << "\nprice: " << value |
| 853 | << "\nimplied vol: " << io::volatility(implVol) |
| 854 | << "\nimplied price: " << value2); |
| 855 | } |
| 856 | } |
| 857 | } |
| 858 | } |
| 859 | } |
| 860 | } |
| 861 | } |
| 862 | } |
| 863 | } |
| 864 | |
| 865 | template <typename Engine> |
| 866 | ext::shared_ptr<Engine> makeConstVolEngine( |
| 867 | const Handle<YieldTermStructure> &discountCurve, |
| 868 | Volatility volatility) |
| 869 | { |
| 870 | Handle<Quote> h(ext::make_shared<SimpleQuote>(args&: volatility)); |
| 871 | return ext::make_shared<Engine>(discountCurve, h); |
| 872 | } |
| 873 | |
| 874 | template <typename Engine> |
| 875 | void checkSwaptionDelta(bool useBachelierVol) |
| 876 | { |
| 877 | using namespace swaption_test; |
| 878 | |
| 879 | CommonVars vars; |
| 880 | Date today = vars.today; |
| 881 | Calendar calendar = vars.calendar; |
| 882 | |
| 883 | const Real bump = 1.e-4; |
| 884 | const Real epsilon = 1.e-10; |
| 885 | |
| 886 | RelinkableHandle<YieldTermStructure> projectionCurveHandle; |
| 887 | |
| 888 | const Real projectionRate = 0.01; |
| 889 | RelinkableHandle<Quote> projectionQuoteHandle; |
| 890 | |
| 891 | ext::shared_ptr<YieldTermStructure> projectionCurve = ext::make_shared<FlatForward>( |
| 892 | args&: today, args&: projectionQuoteHandle, args: Actual365Fixed()); |
| 893 | projectionCurveHandle.linkTo(h: projectionCurve); |
| 894 | |
| 895 | Handle<YieldTermStructure> discountHandle(ext::make_shared<FlatForward>( |
| 896 | args&: today, |
| 897 | args: Handle<Quote>(ext::make_shared<SimpleQuote>(args: 0.0085)), |
| 898 | args: Actual365Fixed())); |
| 899 | ext::shared_ptr<DiscountingSwapEngine> swapEngine = ext::make_shared<DiscountingSwapEngine>( |
| 900 | args&: discountHandle); |
| 901 | |
| 902 | ext::shared_ptr<IborIndex> idx = ext::make_shared<Euribor6M>(args&: projectionCurveHandle); |
| 903 | |
| 904 | Settlement::Type types[] = { Settlement::Physical, Settlement::Cash }; |
| 905 | Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::CollateralizedCashPrice}; |
| 906 | |
| 907 | Rate strikes[] = { 0.03, 0.04, 0.05, 0.06, 0.07 }; |
| 908 | Volatility vols[] = { 0.0, 0.10, 0.20, 0.30, 0.70, 0.90 }; |
| 909 | |
| 910 | for (Real vol : vols) { |
| 911 | for (auto exercise : exercises) { |
| 912 | for (auto& length : lengths) { |
| 913 | for (Real& strike : strikes) { |
| 914 | for (Size h=0; h<LENGTH(type); h++) { |
| 915 | Volatility volatility = useBachelierVol ? vol / 100.0 : vol; |
| 916 | ext::shared_ptr<Engine> swaptionEngine = makeConstVolEngine<Engine>( |
| 917 | discountHandle, volatility); |
| 918 | |
| 919 | Date exerciseDate = calendar.advance(date: today, period: exercise); |
| 920 | Date startDate = calendar.advance(date: exerciseDate, period: 2*Days); |
| 921 | projectionQuoteHandle.linkTo(h: ext::make_shared<SimpleQuote>(args: projectionRate)); |
| 922 | |
| 923 | ext::shared_ptr<VanillaSwap> underlying = |
| 924 | MakeVanillaSwap(length, idx, strike) |
| 925 | .withEffectiveDate(startDate) |
| 926 | .withFixedLegTenor(t: 1 * Years) |
| 927 | .withFixedLegDayCount(dc: Thirty360(Thirty360::BondBasis)) |
| 928 | .withFloatingLegSpread(sp: 0.0) |
| 929 | .withType(type: type[h]); |
| 930 | underlying->setPricingEngine(swapEngine); |
| 931 | |
| 932 | Real fairRate = underlying->fairRate(); |
| 933 | |
| 934 | ext::shared_ptr<Swaption> swaption = ext::make_shared<Swaption>( |
| 935 | args&: underlying, |
| 936 | args: ext::make_shared<EuropeanExercise>(args&: exerciseDate), |
| 937 | args&: types[h], |
| 938 | args&: methods[h]); |
| 939 | swaption->setPricingEngine(swaptionEngine); |
| 940 | |
| 941 | Real value = swaption->NPV(); |
| 942 | Real delta = swaption->result<Real>(tag: "delta" ) * bump; |
| 943 | |
| 944 | projectionQuoteHandle.linkTo(h: ext::make_shared<SimpleQuote>( |
| 945 | args: projectionRate + bump)); |
| 946 | |
| 947 | Real bumpedFairRate = underlying->fairRate(); |
| 948 | Real bumpedValue = swaption->NPV(); |
| 949 | Real bumpedDelta = swaption->result<Real>(tag: "delta" ) * bump; |
| 950 | |
| 951 | Real deltaBump = bumpedFairRate - fairRate; |
| 952 | Real approxDelta = (bumpedValue - value) / deltaBump * bump; |
| 953 | |
| 954 | Real lowerBound = std::min(a: delta, b: bumpedDelta) - epsilon; |
| 955 | Real upperBound = std::max(a: delta, b: bumpedDelta) + epsilon; |
| 956 | |
| 957 | /*! Based on the Mean Value Theorem, the below inequality |
| 958 | should hold for any function that is monotonic in the |
| 959 | area of the bump. |
| 960 | */ |
| 961 | bool checkIsCorrect = (lowerBound < approxDelta) && (approxDelta < upperBound); |
| 962 | |
| 963 | if (!checkIsCorrect) |
| 964 | BOOST_FAIL( |
| 965 | "failed to compute swaption delta:" |
| 966 | << "\n option tenor: " << exerciseDate |
| 967 | << "\n volatility: " << io::rate(volatility) |
| 968 | << "\n option type: " << swaption->type() |
| 969 | << "\n swap tenor: " << length << "\n strike: " |
| 970 | << strike << "\n settlement: " << types[h] |
| 971 | << "\n method: " << methods[h] |
| 972 | << "\n nominal: " << swaption->underlyingSwap()->nominal() |
| 973 | << "\n npv: " << value << "\n calculated delta: " |
| 974 | << delta << "\n expected delta: " << approxDelta); |
| 975 | } |
| 976 | } |
| 977 | } |
| 978 | } |
| 979 | } |
| 980 | } |
| 981 | |
| 982 | void SwaptionTest::testSwaptionDeltaInBlackModel() { |
| 983 | |
| 984 | BOOST_TEST_MESSAGE("Testing swaption delta in Black model..." ); |
| 985 | |
| 986 | checkSwaptionDelta<BlackSwaptionEngine>(useBachelierVol: false); |
| 987 | } |
| 988 | |
| 989 | void SwaptionTest::testSwaptionDeltaInBachelierModel() { |
| 990 | |
| 991 | BOOST_TEST_MESSAGE("Testing swaption delta in Bachelier model..." ); |
| 992 | |
| 993 | checkSwaptionDelta<BachelierSwaptionEngine>(useBachelierVol: true); |
| 994 | } |
| 995 | |
| 996 | test_suite* SwaptionTest::suite(SpeedLevel speed) { |
| 997 | auto* suite = BOOST_TEST_SUITE("Swaption tests" ); |
| 998 | |
| 999 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testCashSettledSwaptions)); |
| 1000 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testStrikeDependency)); |
| 1001 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSpreadDependency)); |
| 1002 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSpreadTreatment)); |
| 1003 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testCachedValue)); |
| 1004 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testVega)); |
| 1005 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSwaptionDeltaInBlackModel)); |
| 1006 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testSwaptionDeltaInBachelierModel)); |
| 1007 | |
| 1008 | if (speed <= Fast) { |
| 1009 | suite->add(QUANTLIB_TEST_CASE(&SwaptionTest::testImpliedVolatility)); |
| 1010 | }; |
| 1011 | |
| 1012 | return suite; |
| 1013 | } |
| 1014 | |