| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2008, 2009 Jose Aparicio |
| 5 | Copyright (C) 2008 Roland Lichters |
| 6 | Copyright (C) 2008 StatPro Italia srl |
| 7 | |
| 8 | This file is part of QuantLib, a free-software/open-source library |
| 9 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 10 | |
| 11 | QuantLib is free software: you can redistribute it and/or modify it |
| 12 | under the terms of the QuantLib license. You should have received a |
| 13 | copy of the license along with this program; if not, please email |
| 14 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 15 | <http://quantlib.org/license.shtml>. |
| 16 | |
| 17 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 19 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 20 | */ |
| 21 | |
| 22 | #include <ql/cashflows/fixedratecoupon.hpp> |
| 23 | #include <ql/cashflows/simplecashflow.hpp> |
| 24 | #include <ql/instruments/claim.hpp> |
| 25 | #include <ql/instruments/creditdefaultswap.hpp> |
| 26 | #include <ql/math/solvers1d/brent.hpp> |
| 27 | #include <ql/pricingengines/credit/isdacdsengine.hpp> |
| 28 | #include <ql/pricingengines/credit/midpointcdsengine.hpp> |
| 29 | #include <ql/quotes/simplequote.hpp> |
| 30 | #include <ql/termstructures/credit/flathazardrate.hpp> |
| 31 | #include <ql/termstructures/yieldtermstructure.hpp> |
| 32 | #include <ql/time/calendars/weekendsonly.hpp> |
| 33 | #include <ql/time/schedule.hpp> |
| 34 | #include <ql/optional.hpp> |
| 35 | #include <utility> |
| 36 | |
| 37 | namespace QuantLib { |
| 38 | |
| 39 | CreditDefaultSwap::CreditDefaultSwap(Protection::Side side, |
| 40 | Real notional, |
| 41 | Rate spread, |
| 42 | const Schedule& schedule, |
| 43 | BusinessDayConvention convention, |
| 44 | const DayCounter& dayCounter, |
| 45 | bool settlesAccrual, |
| 46 | bool paysAtDefaultTime, |
| 47 | const Date& protectionStart, |
| 48 | ext::shared_ptr<Claim> claim, |
| 49 | const DayCounter& lastPeriodDayCounter, |
| 50 | const bool rebatesAccrual, |
| 51 | const Date& tradeDate, |
| 52 | Natural cashSettlementDays) |
| 53 | : side_(side), notional_(notional), upfront_(ext::nullopt), runningSpread_(spread), |
| 54 | settlesAccrual_(settlesAccrual), paysAtDefaultTime_(paysAtDefaultTime), |
| 55 | claim_(std::move(claim)), |
| 56 | protectionStart_(protectionStart == Null<Date>() ? schedule[0] : protectionStart), |
| 57 | tradeDate_(tradeDate), cashSettlementDays_(cashSettlementDays) { |
| 58 | |
| 59 | init(schedule, paymentConvention: convention, dayCounter, lastPeriodDayCounter, rebatesAccrual); |
| 60 | } |
| 61 | |
| 62 | CreditDefaultSwap::CreditDefaultSwap(Protection::Side side, |
| 63 | Real notional, |
| 64 | Rate upfront, |
| 65 | Rate runningSpread, |
| 66 | const Schedule& schedule, |
| 67 | BusinessDayConvention convention, |
| 68 | const DayCounter& dayCounter, |
| 69 | bool settlesAccrual, |
| 70 | bool paysAtDefaultTime, |
| 71 | const Date& protectionStart, |
| 72 | const Date& upfrontDate, |
| 73 | ext::shared_ptr<Claim> claim, |
| 74 | const DayCounter& lastPeriodDayCounter, |
| 75 | const bool rebatesAccrual, |
| 76 | const Date& tradeDate, |
| 77 | Natural cashSettlementDays) |
| 78 | : side_(side), notional_(notional), upfront_(upfront), runningSpread_(runningSpread), |
| 79 | settlesAccrual_(settlesAccrual), paysAtDefaultTime_(paysAtDefaultTime), |
| 80 | claim_(std::move(claim)), |
| 81 | protectionStart_(protectionStart == Null<Date>() ? schedule[0] : protectionStart), |
| 82 | tradeDate_(tradeDate), cashSettlementDays_(cashSettlementDays) { |
| 83 | |
| 84 | init(schedule, paymentConvention: convention, dayCounter, lastPeriodDayCounter, rebatesAccrual, upfrontDate); |
| 85 | } |
| 86 | |
| 87 | void CreditDefaultSwap::init(const Schedule& schedule, BusinessDayConvention paymentConvention, |
| 88 | const DayCounter& dayCounter, const DayCounter& lastPeriodDayCounter, |
| 89 | bool rebatesAccrual, const Date& upfrontDate) { |
| 90 | |
| 91 | QL_REQUIRE(!schedule.empty(), "CreditDefaultSwap needs a non-empty schedule." ); |
| 92 | |
| 93 | bool postBigBang = false; |
| 94 | if (schedule.hasRule()) { |
| 95 | DateGeneration::Rule rule = schedule.rule(); |
| 96 | postBigBang = rule == DateGeneration::CDS || rule == DateGeneration::CDS2015; |
| 97 | } |
| 98 | |
| 99 | if (!postBigBang) { |
| 100 | QL_REQUIRE(protectionStart_ <= schedule[0], "protection can not start after accrual" ); |
| 101 | } |
| 102 | |
| 103 | leg_ = FixedRateLeg(schedule) |
| 104 | .withNotionals(notional_) |
| 105 | .withCouponRates(runningSpread_, paymentDayCounter: dayCounter) |
| 106 | .withPaymentAdjustment(paymentConvention) |
| 107 | .withLastPeriodDayCounter(lastPeriodDayCounter); |
| 108 | |
| 109 | // Deduce the trade date if not given. |
| 110 | if (tradeDate_ == Date()) { |
| 111 | if (postBigBang) { |
| 112 | tradeDate_ = protectionStart_; |
| 113 | } else { |
| 114 | tradeDate_ = protectionStart_ - 1; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | // Deduce the cash settlement date if not given. |
| 119 | Date effectiveUpfrontDate = upfrontDate; |
| 120 | if (effectiveUpfrontDate == Date()) { |
| 121 | effectiveUpfrontDate = schedule.calendar().advance(tradeDate_, |
| 122 | n: cashSettlementDays_, unit: Days, convention: paymentConvention); |
| 123 | } |
| 124 | QL_REQUIRE(effectiveUpfrontDate >= protectionStart_, |
| 125 | "The cash settlement date must not be before the protection start date." ); |
| 126 | |
| 127 | // Create the upfront payment, if one is provided. |
| 128 | Real upfrontAmount = 0.0; |
| 129 | if (upfront_) // NOLINT(readability-implicit-bool-conversion) |
| 130 | upfrontAmount = *upfront_ * notional_; |
| 131 | upfrontPayment_ = ext::make_shared<SimpleCashFlow>(args&: upfrontAmount, args&: effectiveUpfrontDate); |
| 132 | |
| 133 | // Set the maturity date. |
| 134 | maturity_ = schedule.dates().back(); |
| 135 | |
| 136 | // Deal with the accrual rebate. We use the standard conventions for accrual calculation introduced with the |
| 137 | // CDS Big Bang in 2009. |
| 138 | if (rebatesAccrual) { |
| 139 | |
| 140 | Real rebateAmount = 0.0; |
| 141 | Date refDate = tradeDate_ + 1; |
| 142 | |
| 143 | if (tradeDate_ >= schedule.dates().front()) { |
| 144 | for (Size i = 0; i < leg_.size(); ++i) { |
| 145 | const ext::shared_ptr<CashFlow>& cf = leg_[i]; |
| 146 | if (refDate > cf->date()) { |
| 147 | // This coupon is in the past; check the next one |
| 148 | continue; |
| 149 | } else if (refDate == cf->date()) { |
| 150 | // This coupon pays at the reference date. |
| 151 | // If it's not the last coupon, the accrual is 0 so do nothing. |
| 152 | if (i < leg_.size() - 1) |
| 153 | rebateAmount = 0.0; |
| 154 | else { |
| 155 | // On last coupon |
| 156 | ext::shared_ptr<FixedRateCoupon> frc = ext::dynamic_pointer_cast<FixedRateCoupon>(r: cf); |
| 157 | rebateAmount = frc->amount(); |
| 158 | } |
| 159 | break; |
| 160 | } else { |
| 161 | // This coupon pays in the future, and is the first coupon to do so (since they're sorted). |
| 162 | // Calculate the accrual and skip further coupons |
| 163 | ext::shared_ptr<FixedRateCoupon> frc = ext::dynamic_pointer_cast<FixedRateCoupon>(r: cf); |
| 164 | rebateAmount = frc->accruedAmount(refDate); |
| 165 | break; |
| 166 | } |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | accrualRebate_ = ext::make_shared<SimpleCashFlow>(args&: rebateAmount, args&: effectiveUpfrontDate); |
| 171 | } |
| 172 | |
| 173 | if (!claim_) |
| 174 | claim_ = ext::make_shared<FaceValueClaim>(); |
| 175 | registerWith(h: claim_); |
| 176 | } |
| 177 | |
| 178 | Protection::Side CreditDefaultSwap::side() const { |
| 179 | return side_; |
| 180 | } |
| 181 | |
| 182 | Real CreditDefaultSwap::notional() const { |
| 183 | return notional_; |
| 184 | } |
| 185 | |
| 186 | Rate CreditDefaultSwap::runningSpread() const { |
| 187 | return runningSpread_; |
| 188 | } |
| 189 | |
| 190 | ext::optional<Rate> CreditDefaultSwap::upfront() const { |
| 191 | return upfront_; |
| 192 | } |
| 193 | |
| 194 | bool CreditDefaultSwap::settlesAccrual() const { |
| 195 | return settlesAccrual_; |
| 196 | } |
| 197 | |
| 198 | bool CreditDefaultSwap::paysAtDefaultTime() const { |
| 199 | return paysAtDefaultTime_; |
| 200 | } |
| 201 | |
| 202 | const Leg& CreditDefaultSwap::coupons() const { |
| 203 | return leg_; |
| 204 | } |
| 205 | |
| 206 | |
| 207 | bool CreditDefaultSwap::isExpired() const { |
| 208 | for (auto i = leg_.rbegin(); i != leg_.rend(); ++i) { |
| 209 | if (!(*i)->hasOccurred()) |
| 210 | return false; |
| 211 | } |
| 212 | return true; |
| 213 | } |
| 214 | |
| 215 | void CreditDefaultSwap::setupExpired() const { |
| 216 | Instrument::setupExpired(); |
| 217 | fairSpread_ = fairUpfront_ = 0.0; |
| 218 | couponLegBPS_ = upfrontBPS_ = 0.0; |
| 219 | couponLegNPV_ = defaultLegNPV_ = upfrontNPV_ = 0.0; |
| 220 | } |
| 221 | |
| 222 | void CreditDefaultSwap::setupArguments( |
| 223 | PricingEngine::arguments* args) const { |
| 224 | auto* arguments = dynamic_cast<CreditDefaultSwap::arguments*>(args); |
| 225 | QL_REQUIRE(arguments != nullptr, "wrong argument type" ); |
| 226 | |
| 227 | arguments->side = side_; |
| 228 | arguments->notional = notional_; |
| 229 | arguments->leg = leg_; |
| 230 | arguments->upfrontPayment = upfrontPayment_; |
| 231 | arguments->accrualRebate = accrualRebate_; |
| 232 | arguments->settlesAccrual = settlesAccrual_; |
| 233 | arguments->paysAtDefaultTime = paysAtDefaultTime_; |
| 234 | arguments->claim = claim_; |
| 235 | arguments->upfront = upfront_; |
| 236 | arguments->spread = runningSpread_; |
| 237 | arguments->protectionStart = protectionStart_; |
| 238 | arguments->maturity = maturity_; |
| 239 | } |
| 240 | |
| 241 | |
| 242 | void CreditDefaultSwap::fetchResults( |
| 243 | const PricingEngine::results* r) const { |
| 244 | Instrument::fetchResults(r); |
| 245 | |
| 246 | const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(r); |
| 247 | QL_REQUIRE(results != nullptr, "wrong result type" ); |
| 248 | |
| 249 | fairSpread_ = results->fairSpread; |
| 250 | fairUpfront_ = results->fairUpfront; |
| 251 | couponLegBPS_ = results->couponLegBPS; |
| 252 | couponLegNPV_ = results->couponLegNPV; |
| 253 | defaultLegNPV_ = results->defaultLegNPV; |
| 254 | upfrontNPV_ = results->upfrontNPV; |
| 255 | upfrontBPS_ = results->upfrontBPS; |
| 256 | accrualRebateNPV_ = results->accrualRebateNPV; |
| 257 | } |
| 258 | |
| 259 | Rate CreditDefaultSwap::fairUpfront() const { |
| 260 | calculate(); |
| 261 | QL_REQUIRE(fairUpfront_ != Null<Rate>(), |
| 262 | "fair upfront not available" ); |
| 263 | return fairUpfront_; |
| 264 | } |
| 265 | |
| 266 | Rate CreditDefaultSwap::fairSpread() const { |
| 267 | calculate(); |
| 268 | QL_REQUIRE(fairSpread_ != Null<Rate>(), |
| 269 | "fair spread not available" ); |
| 270 | return fairSpread_; |
| 271 | } |
| 272 | |
| 273 | Real CreditDefaultSwap::couponLegBPS() const { |
| 274 | calculate(); |
| 275 | QL_REQUIRE(couponLegBPS_ != Null<Rate>(), |
| 276 | "coupon-leg BPS not available" ); |
| 277 | return couponLegBPS_; |
| 278 | } |
| 279 | |
| 280 | Real CreditDefaultSwap::couponLegNPV() const { |
| 281 | calculate(); |
| 282 | QL_REQUIRE(couponLegNPV_ != Null<Rate>(), |
| 283 | "coupon-leg NPV not available" ); |
| 284 | return couponLegNPV_; |
| 285 | } |
| 286 | |
| 287 | Real CreditDefaultSwap::defaultLegNPV() const { |
| 288 | calculate(); |
| 289 | QL_REQUIRE(defaultLegNPV_ != Null<Rate>(), |
| 290 | "default-leg NPV not available" ); |
| 291 | return defaultLegNPV_; |
| 292 | } |
| 293 | |
| 294 | Real CreditDefaultSwap::upfrontNPV() const { |
| 295 | calculate(); |
| 296 | QL_REQUIRE(upfrontNPV_ != Null<Real>(), |
| 297 | "upfront NPV not available" ); |
| 298 | return upfrontNPV_; |
| 299 | } |
| 300 | |
| 301 | Real CreditDefaultSwap::upfrontBPS() const { |
| 302 | calculate(); |
| 303 | QL_REQUIRE(upfrontBPS_ != Null<Real>(), |
| 304 | "upfront BPS not available" ); |
| 305 | return upfrontBPS_; |
| 306 | } |
| 307 | |
| 308 | Real CreditDefaultSwap::accrualRebateNPV() const { |
| 309 | calculate(); |
| 310 | QL_REQUIRE(accrualRebateNPV_ != Null<Real>(), |
| 311 | "accrual Rebate NPV not available" ); |
| 312 | return accrualRebateNPV_; |
| 313 | } |
| 314 | |
| 315 | namespace { |
| 316 | |
| 317 | class ObjectiveFunction { |
| 318 | public: |
| 319 | ObjectiveFunction(Real target, |
| 320 | SimpleQuote& quote, |
| 321 | PricingEngine& engine, |
| 322 | const CreditDefaultSwap::results* results) |
| 323 | : target_(target), quote_(quote), |
| 324 | engine_(engine), results_(results) {} |
| 325 | |
| 326 | Real operator()(Real guess) const { |
| 327 | quote_.setValue(guess); |
| 328 | engine_.calculate(); |
| 329 | return results_->value - target_; |
| 330 | } |
| 331 | private: |
| 332 | Real target_; |
| 333 | SimpleQuote& quote_; |
| 334 | PricingEngine& engine_; |
| 335 | const CreditDefaultSwap::results* results_; |
| 336 | }; |
| 337 | |
| 338 | } |
| 339 | |
| 340 | Rate CreditDefaultSwap::impliedHazardRate( |
| 341 | Real targetNPV, |
| 342 | const Handle<YieldTermStructure>& discountCurve, |
| 343 | const DayCounter& dayCounter, |
| 344 | Real recoveryRate, |
| 345 | Real accuracy, |
| 346 | PricingModel model) const { |
| 347 | |
| 348 | ext::shared_ptr<SimpleQuote> flatRate = ext::make_shared<SimpleQuote>(args: 0.0); |
| 349 | |
| 350 | Handle<DefaultProbabilityTermStructure> probability = |
| 351 | Handle<DefaultProbabilityTermStructure>( |
| 352 | ext::make_shared<FlatHazardRate>(args: 0, args: WeekendsOnly(), |
| 353 | args: Handle<Quote>(flatRate), args: dayCounter)); |
| 354 | |
| 355 | ext::shared_ptr<PricingEngine> engine; |
| 356 | switch (model) { |
| 357 | case Midpoint: |
| 358 | engine = ext::make_shared<MidPointCdsEngine>( |
| 359 | args&: probability, args&: recoveryRate, args: discountCurve); |
| 360 | break; |
| 361 | case ISDA: |
| 362 | engine = ext::make_shared<IsdaCdsEngine>( |
| 363 | args&: probability, args&: recoveryRate, args: discountCurve, |
| 364 | args: ext::nullopt, |
| 365 | args: IsdaCdsEngine::Taylor, |
| 366 | args: IsdaCdsEngine::HalfDayBias, |
| 367 | args: IsdaCdsEngine::Piecewise); |
| 368 | break; |
| 369 | default: |
| 370 | QL_FAIL("unknown CDS pricing model: " << model); |
| 371 | } |
| 372 | |
| 373 | setupArguments(engine->getArguments()); |
| 374 | const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(engine->getResults()); |
| 375 | |
| 376 | ObjectiveFunction f(targetNPV, *flatRate, *engine, results); |
| 377 | //very close guess if targetNPV = 0. |
| 378 | Rate guess = runningSpread_ / (1 - recoveryRate) * 365./360.; |
| 379 | Real step = 0.1 * guess; |
| 380 | return Brent().solve(f, accuracy, guess, step); |
| 381 | } |
| 382 | |
| 383 | Rate CreditDefaultSwap::conventionalSpread( |
| 384 | Real conventionalRecovery, |
| 385 | const Handle<YieldTermStructure>& discountCurve, |
| 386 | const DayCounter& dayCounter, |
| 387 | PricingModel model) const { |
| 388 | |
| 389 | ext::shared_ptr<SimpleQuote> flatRate = ext::make_shared<SimpleQuote>(args: 0.0); |
| 390 | |
| 391 | Handle<DefaultProbabilityTermStructure> probability = |
| 392 | Handle<DefaultProbabilityTermStructure>( |
| 393 | ext::make_shared<FlatHazardRate>(args: 0, args: WeekendsOnly(), |
| 394 | args: Handle<Quote>(flatRate), args: dayCounter)); |
| 395 | |
| 396 | ext::shared_ptr<PricingEngine> engine; |
| 397 | switch (model) { |
| 398 | case Midpoint: |
| 399 | engine = ext::make_shared<MidPointCdsEngine>( |
| 400 | args&: probability, args&: conventionalRecovery, args: discountCurve); |
| 401 | break; |
| 402 | case ISDA: |
| 403 | engine = ext::make_shared<IsdaCdsEngine>( |
| 404 | args&: probability, args&: conventionalRecovery, args: discountCurve, |
| 405 | args: ext::nullopt, |
| 406 | args: IsdaCdsEngine::Taylor, |
| 407 | args: IsdaCdsEngine::HalfDayBias, |
| 408 | args: IsdaCdsEngine::Piecewise); |
| 409 | break; |
| 410 | default: |
| 411 | QL_FAIL("unknown CDS pricing model: " << model); |
| 412 | } |
| 413 | |
| 414 | setupArguments(engine->getArguments()); |
| 415 | const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(engine->getResults()); |
| 416 | |
| 417 | ObjectiveFunction f(0., *flatRate, *engine, results); |
| 418 | Rate guess = runningSpread_ / (1 - conventionalRecovery) * 365./360.; |
| 419 | Real step = guess * 0.1; |
| 420 | |
| 421 | Brent().solve(f, accuracy: 1e-9, guess, step); |
| 422 | return results->fairSpread; |
| 423 | } |
| 424 | |
| 425 | |
| 426 | const Date& CreditDefaultSwap::protectionStartDate() const { |
| 427 | return protectionStart_; |
| 428 | } |
| 429 | |
| 430 | const Date& CreditDefaultSwap::protectionEndDate() const { |
| 431 | return ext::dynamic_pointer_cast<Coupon>(r: leg_.back()) |
| 432 | ->accrualEndDate(); |
| 433 | } |
| 434 | |
| 435 | const ext::shared_ptr<SimpleCashFlow>& CreditDefaultSwap::upfrontPayment() const { |
| 436 | return upfrontPayment_; |
| 437 | } |
| 438 | |
| 439 | const ext::shared_ptr<SimpleCashFlow>& CreditDefaultSwap::accrualRebate() const { |
| 440 | return accrualRebate_; |
| 441 | } |
| 442 | |
| 443 | const Date& CreditDefaultSwap::tradeDate() const { |
| 444 | return tradeDate_; |
| 445 | } |
| 446 | |
| 447 | Natural CreditDefaultSwap::cashSettlementDays() const { |
| 448 | return cashSettlementDays_; |
| 449 | } |
| 450 | |
| 451 | CreditDefaultSwap::arguments::arguments() |
| 452 | : side(Protection::Side(-1)), notional(Null<Real>()), |
| 453 | spread(Null<Rate>()) {} |
| 454 | |
| 455 | void CreditDefaultSwap::arguments::validate() const { |
| 456 | QL_REQUIRE(side != Protection::Side(-1), "side not set" ); |
| 457 | QL_REQUIRE(notional != Null<Real>(), "notional not set" ); |
| 458 | QL_REQUIRE(notional != 0.0, "null notional set" ); |
| 459 | QL_REQUIRE(spread != Null<Rate>(), "spread not set" ); |
| 460 | QL_REQUIRE(!leg.empty(), "coupons not set" ); |
| 461 | QL_REQUIRE(upfrontPayment, "upfront payment not set" ); |
| 462 | QL_REQUIRE(claim, "claim not set" ); |
| 463 | QL_REQUIRE(protectionStart != Null<Date>(), |
| 464 | "protection start date not set" ); |
| 465 | QL_REQUIRE(maturity != Null<Date>(), |
| 466 | "maturity date not set" ); |
| 467 | } |
| 468 | |
| 469 | void CreditDefaultSwap::results::reset() { |
| 470 | Instrument::results::reset(); |
| 471 | fairSpread = Null<Rate>(); |
| 472 | fairUpfront = Null<Rate>(); |
| 473 | couponLegBPS = Null<Real>(); |
| 474 | couponLegNPV = Null<Real>(); |
| 475 | defaultLegNPV = Null<Real>(); |
| 476 | upfrontBPS = Null<Real>(); |
| 477 | upfrontNPV = Null<Real>(); |
| 478 | accrualRebateNPV = Null<Real>(); |
| 479 | } |
| 480 | |
| 481 | Date cdsMaturity(const Date& tradeDate, const Period& tenor, DateGeneration::Rule rule) { |
| 482 | |
| 483 | QL_REQUIRE(rule == DateGeneration::CDS2015 || rule == DateGeneration::CDS || rule == DateGeneration::OldCDS, |
| 484 | "cdsMaturity should only be used with date generation rule CDS2015, CDS or OldCDS" ); |
| 485 | |
| 486 | QL_REQUIRE(tenor.units() == Years || (tenor.units() == Months && tenor.length() % 3 == 0), |
| 487 | "cdsMaturity expects a tenor that is a multiple of 3 months." ); |
| 488 | |
| 489 | if (rule == DateGeneration::OldCDS) { |
| 490 | QL_REQUIRE(tenor != 0 * Months, "A tenor of 0M is not supported for OldCDS." ); |
| 491 | } |
| 492 | |
| 493 | Date anchorDate = previousTwentieth(d: tradeDate, rule); |
| 494 | if (rule == DateGeneration::CDS2015 && (anchorDate == Date(20, Dec, anchorDate.year()) || |
| 495 | anchorDate == Date(20, Jun, anchorDate.year()))) { |
| 496 | if (tenor.length() == 0) { |
| 497 | return Null<Date>(); |
| 498 | } else { |
| 499 | anchorDate -= 3 * Months; |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | Date maturity = anchorDate + tenor + 3 * Months; |
| 504 | QL_REQUIRE(maturity > tradeDate, "error calculating CDS maturity. Tenor is " << tenor << ", trade date is " << |
| 505 | io::iso_date(tradeDate) << " generating a maturity of " << io::iso_date(maturity) << " <= trade date." ); |
| 506 | |
| 507 | return maturity; |
| 508 | } |
| 509 | |
| 510 | } |
| 511 | |