Skip to content

Commit 8907cf4

Browse files
author
Sebastien Bouvard
committed
Merge branch 'feature/QPR-12830' into 'master'
QPR-12830, QPR-13094 Support IsdaCdsEngine + Conv Spreads + Assumed RR Closes QPR-12830 See merge request qs/oreplus!2911
2 parents 97043d3 + 718f485 commit 8907cf4

25 files changed

Lines changed: 193 additions & 297 deletions

Docs/UserGuide/curve_configurations/default_curves_from_cds.tex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ \subsubsection{Default Curves from CDS}
1515
The default curve's currency.
1616

1717
\item \lstinline!Type!:
18-
For a default curve built from CDS, the \lstinline!Type! should be set to \lstinline!SpreadCDS! if the \lstinline!Quotes! reference CDS spread quotes or \lstinline!Price! if the \lstinline!Quotes! reference upfront price quotes.
18+
For a default curve built from CDS, the \lstinline!Type! should be set to \lstinline!SpreadCDS! if the \lstinline!Quotes! reference CDS spread quotes or \lstinline!Price! if the \lstinline!Quotes! reference upfront price quotes or \lstinline!ConvSpreadCDS! if the \lstinline!Quotes! reference Conventional CDS spread quotes. Note that if ConvSpreadCDS or Price is used, the model will be IsdaCdsEngine. Else, MidPointCdsEngine.
1919

2020
\item \lstinline!DiscountCurve!:
2121
A reference to a valid discount curve specification that will be used to discount cashflows during the bootstrap process. It should be of the form \lstinline!Yield/Currency/curve_name! where \lstinline!curve_name! is the name of a yield curve defined in the yield curve configurations.
@@ -43,7 +43,7 @@ \subsubsection{Default Curves from CDS}
4343
using the adjacent terms of the provided quotes.
4444

4545
\item \lstinline!Quotes!:
46-
The \lstinline!Quotes! element should be populated with a list of valid \lstinline!Quote! elements. If the \lstinline!Type! is \lstinline!SpreadCDS!, the quotes should be CDS spread quote strings as documented in Section \ref{md:cds_spread_quote} and if \lstinline!Type! is \lstinline!Price!, the quotes should be CDS upfront price quote strings as documented in Section \ref{md:cds_price_quote}. The attribute \lstinline!optional! in the \lstinline!Quote! element should be set to \lstinline!true! if the associated quote is optional and set to \lstinline!false! if the associated quote is mandatory. If a quote is mandatory and not found in the market, the default curve building will fail. The attribute \lstinline!optional! may be omitted from the quote element. In this case, it defaults to \lstinline!false! and the quote is mandatory. Note also that instead of a list of explicit quotes, a single quote may be provided with the wildcard character \lstinline!*!. In this case, the market is searched for quotes matching the pattern. For example, \lstinline!CDS/CREDIT_SPREAD/JPM/SNRFOR/USD/XR14/*! would return all quotes in the market that start with \lstinline!CDS/CREDIT_SPREAD/JPM/SNRFOR/USD/XR14!.
46+
The \lstinline!Quotes! element should be populated with a list of valid \lstinline!Quote! elements. If the \lstinline!Type! is \lstinline!SpreadCDS!, the quotes should be CDS spread quote strings as documented in Section \ref{md:cds_spread_quote} and if \lstinline!Type! is \lstinline!Price!, the quotes should be CDS upfront price quote strings as documented in Section \ref{md:cds_price_quote} and If the \lstinline!Type! is \lstinline!ConvSpreadCDS!, the quotes should be Conv CDS spread quote strings as documented in Section \ref{md:cds_spread_quote}. The attribute \lstinline!optional! in the \lstinline!Quote! element should be set to \lstinline!true! if the associated quote is optional and set to \lstinline!false! if the associated quote is mandatory. If a quote is mandatory and not found in the market, the default curve building will fail. The attribute \lstinline!optional! may be omitted from the quote element. In this case, it defaults to \lstinline!false! and the quote is mandatory. Note also that instead of a list of explicit quotes, a single quote may be provided with the wildcard character \lstinline!*!. In this case, the market is searched for quotes matching the pattern. For example, \lstinline!CDS/CREDIT_SPREAD/JPM/SNRFOR/USD/XR14/*! would return all quotes in the market that start with \lstinline!CDS/CREDIT_SPREAD/JPM/SNRFOR/USD/XR14!.
4747

4848
\item \lstinline!Conventions!:
4949
The name of a valid set of CDS conventions, as documented in Section \ref{sss:cds_conventions}, to use in the bootstrap.

Docs/UserGuide/marketdata.tex

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ \section{Market Data}\label{sec:market_data}
3232
\hline
3333
Instrument Type & \emph{ZERO, DISCOUNT, MM, MM\_FUTURE, FRA, IMM\_FRA,
3434
IR\_SWAP, BASIS\_SWAP, CC\_BASIS\_SWAP, CDS, CDS\_INDEX, FX\_SPOT, FX\_FWD,
35-
SWAPTION, CAPFLOOR, FX\_OPTION, HAZARD\_RATE, RECOVERY\_RATE,
35+
SWAPTION, CAPFLOOR, FX\_OPTION, HAZARD\_RATE, RECOVERY\_RATE, ASSUMED\_RECOVERY\_RATE,
3636
ZC\_INFLATIONSWAP, YY\_INFLATIONSWAP, ZC\_INFLATIONCAPFLOOR,
3737
SEASONALITY, EQUITY\_SPOT, EQUITY\_FWD, EQUITY\_DIVIDEND,
3838
EQUITY\_OPTION, BOND, INDEX\_CDS\_OPTION, CPR, COMMODITY, COMMODITY\_FWD, COMMODITY\_OPTION} \\
3939
\hline
40-
Quote Type & \emph{BASIS\_SPREAD, CREDIT\_SPREAD, YIELD\_SPREAD, HAZARD\_RATE,
40+
Quote Type & \emph{BASIS\_SPREAD, CREDIT\_SPREAD, CONV\_CREDIT\_SPREAD, YIELD\_SPREAD, HAZARD\_RATE,
4141
RATE, RATIO, PRICE, RATE\_LNVOL, RATE\_NVOL, RATE\_SLNVOL,
4242
BASE\_CORRELATION, SHIFT} \\
4343
\hline
@@ -678,6 +678,14 @@ \subsection{CDS Spread}
678678
\item \lstinline!CDS/CREDIT_SPREAD/RBS/SUBLT2/EUR/MR14/10Y/500!
679679
\item \lstinline!CDS/CREDIT_SPREAD/RBS/SUBLT2/EUR/1Y!
680680
\item \lstinline!CDS/CREDIT_SPREAD/RBS/SUBLT2/EUR/1Y/500!
681+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/JPM/SNRFOR/USD/5Y!
682+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/JPM/SNRFOR/USD/5Y/100!
683+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/JPM/SNRFOR/USD/XR14/5Y!
684+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/JPM/SNRFOR/USD/XR14/5Y/100!
685+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/RBS/SUBLT2/EUR/MR14/10Y!
686+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/RBS/SUBLT2/EUR/MR14/10Y/500!
687+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/RBS/SUBLT2/EUR/1Y!
688+
\item \lstinline!CDS/CONV_CREDIT_SPREAD/RBS/SUBLT2/EUR/1Y/500!
681689
\end{itemize}
682690

683691
\subsection{CDS Upfront Price}
@@ -721,7 +729,7 @@ \subsection{CDS Recovery Rate}
721729
\begin{tabular}{|p{3cm}|p{3.5cm}|p{7cm}|}
722730
\hline
723731
{\bf Property} & {\bf Allowable values} & {\bf Description} \\ \hline
724-
Instrument Type & \lstinline!RECOVERY_RATE! & \\ \hline
732+
Instrument Type & \lstinline!RECOVERY_RATE! or \lstinline!ASSUMED_RECOVERY_RATE! & \\ \hline
725733
Quote Type & \lstinline!RATE! & \\ \hline
726734
Entity & String & The CDS reference entity name \\ \hline
727735
Tier & String & The CDS tier \\ \hline
@@ -772,17 +780,20 @@ \subsection{Security Recovery Rate}\label{md:sec_rec_rates}
772780
\begin{tabular}{|p{3cm}|p{3.5cm}|p{7cm}|}
773781
\hline
774782
{\bf Property} & {\bf Allowable values} & {\bf Description} \\ \hline
775-
Instrument Type & \emph{RECOVERY\_RATE} & \\ \hline
783+
Instrument Type & \emph{RECOVERY\_RATE} or \emph{ASSUMED\_RECOVERY\_RATE} & \\ \hline
776784
Quote Type & \emph{RATE} & \\ \hline
777785
ID & String & Security ID \\ \hline
778786
\end{tabular}
779787
\caption{Security Recovery Rate}
780788
\label{tab:secrecrate_quote}
781789
\end{table}
782790

791+
Currently, only par (real) recovery rates (quote type \lstinline!RECOVERY_RATE!) are supported.
792+
783793
Example:
784794
\begin{itemize}
785795
\item {RECOVERY\_RATE/RATE/SECURITY\_1}
796+
\item {ASSUMED\_RECOVERY\_RATE/RATE/SECURITY\_1}
786797
\end{itemize}
787798

788799
% %- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

ORE-SWIG/QuantLib-SWIG

OREAnalytics/test/testmarket.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ parRateCurveHelpers(const string& name, const vector<Period>& parTenor, const ve
180180
BOOST_ASSERT(*exDiscount);
181181
rateHelper = QuantLib::ext::make_shared<QuantExt::SpreadCdsHelper>(
182182
parRateQuote, tenor, cdsConv->settlementDays(), cdsConv->calendar(), cdsConv->frequency(),
183-
cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(), recoveryRate, exDiscount,
183+
cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(), recoveryRate, exDiscount, CreditDefaultSwap::PricingModel::Midpoint,
184184
true, QuantExt::CreditDefaultSwap::ProtectionPaymentTime::atDefault, today + cdsConv->settlementDays());
185185
instruments.push_back(rateHelper);
186186
}

OREData/ored/configuration/defaultcurveconfig.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ void DefaultCurveConfig::Config::fromXML(XMLNode* node) {
139139
type_ = Type::HazardRate;
140140
} else if (type == "Price") {
141141
type_ = Type::Price;
142+
} else if (type == "ConvSpreadCDS") {
143+
type_ = Type::ConvSpreadCDS;
142144
} else if (type == "Benchmark") {
143145
type_ = Type::Benchmark;
144146
} else if (type == "MultiSection") {
@@ -201,10 +203,10 @@ void DefaultCurveConfig::Config::fromXML(XMLNode* node) {
201203
// Read the optional start date
202204
string d = XMLUtils::getChildValue(node, "StartDate", false);
203205
if (d != "") {
204-
if (type_ == Type::SpreadCDS || type_ == Type::Price) {
206+
if (type_ == Type::SpreadCDS || type_ == Type::Price || type_ == Type::ConvSpreadCDS) {
205207
startDate_ = parseDate(d);
206208
} else {
207-
WLOG("'StartDate' is only used when type is 'SpreadCDS' or 'Price'");
209+
WLOG("'StartDate' is only used when type is 'SpreadCDS' or 'Price' or 'ConvSpreadCDS'");
208210
}
209211
}
210212
string s = XMLUtils::getChildValue(node, "RunningSpread", false);
@@ -230,9 +232,11 @@ void DefaultCurveConfig::Config::fromXML(XMLNode* node) {
230232
XMLNode* DefaultCurveConfig::Config::toXML(XMLDocument& doc) const {
231233
XMLNode* node = doc.allocNode("Configuration");
232234
XMLUtils::addAttribute(doc, node, "priority", std::to_string(priority_));
233-
if (type_ == Type::SpreadCDS || type_ == Type::HazardRate || type_ == Type::Price) {
235+
if (type_ == Type::SpreadCDS || type_ == Type::HazardRate || type_ == Type::Price || type_ == Type::ConvSpreadCDS) {
234236
if (type_ == Type::SpreadCDS) {
235237
XMLUtils::addChild(doc, node, "Type", "SpreadCDS");
238+
} else if (type_ == Type::ConvSpreadCDS){
239+
XMLUtils::addChild(doc, node, "Type", "ConvSpreadCDS");
236240
} else if (type_ == Type::HazardRate) {
237241
XMLUtils::addChild(doc, node, "Type", "HazardRate");
238242
} else {

OREData/ored/configuration/defaultcurveconfig.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class DefaultCurveConfig : public CurveConfig {
5151
class Config : public XMLSerializable {
5252
public:
5353
//! Supported default curve types
54-
enum class Type { SpreadCDS, HazardRate, Benchmark, Price, MultiSection, TransitionMatrix, Null };
54+
enum class Type { SpreadCDS, ConvSpreadCDS, HazardRate, Benchmark, Price, MultiSection, TransitionMatrix, Null };
5555
Config(const Type& type, const string& discountCurveID, const string& recoveryRateQuote,
5656
const DayCounter& dayCounter, const string& conventionID,
5757
const std::vector<std::pair<std::string, bool>>& cdsQuotes = {}, bool extrapolation = true,

OREData/ored/marketdata/defaultcurve.cpp

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
#include <qle/termstructures/generatordefaulttermstructure.hpp>
2525
#include <qle/termstructures/interpolatedhazardratecurve.hpp>
26-
#include <qle/termstructures/interpolatedsurvivalprobabilitycurve.hpp>
2726
#include <qle/termstructures/iterativebootstrap.hpp>
2827
#include <qle/termstructures/multisectiondefaultcurve.hpp>
2928
#include <qle/termstructures/probabilitytraits.hpp>
@@ -34,6 +33,7 @@
3433
#include <ql/math/interpolations/backwardflatinterpolation.hpp>
3534
#include <ql/math/interpolations/loginterpolation.hpp>
3635
#include <ql/termstructures/credit/defaultprobabilityhelpers.hpp>
36+
#include <ql/termstructures/credit/interpolatedsurvivalprobabilitycurve.hpp>
3737
#include <ql/termstructures/credit/flathazardrate.hpp>
3838
#include <ql/time/daycounters/actual365fixed.hpp>
3939

@@ -103,14 +103,12 @@ set<QuoteData> getRegexQuotes(const Wildcard& wc, const string& configId, Defaul
103103
auto mdqt = md->quoteType();
104104

105105
// 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 &&
106+
if (((type == DCCT::SpreadCDS && mdit == MDIT::CDS)||(type == DCCT::ConvSpreadCDS && mdit == MDIT::CDS)) &&
107107
(mdqt == MDQT::CREDIT_SPREAD || mdqt == MDQT::CONV_CREDIT_SPREAD)) {
108108

109109
auto q = QuantLib::ext::dynamic_pointer_cast<CdsQuote>(md);
110110
QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to CdsQuote");
111111
if (wc.matches(q->name())) {
112-
QL_REQUIRE(mdqt != MDQT::CONV_CREDIT_SPREAD,
113-
"Conventional credit spread are currently not supported for default curves");
114112
addQuote(result, configId, q->name(), q->term(), q->quote()->value(), q->seniority(), q->ccy(),
115113
q->docClause(), q->runningSpread());
116114
}
@@ -205,7 +203,7 @@ set<QuoteData> getConfiguredQuotes(const std::string& curveID, const DefaultCurv
205203

206204
using DCCT = DefaultCurveConfig::Config::Type;
207205
auto type = config.type();
208-
QL_REQUIRE(type == DCCT::SpreadCDS || type == DCCT::Price || type == DCCT::HazardRate,
206+
QL_REQUIRE(type == DCCT::SpreadCDS || type == DCCT::Price || type == DCCT::HazardRate || type == DCCT::ConvSpreadCDS,
209207
"getConfiguredQuotes expects a curve type of SpreadCDS, Price or HazardRate.");
210208
QL_REQUIRE(!config.cdsQuotes().empty(), "No quotes configured for curve " << curveID);
211209

@@ -264,6 +262,7 @@ DefaultCurve::DefaultCurve(Date asof, DefaultCurveSpec spec, const Loader& loade
264262
// Build the default curve of the requested type
265263
switch (config.second.type()) {
266264
case DefaultCurveConfig::Config::Type::SpreadCDS:
265+
case DefaultCurveConfig::Config::Type::ConvSpreadCDS:
267266
case DefaultCurveConfig::Config::Type::Price:
268267
buildCdsCurve(configs->curveID(), config.second, asof, spec, loader, yieldCurves);
269268
break;
@@ -308,7 +307,8 @@ void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveC
308307
LOG("Start building default curve of type SpreadCDS for curve " << curveID);
309308

310309
QL_REQUIRE(config.type() == DefaultCurveConfig::Config::Type::SpreadCDS ||
311-
config.type() == DefaultCurveConfig::Config::Type::Price,
310+
config.type() == DefaultCurveConfig::Config::Type::Price ||
311+
config.type() == DefaultCurveConfig::Config::Type::ConvSpreadCDS,
312312
"DefaultCurve::buildCdsCurve expected a default curve configuration with type SpreadCDS/Price");
313313
QL_REQUIRE(recoveryRate_ != Null<Real>(), "DefaultCurve: recovery rate needed to build SpreadCDS curve");
314314

@@ -378,6 +378,7 @@ void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveC
378378
: QuantExt::CreditDefaultSwap::atPeriodEnd;
379379

380380
if (config.type() == DefaultCurveConfig::Config::Type::SpreadCDS) {
381+
refData.type = "SpreadCDS";
381382
for (auto quote : quotes) {
382383
try {
383384
if ((cdsConv->rule() == DateGeneration::CDS || cdsConv->rule() == DateGeneration::CDS2015 ||
@@ -393,7 +394,7 @@ void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveC
393394
};
394395
helpers.push_back(QuantLib::ext::make_shared<SpreadCdsHelper>(
395396
quote.value, quote.term, cdsConv->settlementDays(), cdsConv->calendar(), cdsConv->frequency(),
396-
cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(), recoveryRate_, discountCurve,
397+
cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(), recoveryRate_, discountCurve, CreditDefaultSwap::PricingModel::Midpoint,
397398
cdsConv->settlesAccrual(), ppt, config.startDate(), cdsConv->lastPeriodDayCounter()));
398399
runningSpread = config.runningSpread();
399400
helperQuoteTerms[helpers.back()->latestDate()] = quote.term;
@@ -408,7 +409,41 @@ void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveC
408409
}
409410
}
410411
}
411-
} else {
412+
}else if(config.type() == DefaultCurveConfig::Config::Type::ConvSpreadCDS){
413+
refData.type = "ConvSpreadCDS";
414+
// Currently same than SpreadCDS
415+
for (auto quote : quotes) {
416+
try {
417+
if ((cdsConv->rule() == DateGeneration::CDS || cdsConv->rule() == DateGeneration::CDS2015 ||
418+
cdsConv->rule() == DateGeneration::OldCDS) &&
419+
cdsMaturity(asof, quote.term, cdsConv->rule()) <= asof + 1 * Days) {
420+
auto maturity = cdsMaturity(asof, quote.term, cdsConv->rule());
421+
WLOG("DefaultCurve:: SKIP cds with term "
422+
<< quote.term << " because cds maturity (" << io::iso_date(maturity)
423+
<< ") is <= T + 1 (T =" << io::iso_date(asof)
424+
<< "), but by standard conventioons the first CDS payment is the next IMM payment"
425+
"date strictly after T + 1.");
426+
continue;
427+
};
428+
helpers.push_back(QuantLib::ext::make_shared<SpreadCdsHelper>(
429+
quote.value, quote.term, cdsConv->settlementDays(), cdsConv->calendar(), cdsConv->frequency(),
430+
cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(), recoveryRate_, discountCurve, CreditDefaultSwap::PricingModel::ISDA,
431+
cdsConv->settlesAccrual(), ppt, config.startDate(), cdsConv->lastPeriodDayCounter()));
432+
runningSpread = config.runningSpread();
433+
helperQuoteTerms[helpers.back()->latestDate()] = quote.term;
434+
} catch (exception& e) {
435+
if (quote.term == Period(0, Months)) {
436+
WLOG("DefaultCurve:: Cannot add quote of term 0M to CDS curve " << curveID << " for asof date "
437+
<< asof);
438+
} else {
439+
QL_FAIL("DefaultCurve:: Failed to add quote of term " << quote.term << " to CDS curve " << curveID
440+
<< " for asof date " << asof
441+
<< ", with error: " << e.what());
442+
}
443+
}
444+
}
445+
}else {
446+
refData.type = "Upfront";
412447
for (auto quote : quotes) {
413448
// If there is no running spread encoded in the quote, the config must have one.
414449
runningSpread = quote.runningSpread;
@@ -421,7 +456,7 @@ void DefaultCurve::buildCdsCurve(const std::string& curveID, const DefaultCurveC
421456
auto tmp = QuantLib::ext::make_shared<UpfrontCdsHelper>(
422457
quote.value, runningSpread, quote.term, cdsConv->settlementDays(), cdsConv->calendar(),
423458
cdsConv->frequency(), cdsConv->paymentConvention(), cdsConv->rule(), cdsConv->dayCounter(),
424-
recoveryRate_, discountCurve, cdsConv->upfrontSettlementDays(), cdsConv->settlesAccrual(), ppt,
459+
recoveryRate_, discountCurve, CreditDefaultSwap::PricingModel::ISDA, cdsConv->upfrontSettlementDays(), cdsConv->settlesAccrual(), ppt,
425460
config.startDate(), cdsConv->lastPeriodDayCounter());
426461
if (tmp->latestDate() > asof) {
427462
helpers.push_back(tmp);

0 commit comments

Comments
 (0)