Build a default curve from CDS spread quotes.
299 {
300
301 LOG(
"Start building default curve of type SpreadCDS for curve " << curveID);
302
305 "DefaultCurve::buildCdsCurve expected a default curve configuration with type SpreadCDS/Price");
306 QL_REQUIRE(
recoveryRate_ != Null<Real>(),
"DefaultCurve: recovery rate needed to build SpreadCDS curve");
307
308
309 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
311 QuantLib::ext::shared_ptr<CdsConvention> cdsConv =
312 QuantLib::ext::dynamic_pointer_cast<CdsConvention>(conventions->get(config.
conventionID()));
313 QL_REQUIRE(cdsConv, "SpreadCDS curves require CDS convention");
314
315
317 QL_REQUIRE(it != yieldCurves.end(),
"The discount curve, " << config.
discountCurveID()
318 << ", required in the building of the curve, "
319 <<
spec.
name() <<
", was not found.");
320 Handle<YieldTermStructure> discountCurve = it->second->handle();
321
322
323 set<QuoteData> quotes = getConfiguredQuotes(curveID, config, asof, loader);
324
325
329 refData.
tenor = Period(cdsConv->frequency());
330 refData.
calendar = cdsConv->calendar();
331 refData.
convention = cdsConv->paymentConvention();
333 refData.
rule = cdsConv->rule();
338
339
342
343
344
345
346 vector<Date> dates{asof, asof + 1 * Years, asof + 10 * Years};
347 vector<Real> survivalProbs{1.0, 1e-16, 1e-18};
348 curve_ = QuantLib::ext::make_shared<QuantExt::CreditCurve>(
349 Handle<DefaultProbabilityTermStructure>(
351 dates, survivalProbs, config.
dayCounter(), Calendar(), std::vector<Handle<Quote>>(),
352 std::vector<Date>(), LogLinear())),
353 discountCurve, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(
recoveryRate_)), refData);
354 curve_->curve()->enableExtrapolation();
355 WLOG(
"DefaultCurve: recovery rate found but no CDS quotes for "
356 << curveID << " and "
357 << "ImplyDefaultFromMarket is true. Curve built that gives default immediately.");
358 return;
359 }
360 } else {
361 QL_REQUIRE(!quotes.empty(), "No market points found for CDS curve config " << curveID);
362 }
363
364
365 vector<QuantLib::ext::shared_ptr<QuantExt::DefaultProbabilityHelper>> helpers;
366 std::map<QuantLib::Date, QuantLib::Period> helperQuoteTerms;
367 Real runningSpread = Null<Real>();
368 QuantExt::CreditDefaultSwap::ProtectionPaymentTime ppt = cdsConv->paysAtDefaultTime()
369 ? QuantExt::CreditDefaultSwap::atDefault
370 : QuantExt::CreditDefaultSwap::atPeriodEnd;
371
373 for (auto quote : quotes) {
374 QuantLib::ext::shared_ptr<SpreadCdsHelper> tmp;
375 try {
376 tmp = QuantLib::ext::make_shared<SpreadCdsHelper>(
377 quote.value, quote.term, cdsConv->settlementDays(), cdsConv->calendar(), cdsConv->frequency(),
378 cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(),
recoveryRate_, discountCurve,
379 cdsConv->settlesAccrual(), ppt, config.
startDate(), cdsConv->lastPeriodDayCounter());
380
381 } catch (exception& e) {
382 if (quote.term == Period(0, Months)) {
383 WLOG(
"DefaultCurve:: Cannot add quote of term 0M to CDS curve " << curveID <<
" for asof date "
384 << asof);
385 } else {
386 QL_FAIL("DefaultCurve:: Failed to add quote of term " << quote.term << " to CDS curve " << curveID
387 << " for asof date " << asof
388 << ", with error: " << e.what());
389 }
390 }
391 if (tmp) {
392 if (tmp->latestDate() > asof) {
393 helpers.push_back(tmp);
395 }
396 helperQuoteTerms[tmp->latestDate()] = quote.term;
397 }
398 }
399 } else {
400 for (auto quote : quotes) {
401
402 runningSpread = quote.runningSpread;
403 if (runningSpread == Null<Real>()) {
405 "A running spread was not provided in the quote "
406 << "string so it must be provided in the config for CDS upfront curve " << curveID);
408 }
409 auto tmp = QuantLib::ext::make_shared<UpfrontCdsHelper>(
410 quote.value, runningSpread, quote.term, cdsConv->settlementDays(), cdsConv->calendar(),
411 cdsConv->frequency(), cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(),
412 recoveryRate_, discountCurve, cdsConv->upfrontSettlementDays(), cdsConv->settlesAccrual(), ppt,
413 config.
startDate(), cdsConv->lastPeriodDayCounter());
414 if (tmp->latestDate() > asof) {
415 helpers.push_back(tmp);
416 }
417 helperQuoteTerms[tmp->latestDate()] = quote.term;
418 }
419 }
420
421 QL_REQUIRE(!helpers.empty(), "DefaultCurve: no alive quotes found.");
422
424
425
426
427 std::sort(helpers.begin(), helpers.end(), QuantLib::detail::BootstrapHelperSorter());
428
429
437
438 typedef PiecewiseDefaultCurve<QuantExt::SurvivalProbability, LogLinear, QuantExt::IterativeBootstrap> SpCurve;
439 SpCurve::bootstrap_type btconfig(accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor,
440 minFactor, dontThrowSteps);
441 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> qlCurve;
442
444
445
446
447
448 std::vector<Real> helperTermTimes;
449 for (auto const& h : helpers) {
451 }
452
454 Size helperIndex_m, helperIndex_p;
455 Real alpha;
457
458 auto tmp1 = QuantLib::ext::make_shared<SpCurve>(
459 asof, std::vector<QuantLib::ext::shared_ptr<QuantExt::DefaultProbabilityHelper>>{helpers[helperIndex_m]},
461 Date d1 = helpers[helperIndex_m]->pillarDate();
462 Real p1 = tmp1->survivalProbability(d1);
463 auto tmp1i = QuantLib::ext::make_shared<QuantExt::InterpolatedSurvivalProbabilityCurve<LogLinear>>(
464 std::vector<Date>{asof, d1}, std::vector<Real>{1.0, p1}, config.
dayCounter(), Calendar(),
465 std::vector<Handle<Quote>>(), std::vector<Date>(), LogLinear(), config.
allowNegativeRates());
466
468 qlCurve = tmp1i;
469 } else {
470 auto tmp2 = QuantLib::ext::make_shared<SpCurve>(
471 asof, std::vector<QuantLib::ext::shared_ptr<QuantExt::DefaultProbabilityHelper>>{helpers[helperIndex_p]},
473 Date d2 = helpers[helperIndex_p]->pillarDate();
474 Real p2 = tmp2->survivalProbability(d2);
475 auto tmp2i = QuantLib::ext::make_shared<QuantExt::InterpolatedSurvivalProbabilityCurve<LogLinear>>(
476 std::vector<Date>{asof, d2}, std::vector<Real>{1.0, p2}, config.
dayCounter(), Calendar(),
477 std::vector<Handle<Quote>>(), std::vector<Date>(), LogLinear(), config.
allowNegativeRates());
478 tmp1i->enableExtrapolation();
479 tmp2i->enableExtrapolation();
480 qlCurve = QuantLib::ext::make_shared<QuantExt::TermInterpolatedDefaultCurve>(
481 Handle<DefaultProbabilityTermStructure>(tmp1i), Handle<DefaultProbabilityTermStructure>(tmp2i), alpha);
482 }
483
484 } else {
485
486
487
488 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> tmp = QuantLib::ext::make_shared<SpCurve>(
489 asof, helpers, config.
dayCounter(), LogLinear(),
491 minFactor, dontThrowSteps));
492
493
494
495 vector<Date> dates;
496 vector<Real> survivalProbs;
497 dates.push_back(asof);
498 survivalProbs.push_back(1.0);
499
500 for (Size i = 0; i < helpers.size(); ++i) {
501 if (helpers[i]->latestDate() > asof) {
502 Date pillarDate = helpers[i]->pillarDate();
503 Probability sp = tmp->survivalProbability(pillarDate);
504
505
506
507
508 if (!survivalProbs.empty() && close(survivalProbs.back(), sp)) {
509 DLOG(
"Survival probability for curve " <<
spec.
name() <<
" at date " << io::iso_date(pillarDate)
510 << " is the same as that at previous date "
511 << io::iso_date(dates.back()) << " so skipping it.");
512 continue;
513 }
514
515 dates.push_back(pillarDate);
516 survivalProbs.push_back(sp);
517 TLOG(io::iso_date(pillarDate) <<
"," << fixed << setprecision(9) << sp);
518 }
519 }
520 if (dates.size() == 1) {
521
522 dates.push_back(dates.back() + 1);
523 survivalProbs.push_back(survivalProbs.back());
524 }
525 qlCurve = QuantLib::ext::make_shared<QuantExt::InterpolatedSurvivalProbabilityCurve<LogLinear>>(
526 dates, survivalProbs, config.
dayCounter(), Calendar(), std::vector<Handle<Quote>>(), std::vector<Date>(),
528 }
529
531 qlCurve->enableExtrapolation();
532 DLOG(
"DefaultCurve: Enabled Extrapolation");
533 }
534
535 curve_ = QuantLib::ext::make_shared<QuantExt::CreditCurve>(Handle<DefaultProbabilityTermStructure>(qlCurve), discountCurve,
536 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(
recoveryRate_)),
537 refData);
538
539 LOG(
"Finished building default curve of type SpreadCDS for curve " << curveID);
540}
QuantLib::Real maxFactor() const
QuantLib::Size dontThrowSteps() const
QuantLib::Real globalAccuracy() const
QuantLib::Real accuracy() const
QuantLib::Real minFactor() const
QuantLib::Size maxAttempts() const
string name() const
returns the unique curve name
const Real runningSpread() const
const Type & type() const
const QuantLib::Period & indexTerm() const
const string & discountCurveID() const
const QuantLib::Date & startDate() const
const boost::optional< bool > & implyDefaultFromMarket() const
const DayCounter & dayCounter() const
const bool allowNegativeRates() const
bool extrapolation() const
const string & conventionID() const
const BootstrapConfig & bootstrapConfig() const
#define LOG(text)
Logging Macro (Level = Notice)
#define WLOG(text)
Logging Macro (Level = Warning)
#define TLOG(text)
Logging Macro (Level = Data)
std::tuple< Size, Size, Real > interpolationIndices(const T &x, const Real v)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Real periodToTime(const Period &p)
QuantLib::DayCounter lastPeriodDayCounter
QuantLib::Period indexTerm
QuantLib::DateGeneration::Rule rule
QuantLib::DayCounter dayCounter
QuantLib::Natural cashSettlementDays
QuantLib::BusinessDayConvention termConvention
QuantLib::Calendar calendar
QuantLib::BusinessDayConvention payConvention
QuantLib::BusinessDayConvention convention
QuantLib::Real runningSpread