Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
defaultcurve.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
23
33
34#include <ql/math/interpolations/backwardflatinterpolation.hpp>
35#include <ql/math/interpolations/loginterpolation.hpp>
36#include <ql/termstructures/credit/defaultprobabilityhelpers.hpp>
37#include <ql/termstructures/credit/flathazardrate.hpp>
38#include <ql/time/daycounters/actual365fixed.hpp>
39
40#include <algorithm>
41#include <set>
42
43using namespace QuantLib;
44using namespace std;
45
46namespace {
47
48using namespace ore::data;
49
50struct QuoteData {
51 QuoteData() : value(Null<Real>()), runningSpread(Null<Real>()) {}
52
53 QuoteData(const Period& t, Real v, string s, string c, string d, Real rs = Null<Real>())
54 : term(t), value(v), seniority(s), ccy(c), docClause(d), runningSpread(rs) {}
55
56 Period term;
57 Real value;
58 string seniority;
59 string ccy;
60 string docClause;
61 Real runningSpread;
62};
63
64bool operator<(const QuoteData& lhs, const QuoteData& rhs) { return lhs.term < rhs.term; }
65
66void addQuote(set<QuoteData>& quotes, const string& configId, const string& name, const Period& tenor, Real value,
67 string seniority, string ccy, string docClause, Real runningSpread = Null<Real>()) {
68
69 // Add to quotes, with a check that we have no duplicate tenors
70 auto r = quotes.insert(QuoteData(tenor, value, seniority, ccy, docClause, runningSpread));
71 QL_REQUIRE(r.second, "duplicate term in quotes found (" << tenor << ") while loading default curve " << configId);
72 TLOG("Loaded quote " << name << " for default curve " << configId);
73}
74
75set<QuoteData> getRegexQuotes(const Wildcard& wc, const string& configId, DefaultCurveConfig::Config::Type type,
76 const Date& asof, const Loader& loader) {
77
79 using MDQT = MarketDatum::QuoteType;
80 using MDIT = MarketDatum::InstrumentType;
81 LOG("Loading regex quotes for default curve " << configId);
82
83 // Loop over the available market data and pick out quotes that match the expression
84 set<QuoteData> result;
85
86 std::ostringstream ss1;
87 ss1 << MDIT::CDS << "/*";
88 Wildcard w1(ss1.str());
89 auto data1 = loader.get(w1, asof);
90
91 std::ostringstream ss2;
92 ss2 << MDIT::HAZARD_RATE << "/*";
93 Wildcard w2(ss2.str());
94 auto data2 = loader.get(w2, asof);
95
96 data1.merge(data2);
97
98 for (const auto& md : data1) {
99
100 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
101
102 auto mdit = md->instrumentType();
103 auto mdqt = md->quoteType();
104
105 // If we have a CDS spread or hazard rate quote, check it and populate tenor and value if it matches
106 if (type == DCCT::SpreadCDS && mdit == MDIT::CDS &&
107 (mdqt == MDQT::CREDIT_SPREAD || mdqt == MDQT::CONV_CREDIT_SPREAD)) {
108
109 auto q = QuantLib::ext::dynamic_pointer_cast<CdsQuote>(md);
110 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to CdsQuote");
111 if (wc.matches(q->name())) {
112 addQuote(result, configId, q->name(), q->term(), q->quote()->value(), q->seniority(), q->ccy(),
113 q->docClause(), q->runningSpread());
114 }
115
116 } else if (type == DCCT::Price && (mdit == MDIT::CDS && mdqt == MDQT::PRICE)) {
117
118 auto q = QuantLib::ext::dynamic_pointer_cast<CdsQuote>(md);
119 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to CdsQuote");
120 if (wc.matches(q->name())) {
121 addQuote(result, configId, q->name(), q->term(), q->quote()->value(), q->seniority(), q->ccy(),
122 q->docClause(), q->runningSpread());
123 }
124
125 } else if (type == DCCT::HazardRate && (mdit == MDIT::HAZARD_RATE && mdqt == MDQT::RATE)) {
126
127 auto q = QuantLib::ext::dynamic_pointer_cast<HazardRateQuote>(md);
128 QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to HazardRateQuote");
129 if (wc.matches(q->name())) {
130 addQuote(result, configId, q->name(), q->term(), q->quote()->value(), q->seniority(), q->ccy(),
131 q->docClause());
132 }
133 }
134 }
135
136 // check whether the set of quotes contains more than one seniority, ccy or doc clause,
137 // which probably means that the wildcard has pulled in too many quotes
138
139 std::set<string> seniorities, ccys, docClauses;
140 for (auto const& q : result) {
141 if (!q.seniority.empty())
142 seniorities.insert(q.seniority);
143 if (!q.ccy.empty())
144 ccys.insert(q.ccy);
145 if (!q.docClause.empty())
146 docClauses.insert(q.docClause);
147 }
148
149 QL_REQUIRE(seniorities.size() < 2, "More than one seniority found in wildcard quotes.");
150 QL_REQUIRE(ccys.size() < 2, "More than one seniority found in wildcard quotes.");
151 QL_REQUIRE(docClauses.size() < 2, "More than one seniority found in wildcard quotes.");
152
153 // We don't check for an empty set of CDS quotes here. We check it later because under some circumstances,
154 // it may be allowable to have no quotes.
155 if (type != DCCT::SpreadCDS && type != DCCT::Price) {
156 QL_REQUIRE(!result.empty(), "No market points found for curve config " << configId);
157 }
158
159 LOG("DefaultCurve " << configId << " loaded and using " << result.size() << " quotes.");
160
161 return result;
162}
163
164set<QuoteData> getExplicitQuotes(const vector<pair<string, bool>>& quotes, const string& configId,
165 DefaultCurveConfig::Config::Type type, const Date& asof, const Loader& loader) {
166
168 LOG("Loading explicit quotes for default curve " << configId);
169
170 set<QuoteData> result;
171 for (const auto& p : quotes) {
172 if (QuantLib::ext::shared_ptr<MarketDatum> md = loader.get(p, asof)) {
173 if (type == DCCT::SpreadCDS || type == DCCT::Price) {
174 auto q = QuantLib::ext::dynamic_pointer_cast<CdsQuote>(md);
175 QL_REQUIRE(q, "Quote " << p.first << " for config " << configId << " should be a CdsQuote");
176 addQuote(result, configId, q->name(), q->term(), q->quote()->value(), q->seniority(), q->ccy(),
177 q->docClause(), q->runningSpread());
178 } else {
179 auto q = QuantLib::ext::dynamic_pointer_cast<HazardRateQuote>(md);
180 QL_REQUIRE(q, "Quote " << p.first << " for config " << configId << " should be a HazardRateQuote");
181 addQuote(result, configId, q->name(), q->term(), q->quote()->value(), q->seniority(), q->ccy(),
182 q->docClause());
183 }
184 }
185 }
186
187 // We don't check for an empty set of CDS quotes here. We check it later because under some circumstances,
188 // it may be allowable to have no quotes.
189 if (type != DCCT::SpreadCDS && type != DCCT::Price) {
190 QL_REQUIRE(!result.empty(), "No market points found for curve config " << configId);
191 }
192
193 LOG("DefaultCurve " << configId << " using " << result.size() << " default quotes of " << quotes.size()
194 << " requested quotes.");
195
196 return result;
197}
198
199set<QuoteData> getConfiguredQuotes(const std::string& curveID, const DefaultCurveConfig::Config& config,
200 const Date& asof, const Loader& loader) {
201
203 auto type = config.type();
204 QL_REQUIRE(type == DCCT::SpreadCDS || type == DCCT::Price || type == DCCT::HazardRate,
205 "getConfiguredQuotes expects a curve type of SpreadCDS, Price or HazardRate.");
206 QL_REQUIRE(!config.cdsQuotes().empty(), "No quotes configured for curve " << curveID);
207
208 // We may have a _single_ regex quote or a list of explicit quotes. Check if we have single regex quote.
209 std::vector<std::string> tmp;
210 std::transform(config.cdsQuotes().begin(), config.cdsQuotes().end(), std::back_inserter(tmp),
211 [](const std::pair<std::string, bool>& p) { return p.first; });
212 auto wildcard = getUniqueWildcard(tmp);
213
214 if (wildcard) {
215 return getRegexQuotes(*wildcard, curveID, config.type(), asof, loader);
216 } else {
217 return getExplicitQuotes(config.cdsQuotes(), curveID, config.type(), asof, loader);
218 }
219}
220
221} // namespace
222
223namespace ore {
224namespace data {
225
228 map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
229 map<string, QuantLib::ext::shared_ptr<DefaultCurve>>& defaultCurves) {
230 const QuantLib::ext::shared_ptr<DefaultCurveConfig>& configs = curveConfigs.defaultCurveConfig(spec.curveConfigID());
231 bool built = false;
232 std::string errors;
233 for (auto const& config : configs->configs()) {
234 try {
235 recoveryRate_ = Null<Real>();
236 if (!config.second.recoveryRateQuote().empty()) {
237 // handle case where the recovery rate is hardcoded in the curve config
238 if (!tryParseReal(config.second.recoveryRateQuote(), recoveryRate_)) {
239 Wildcard wc(config.second.recoveryRateQuote());
240 if (wc.hasWildcard()) {
241 for (auto const& q : loader.get(wc, asof)) {
242 if (wc.matches(q->name())) {
243 QL_REQUIRE(recoveryRate_ == Null<Real>(),
244 "There is more than one recovery rate matching the pattern '" << wc.pattern()
245 << "'.");
246 recoveryRate_ = q->quote()->value();
247 }
248 }
249 } else {
250 QL_REQUIRE(loader.has(config.second.recoveryRateQuote(), asof),
251 "There is no market data for the requested recovery rate "
252 << config.second.recoveryRateQuote());
253 recoveryRate_ = loader.get(config.second.recoveryRateQuote(), asof)->quote()->value();
254 }
255 }
256 }
257 // Build the default curve of the requested type
258 switch (config.second.type()) {
261 buildCdsCurve(configs->curveID(), config.second, asof, spec, loader, yieldCurves);
262 break;
264 buildHazardRateCurve(configs->curveID(), config.second, asof, spec, loader);
265 break;
267 buildBenchmarkCurve(configs->curveID(), config.second, asof, spec, loader, yieldCurves);
268 break;
270 buildMultiSectionCurve(configs->curveID(), config.second, asof, spec, loader, defaultCurves);
271 break;
273 buildTransitionMatrixCurve(configs->curveID(), config.second, asof, spec, loader, defaultCurves);
274 break;
276 buildNullCurve(configs->curveID(), config.second, asof, spec);
277 break;
278 default:
279 QL_FAIL("The DefaultCurveConfig type " << static_cast<int>(config.second.type())
280 << " was not recognised");
281 }
282 built = true;
283 break;
284 } catch (exception& e) {
285 std::ostringstream message;
286 message << "build attempt failed for " << configs->curveID() << " using config with priority "
287 << config.first << ": " << e.what();
288 DLOG(message.str());
289 if (!errors.empty())
290 errors += ", ";
291 errors += message.str();
292 }
293 }
294 QL_REQUIRE(built, "default curve building failed for " << spec.curveConfigID() << ": " << errors);
295}
296
297void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveConfig::Config& config, const Date& asof,
298 const DefaultCurveSpec& spec, const Loader& loader,
299 map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves) {
300
301 LOG("Start building default curve of type SpreadCDS for curve " << curveID);
302
303 QL_REQUIRE(config.type() == DefaultCurveConfig::Config::Type::SpreadCDS ||
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 // Get the CDS curve conventions
309 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
310 QL_REQUIRE(conventions->has(config.conventionID()), "No conventions found with id " << config.conventionID());
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 // Get the discount curve for use in the CDS spread curve bootstrap
316 auto it = yieldCurves.find(config.discountCurveID());
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 // Get the CDS spread / price curve quotes
323 set<QuoteData> quotes = getConfiguredQuotes(curveID, config, asof, loader);
324
325 // Set up ref data for the curve, except runningSpread which is set below
327 refData.indexTerm = config.indexTerm();
328 refData.startDate = config.startDate();
329 refData.tenor = Period(cdsConv->frequency());
330 refData.calendar = cdsConv->calendar();
331 refData.convention = cdsConv->paymentConvention();
332 refData.termConvention = cdsConv->paymentConvention();
333 refData.rule = cdsConv->rule();
334 refData.payConvention = cdsConv->paymentConvention();
335 refData.dayCounter = cdsConv->dayCounter();
336 refData.lastPeriodDayCounter = cdsConv->lastPeriodDayCounter();
337 refData.cashSettlementDays = cdsConv->upfrontSettlementDays();
338
339 // If the configuration instructs us to imply a default from the market data, we do it here.
340 if (config.implyDefaultFromMarket() && *config.implyDefaultFromMarket()) {
341 if (recoveryRate_ != Null<Real>() && quotes.empty()) {
342 // Assume entity is in default, between event determination date and auction date. Build a survival
343 // probability curve with value 0.0 tomorrow to approximate this and allow dependent instruments to price.
344 // Need to use small but positive numbers to avoid downstream issues with log linear survivals e.g. below
345 // and in places like ScenarioSimMarket.
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 // Create the CDS instrument helpers, only keep alive helpers
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);
394 runningSpread = config.runningSpread();
395 }
396 helperQuoteTerms[tmp->latestDate()] = quote.term;
397 }
398 }
399 } else {
400 for (auto quote : quotes) {
401 // If there is no running spread encoded in the quote, the config must have one.
402 runningSpread = quote.runningSpread;
403 if (runningSpread == Null<Real>()) {
404 QL_REQUIRE(config.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);
407 runningSpread = config.runningSpread();
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
423 refData.runningSpread = runningSpread;
424
425 // Ensure that the helpers are sorted. This is done in IterativeBootstrap, but we need
426 // a sorted instruments vector in the code here as well.
427 std::sort(helpers.begin(), helpers.end(), QuantLib::detail::BootstrapHelperSorter());
428
429 // Get configuration values for bootstrap
430 Real accuracy = config.bootstrapConfig().accuracy();
431 Real globalAccuracy = config.bootstrapConfig().globalAccuracy();
432 bool dontThrow = config.bootstrapConfig().dontThrow();
433 Size maxAttempts = config.bootstrapConfig().maxAttempts();
434 Real maxFactor = config.allowNegativeRates() ? config.bootstrapConfig().maxFactor() : 1.0;
435 Real minFactor = config.bootstrapConfig().minFactor();
436 Size dontThrowSteps = config.bootstrapConfig().dontThrowSteps();
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
443 if (config.indexTerm() != 0 * Days) {
444
445 // build flat index curve picking a quote with identical term as configured if possible
446 // otherwise build interpolated curve using the existing quotes
447
448 std::vector<Real> helperTermTimes;
449 for (auto const& h : helpers) {
450 helperTermTimes.push_back(QuantExt::periodToTime(helperQuoteTerms[h->latestDate()]));
451 }
452
453 Real t = QuantExt::periodToTime(config.indexTerm());
454 Size helperIndex_m, helperIndex_p;
455 Real alpha;
456 std::tie(helperIndex_m, helperIndex_p, alpha) = QuantExt::interpolationIndices(helperTermTimes, t);
457
458 auto tmp1 = QuantLib::ext::make_shared<SpCurve>(
459 asof, std::vector<QuantLib::ext::shared_ptr<QuantExt::DefaultProbabilityHelper>>{helpers[helperIndex_m]},
460 config.dayCounter(), LogLinear(), btconfig);
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
467 if (close_enough(alpha, 1.0)) {
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]},
472 config.dayCounter(), LogLinear(), btconfig);
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 // build single name curve
487
488 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> tmp = QuantLib::ext::make_shared<SpCurve>(
489 asof, helpers, config.dayCounter(), LogLinear(),
490 QuantExt::IterativeBootstrap<SpCurve>(accuracy, globalAccuracy, dontThrow, maxAttempts, maxFactor,
491 minFactor, dontThrowSteps));
492
493 // As for yield curves we need to copy the piecewise curve because on eval date changes the relative date
494 // helpers with trigger a bootstrap.
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 // In some cases the bootstrapped survival probability at one tenor will be `close` to that at a
506 // previous tenor. Here we don't add that survival probability and date to avoid issues when creating
507 // the InterpolatedSurvivalProbabilityCurve below.
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 // We might have removed points above. To make the interpolation work, we need at least two points though.
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>(),
527 LogLinear(), config.allowNegativeRates());
528 }
529
530 if (config.extrapolation()) {
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}
541
542void DefaultCurve::buildHazardRateCurve(const std::string& curveID, const DefaultCurveConfig::Config& config,
543 const Date& asof, const DefaultCurveSpec& spec, const Loader& loader) {
544
545 LOG("Start building default curve of type HazardRate for curve " << curveID);
546
548 "DefaultCurve::buildHazardRateCurve expected a default curve configuration with type HazardRate");
549
550 // Get the hazard rate curve conventions
551 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
552 QL_REQUIRE(conventions->has(config.conventionID()), "No conventions found with id " << config.conventionID());
553 QuantLib::ext::shared_ptr<CdsConvention> cdsConv =
554 QuantLib::ext::dynamic_pointer_cast<CdsConvention>(conventions->get(config.conventionID()));
555 QL_REQUIRE(cdsConv, "HazardRate curves require CDS convention");
556
557 // Get the hazard rate quotes
558 set<QuoteData> quotes = getConfiguredQuotes(curveID, config, asof, loader);
559
560 // Build the hazard rate curve
561 Calendar cal = cdsConv->calendar();
562 vector<Date> dates;
563 vector<Real> quoteValues;
564
565 // If first term is not zero, add asof point
566 if (quotes.begin()->term != 0 * Days) {
567 LOG("DefaultCurve: add asof (" << asof << "), hazard rate " << quotes.begin()->value << ", as not given");
568 dates.push_back(asof);
569 quoteValues.push_back(quotes.begin()->value);
570 }
571
572 for (auto quote : quotes) {
573 dates.push_back(cal.advance(asof, quote.term, Following, false));
574 quoteValues.push_back(quote.value);
575 }
576
577 LOG("DefaultCurve: set up interpolated hazard rate curve");
578 curve_ = QuantLib::ext::make_shared<QuantExt::CreditCurve>(
579 Handle<DefaultProbabilityTermStructure>(QuantLib::ext::make_shared<QuantExt::InterpolatedHazardRateCurve<BackwardFlat>>(
580 dates, quoteValues, config.dayCounter(), BackwardFlat(), config.allowNegativeRates())));
581
582 if (config.extrapolation()) {
583 curve_->curve()->enableExtrapolation();
584 DLOG("DefaultCurve: Enabled Extrapolation");
585 }
586
587 if (recoveryRate_ == Null<Real>()) {
588 LOG("DefaultCurve: setting recovery rate to 0.0 for hazard rate curve, because none is given.");
589 recoveryRate_ = 0.0;
590 }
591
592 // Force bootstrap so that errors are thrown during the build, not later
593 curve_->curve()->survivalProbability(QL_EPSILON);
594
595 LOG("Finished building default curve of type HazardRate for curve " << curveID);
596}
597
598void DefaultCurve::buildBenchmarkCurve(const std::string& curveID, const DefaultCurveConfig::Config& config,
599 const Date& asof, const DefaultCurveSpec& spec, const Loader& loader,
600 map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves) {
601
602 LOG("Start building default curve of type Benchmark for curve " << curveID);
603
605 "DefaultCurve::buildBenchmarkCurve expected a default curve configuration with type Benchmark");
606
607 if (recoveryRate_ == Null<Real>())
608 recoveryRate_ = 0.0;
609
610 // Populate benchmark yield curve
611 auto it = yieldCurves.find(config.benchmarkCurveID());
612 QL_REQUIRE(it != yieldCurves.end(), "The benchmark curve, " << config.benchmarkCurveID()
613 << ", required in the building of the curve, "
614 << spec.name() << ", was not found.");
615 QuantLib::ext::shared_ptr<YieldCurve> benchmarkCurve = it->second;
616
617 // Populate source yield curve
618 it = yieldCurves.find(config.sourceCurveID());
619 QL_REQUIRE(it != yieldCurves.end(), "The source curve, " << config.sourceCurveID()
620 << ", required in the building of the curve, "
621 << spec.name() << ", was not found.");
622 QuantLib::ext::shared_ptr<YieldCurve> sourceCurve = it->second;
623
624 // Parameters from the configuration
625 vector<Period> pillars = parseVectorOfValues<Period>(config.pillars(), &parsePeriod);
626 Calendar cal = config.calendar();
627 Size spotLag = config.spotLag();
628
629 // Create the implied survival curve
630 vector<Date> dates;
631 vector<Real> impliedSurvProb;
632 Date spot = cal.advance(asof, spotLag * Days);
633 for (Size i = 0; i < pillars.size(); ++i) {
634 dates.push_back(cal.advance(spot, pillars[i]));
635 Real tmp = dates[i] == asof
636 ? 1.0
637 : sourceCurve->handle()->discount(dates[i]) / benchmarkCurve->handle()->discount(dates[i]);
638 // if a non-zero recovery rate is given, we adjust the implied surv probability according to a market value
639 // recovery model (see the documentation of the benchmark curve in the user guide for more details)
640 impliedSurvProb.push_back(std::pow(tmp, 1.0 / (1.0 - recoveryRate_)));
641 }
642 QL_REQUIRE(dates.size() > 0, "DefaultCurve (Benchmark): no dates given");
643
644 // Insert SP = 1.0 at asof if asof date is not in the pillars
645 if (dates[0] != asof) {
646 dates.insert(dates.begin(), asof);
647 impliedSurvProb.insert(impliedSurvProb.begin(), 1.0);
648 }
649
650 LOG("DefaultCurve: set up interpolated surv prob curve as yield over benchmark");
651 curve_ = QuantLib::ext::make_shared<QuantExt::CreditCurve>(Handle<DefaultProbabilityTermStructure>(
653 dates, impliedSurvProb, config.dayCounter(), Calendar(), std::vector<Handle<Quote>>(), std::vector<Date>(),
654 LogLinear(), config.allowNegativeRates())));
655
656 if (config.extrapolation()) {
657 curve_->curve()->enableExtrapolation();
658 DLOG("DefaultCurve: Enabled Extrapolation");
659 }
660
661 // Force bootstrap so that errors are thrown during the build, not later
662 curve_->curve()->survivalProbability(QL_EPSILON);
663
664 LOG("Finished building default curve of type Benchmark for curve " << curveID);
665}
666
667void DefaultCurve::buildMultiSectionCurve(const std::string& curveID, const DefaultCurveConfig::Config& config,
668 const Date& asof, const DefaultCurveSpec& spec, const Loader& loader,
669 map<string, QuantLib::ext::shared_ptr<DefaultCurve>>& defaultCurves) {
670 LOG("Start building default curve of type MultiSection for curve " << curveID);
671
672 std::vector<Handle<DefaultProbabilityTermStructure>> curves;
673 std::vector<Handle<Quote>> recoveryRates;
674 std::vector<Date> switchDates;
675
676 for (auto const& s : config.multiSectionSourceCurveIds()) {
677 auto it = defaultCurves.find(s);
678 QL_REQUIRE(it != defaultCurves.end(),
679 "The multi section source curve " << s << " required for " << spec.name() << " was not found.");
680 curves.push_back(Handle<DefaultProbabilityTermStructure>(it->second->creditCurve()->curve()));
681 recoveryRates.push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(it->second->recoveryRate())));
682 }
683
684 for (auto const& d : config.multiSectionSwitchDates()) {
685 switchDates.push_back(parseDate(d));
686 }
687
688 Handle<Quote> recoveryRate(QuantLib::ext::make_shared<SimpleQuote>(recoveryRate_));
689 LOG("DefaultCurve: set up multi section curve with " << curves.size() << " sections");
690 curve_ = QuantLib::ext::make_shared<QuantExt::CreditCurve>(
691 Handle<DefaultProbabilityTermStructure>(QuantLib::ext::make_shared<QuantExt::MultiSectionDefaultCurve>(
692 curves, recoveryRates, switchDates, recoveryRate, config.dayCounter(), config.extrapolation())));
693
694 LOG("Finished building default curve of type MultiSection for curve " << curveID);
695}
696
697void DefaultCurve::buildTransitionMatrixCurve(const std::string& curveID, const DefaultCurveConfig::Config& config,
698 const Date& asof, const DefaultCurveSpec& spec, const Loader& loader,
699 map<string, QuantLib::ext::shared_ptr<DefaultCurve>>& defaultCurves) {
700 LOG("Start building default curve of type TransitionMatrix for curve " << curveID);
701 Size dim = config.states().size();
702 QL_REQUIRE(dim >= 2,
703 "DefaultCurve::buildTransitionMatrixCurve(): transition matrix dimension >= 2 required, found " << dim);
704 Matrix transitionMatrix(dim, dim, Null<Real>());
705 map<string, Size> stateIndex;
706 for (Size i = 0; i < config.states().size(); ++i)
707 stateIndex[config.states()[i]] = i;
708 QL_REQUIRE(!config.cdsQuotes().empty(), "DefaultCurve::buildTransitionMatrixCurve(): not quotes given.");
709 std::vector<std::string> tmp;
710 std::transform(config.cdsQuotes().begin(), config.cdsQuotes().end(), std::back_inserter(tmp),
711 [](const std::pair<std::string, bool>& p) { return p.first; });
712 auto wildcard = getUniqueWildcard(tmp);
713 std::set<QuantLib::ext::shared_ptr<MarketDatum>> mdData;
714 if (wildcard) {
715 mdData = loader.get(*wildcard, asof);
716 } else {
717 for (auto const& q : config.cdsQuotes()) {
718 if (auto m = loader.get(q, asof))
719 mdData.insert(m);
720 }
721 }
722 for (const auto& md : mdData) {
723 QL_REQUIRE(md->instrumentType() == MarketDatum::InstrumentType::RATING,
724 "DefaultCurve::buildTransitionMatrixCurve(): quote instrument type must be RATING.");
725 QuantLib::ext::shared_ptr<TransitionProbabilityQuote> q = QuantLib::ext::dynamic_pointer_cast<TransitionProbabilityQuote>(md);
726 Size i = stateIndex[q->fromRating()];
727 Size j = stateIndex[q->toRating()];
728 transitionMatrix[i][j] = q->quote()->value();
729 }
730 for (Size i = 0; i < dim; ++i) {
731 for (Size j = 0; j < dim; ++j) {
732 QL_REQUIRE(transitionMatrix[i][j] != Null<Real>(),
733 "DefaultCurve::buildTransitionMatrixCurve():matrix element "
734 << config.states()[i] << " -> " << config.states()[j] << " missing in market data");
735 }
736 }
737 Size initialStateIndex = stateIndex[config.initialState()];
738 curve_ = QuantLib::ext::make_shared<QuantExt::CreditCurve>(
739 Handle<DefaultProbabilityTermStructure>(QuantLib::ext::make_shared<QuantExt::GeneratorDefaultProbabilityTermStructure>(
740 QuantExt::GeneratorDefaultProbabilityTermStructure::MatrixType::Transition, transitionMatrix,
741 initialStateIndex, asof)));
742 if (recoveryRate_ == Null<Real>())
743 recoveryRate_ = 0.0;
744 LOG("Finished building default curve of type TransitionMatrix for curve " << curveID);
745}
746
747void DefaultCurve::buildNullCurve(const std::string& curveID, const DefaultCurveConfig::Config& config,
748 const Date& asof, const DefaultCurveSpec& spec) {
749 LOG("Start building null default curve for " << curveID);
750 curve_ = QuantLib::ext::make_shared<QuantExt::CreditCurve>(Handle<DefaultProbabilityTermStructure>(
751 QuantLib::ext::make_shared<QuantLib::FlatHazardRate>(asof, 0.0, config.dayCounter())));
752 recoveryRate_ = 0.0;
753 LOG("Finished building default curve of type Null for curve " << curveID);
754}
755
756} // namespace data
757} // namespace ore
QuantLib::Real maxFactor() const
QuantLib::Size dontThrowSteps() const
QuantLib::Real globalAccuracy() const
QuantLib::Real accuracy() const
QuantLib::Real minFactor() const
QuantLib::Size maxAttempts() const
Container class for all Curve Configurations.
const std::string & curveConfigID() const
Definition: curvespec.hpp:83
string name() const
returns the unique curve name
Definition: curvespec.hpp:78
Type
Supported default curve types.
const QuantLib::Period & indexTerm() const
const QuantLib::Date & startDate() const
const vector< string > & multiSectionSwitchDates() const
const boost::optional< bool > & implyDefaultFromMarket() const
const std::vector< string > & pillars() const
const vector< string > & states() const
const std::vector< std::pair< std::string, bool > > & cdsQuotes() const
const vector< string > & multiSectionSourceCurveIds() const
const BootstrapConfig & bootstrapConfig() const
QuantLib::ext::shared_ptr< QuantExt::CreditCurve > curve_
DefaultCurve()
Default constructor.
void buildHazardRateCurve(const std::string &curveID, const DefaultCurveConfig::Config &config, const QuantLib::Date &asof, const DefaultCurveSpec &spec, const Loader &loader)
Build a default curve from hazard rate quotes.
const DefaultCurveSpec & spec() const
void buildMultiSectionCurve(const std::string &curveID, const DefaultCurveConfig::Config &config, const Date &asof, const DefaultCurveSpec &spec, const Loader &loader, map< string, QuantLib::ext::shared_ptr< DefaultCurve > > &defaultCurves)
Build a multi section curve.
void buildCdsCurve(const std::string &curveID, const DefaultCurveConfig::Config &config, const QuantLib::Date &asof, const DefaultCurveSpec &spec, const Loader &loader, std::map< std::string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves)
Build a default curve from CDS spread quotes.
void buildBenchmarkCurve(const std::string &curveID, const DefaultCurveConfig::Config &config, const QuantLib::Date &asof, const DefaultCurveSpec &spec, const Loader &loader, std::map< std::string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves)
Build a default curve implied from a spread over a benchmark curve.
void buildTransitionMatrixCurve(const std::string &curveID, const DefaultCurveConfig::Config &config, const Date &asof, const DefaultCurveSpec &spec, const Loader &loader, map< string, QuantLib::ext::shared_ptr< DefaultCurve > > &defaultCurves)
void buildNullCurve(const std::string &curveID, const DefaultCurveConfig::Config &config, const Date &asof, const DefaultCurveSpec &spec)
Build a null curve (null rate, null recovery)
Default curve description.
Definition: curvespec.hpp:132
Market data loader base class.
Definition: loader.hpp:47
virtual QuantLib::ext::shared_ptr< MarketDatum > get(const std::string &name, const QuantLib::Date &d) const
get quote by its unique name, throws if not existent, override in derived classes for performance
Definition: loader.cpp:24
virtual bool has(const std::string &name, const QuantLib::Date &d) const
Default implementation, returns false if get throws or returns a null pointer.
Definition: loader.cpp:53
InstrumentType
Supported market instrument types.
Definition: marketdatum.hpp:82
QuoteType
Supported market quote types.
bool hasWildcard() const
Definition: wildcard.cpp:62
const std::string & pattern() const
Definition: wildcard.cpp:80
bool matches(const std::string &s) const
Definition: wildcard.cpp:68
SafeStack< ValueType > value
Wrapper class for building Default curves.
bool tryParseReal(const string &s, QuantLib::Real &result)
Attempt to convert text to Real.
Definition: parsers.cpp:126
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Definition: parsers.cpp:51
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Definition: parsers.cpp:171
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
bool operator<(const Dividend &d1, const Dividend &d2)
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)
Size size(const ValueType &v)
Definition: value.cpp:145
boost::optional< Wildcard > getUniqueWildcard(const C &c)
checks if at most one element in C has a wild card and returns it in this case
Definition: wildcard.hpp:65
Serializable Credit Default Swap.
Definition: namespaces.docs:23
QuantLib::DayCounter lastPeriodDayCounter
QuantLib::DateGeneration::Rule rule
QuantLib::DayCounter dayCounter
QuantLib::Natural cashSettlementDays
QuantLib::BusinessDayConvention termConvention
QuantLib::Calendar calendar
QuantLib::BusinessDayConvention payConvention
QuantLib::BusinessDayConvention convention
vector< string > curveConfigs
string name
utilities for wildcard handling
Wrapper class for QuantLib term structures.