Skip to content

Commit a20e033

Browse files
committed
Merge branch 'bugfix/QPR-13691' into 'master'
QPR-13691 refactoring implydefaultsfrommarket during default curves building Closes QPR-13691 See merge request qs/oreplus!3068
2 parents 10d4b16 + 4bf5d62 commit a20e033

2 files changed

Lines changed: 81 additions & 61 deletions

File tree

OREData/ored/marketdata/defaultcurve.cpp

Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -232,79 +232,98 @@ DefaultCurve::DefaultCurve(Date asof, DefaultCurveSpec spec, const Loader& loade
232232
const QuantLib::ext::shared_ptr<DefaultCurveConfig>& configs = curveConfigs.defaultCurveConfig(spec.curveConfigID());
233233
bool built = false;
234234
std::string errors;
235-
for (auto const& config : configs->configs()) {
236-
try {
237-
recoveryRate_ = Null<Real>();
238-
if (!config.second.recoveryRateQuote().empty()) {
239-
// handle case where the recovery rate is hardcoded in the curve config
240-
if (!tryParseReal(config.second.recoveryRateQuote(), recoveryRate_)) {
241-
Wildcard wc(config.second.recoveryRateQuote());
242-
if (wc.hasWildcard()) {
243-
for (auto const& q : loader.get(wc, asof)) {
244-
if (wc.matches(q->name())) {
245-
QL_REQUIRE(
246-
recoveryRate_ == Null<Real>() ||
247-
QuantLib::close_enough(recoveryRate_, q->quote()->value()),
248-
"There is more than one recovery rate with different values matching the pattern '"
249-
<< wc.pattern() << "', values: " << recoveryRate_ << ", "
250-
<< q->quote()->value());
251-
recoveryRate_ = q->quote()->value();
235+
// Try building the curve with each config in turn, until one works
236+
// run first all configs with implyDefaultFromMarket false, only if all configurations have failed
237+
// we retry CDSSpread and Price configurations that have implyDefaultFromMarket true
238+
std::set<size_t> configsWithImplyDefaultFromMarket;
239+
for (auto const& implyDefaultFromMarket : {false, true}) {
240+
for (auto const& config : configs->configs()) {
241+
if (implyDefaultFromMarket && ((config.second.type() != DefaultCurveConfig::Config::Type::SpreadCDS &&
242+
config.second.type() != DefaultCurveConfig::Config::Type::Price) ||
243+
!config.second.implyDefaultFromMarket().value_or(false))) {
244+
// For the second pass we only want SpreadCDS/Price configs that have
245+
// implyDefaultFromMarket set to true
246+
continue;
247+
}
248+
try {
249+
recoveryRate_ = Null<Real>();
250+
if (!config.second.recoveryRateQuote().empty()) {
251+
// handle case where the recovery rate is hardcoded in the curve config
252+
if (!tryParseReal(config.second.recoveryRateQuote(), recoveryRate_)) {
253+
Wildcard wc(config.second.recoveryRateQuote());
254+
if (wc.hasWildcard()) {
255+
for (auto const& q : loader.get(wc, asof)) {
256+
if (wc.matches(q->name())) {
257+
QL_REQUIRE(recoveryRate_ == Null<Real>() ||
258+
QuantLib::close_enough(recoveryRate_, q->quote()->value()),
259+
"There is more than one recovery rate with different values matching "
260+
"the pattern '"
261+
<< wc.pattern() << "', values: " << recoveryRate_ << ", "
262+
<< q->quote()->value());
263+
recoveryRate_ = q->quote()->value();
264+
}
252265
}
266+
} else {
267+
QL_REQUIRE(loader.has(config.second.recoveryRateQuote(), asof),
268+
"There is no market data for the requested recovery rate "
269+
<< config.second.recoveryRateQuote());
270+
recoveryRate_ = loader.get(config.second.recoveryRateQuote(), asof)->quote()->value();
253271
}
254-
} else {
255-
QL_REQUIRE(loader.has(config.second.recoveryRateQuote(), asof),
256-
"There is no market data for the requested recovery rate "
257-
<< config.second.recoveryRateQuote());
258-
recoveryRate_ = loader.get(config.second.recoveryRateQuote(), asof)->quote()->value();
259272
}
260273
}
261-
}
262-
// Build the default curve of the requested type
263-
switch (config.second.type()) {
264-
case DefaultCurveConfig::Config::Type::SpreadCDS:
265-
case DefaultCurveConfig::Config::Type::ConvSpreadCDS:
266-
case DefaultCurveConfig::Config::Type::Price:
267-
buildCdsCurve(configs->curveID(), config.second, asof, spec, loader, yieldCurves);
268-
break;
269-
case DefaultCurveConfig::Config::Type::HazardRate:
270-
buildHazardRateCurve(configs->curveID(), config.second, asof, spec, loader);
271-
break;
272-
case DefaultCurveConfig::Config::Type::Benchmark:
273-
buildBenchmarkCurve(configs->curveID(), config.second, asof, spec, loader, yieldCurves);
274-
break;
275-
case DefaultCurveConfig::Config::Type::MultiSection:
276-
buildMultiSectionCurve(configs->curveID(), config.second, asof, spec, loader, defaultCurves);
277-
break;
278-
case DefaultCurveConfig::Config::Type::TransitionMatrix:
279-
buildTransitionMatrixCurve(configs->curveID(), config.second, asof, spec, loader, defaultCurves);
280-
break;
281-
case DefaultCurveConfig::Config::Type::Null:
282-
buildNullCurve(configs->curveID(), config.second, asof, spec);
274+
// Build the default curve of the requested type
275+
switch (config.second.type()) {
276+
case DefaultCurveConfig::Config::Type::SpreadCDS:
277+
case DefaultCurveConfig::Config::Type::ConvSpreadCDS:
278+
case DefaultCurveConfig::Config::Type::Price:
279+
buildCdsCurve(configs->curveID(), config.second, asof, spec, loader, yieldCurves,
280+
implyDefaultFromMarket);
281+
break;
282+
case DefaultCurveConfig::Config::Type::HazardRate:
283+
buildHazardRateCurve(configs->curveID(), config.second, asof, spec, loader);
284+
break;
285+
case DefaultCurveConfig::Config::Type::Benchmark:
286+
buildBenchmarkCurve(configs->curveID(), config.second, asof, spec, loader, yieldCurves);
287+
break;
288+
case DefaultCurveConfig::Config::Type::MultiSection:
289+
buildMultiSectionCurve(configs->curveID(), config.second, asof, spec, loader, defaultCurves);
290+
break;
291+
case DefaultCurveConfig::Config::Type::TransitionMatrix:
292+
buildTransitionMatrixCurve(configs->curveID(), config.second, asof, spec, loader, defaultCurves);
293+
break;
294+
case DefaultCurveConfig::Config::Type::Null:
295+
buildNullCurve(configs->curveID(), config.second, asof, spec);
296+
break;
297+
default:
298+
QL_FAIL("The DefaultCurveConfig type " << static_cast<int>(config.second.type())
299+
<< " was not recognised");
300+
}
301+
built = true;
283302
break;
284-
default:
285-
QL_FAIL("The DefaultCurveConfig type " << static_cast<int>(config.second.type())
286-
<< " was not recognised");
303+
} catch (exception& e) {
304+
std::ostringstream message;
305+
message << "build attempt failed for " << configs->curveID() << " using config with priority "
306+
<< config.first << ": " << e.what()
307+
<< " and implyDefaultFromMarket= " << to_string(implyDefaultFromMarket);
308+
DLOG(message.str());
309+
if (!errors.empty())
310+
errors += ", ";
311+
errors += message.str();
287312
}
288-
built = true;
289-
break;
290-
} catch (exception& e) {
291-
std::ostringstream message;
292-
message << "build attempt failed for " << configs->curveID() << " using config with priority "
293-
<< config.first << ": " << e.what();
294-
DLOG(message.str());
295-
if (!errors.empty())
296-
errors += ", ";
297-
errors += message.str();
298313
}
314+
if (built)
315+
break;
299316
}
300317
QL_REQUIRE(built, "default curve building failed for " << spec.curveConfigID() << ": " << errors);
301318
}
302319

303320
void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveConfig::Config& config, const Date& asof,
304321
const DefaultCurveSpec& spec, const Loader& loader,
305-
map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves) {
322+
map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
323+
bool implyDefaultFromMarket) {
306324

307-
LOG("Start building default curve of type SpreadCDS for curve " << curveID);
325+
LOG("Start building default curve of type SpreadCDS for curve " << curveID << "and implyDefaultFromMarket = "
326+
<< to_string(implyDefaultFromMarket));
308327

309328
QL_REQUIRE(config.type() == DefaultCurveConfig::Config::Type::SpreadCDS ||
310329
config.type() == DefaultCurveConfig::Config::Type::Price ||
@@ -345,7 +364,7 @@ void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveC
345364
refData.cashSettlementDays = cdsConv->upfrontSettlementDays();
346365

347366
// If the configuration instructs us to imply a default from the market data, we do it here.
348-
if (config.implyDefaultFromMarket() && *config.implyDefaultFromMarket()) {
367+
if (implyDefaultFromMarket) {
349368
if (recoveryRate_ != Null<Real>() && quotes.empty()) {
350369
// Assume entity is in default, between event determination date and auction date. Build a survival
351370
// probability curve with value 0.0 tomorrow to approximate this and allow dependent instruments to price.

OREData/ored/marketdata/defaultcurve.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ class DefaultCurve {
6969
//! Build a default curve from CDS spread quotes
7070
void buildCdsCurve(const std::string& curveID, const DefaultCurveConfig::Config& config, const QuantLib::Date& asof,
7171
const DefaultCurveSpec& spec, const Loader& loader,
72-
std::map<std::string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves);
72+
std::map<std::string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
73+
bool implyDefaultFromMarket);
7374

7475
//! Build a default curve from hazard rate quotes
7576
void buildHazardRateCurve(const std::string& curveID, const DefaultCurveConfig::Config& config,

0 commit comments

Comments
 (0)