26#include <ql/math/comparison.hpp>
27#include <ql/math/matrix.hpp>
41void validateWeightRec(Real
value,
const string&
name,
const string& varName) {
42 QL_REQUIRE(
value <= 1.0,
"The " << varName <<
" value (" <<
value <<
") for name " <<
name <<
43 " should not be greater than 1.0.");
44 QL_REQUIRE(
value >= 0.0,
"The " << varName <<
" value (" <<
value <<
") for name " <<
name <<
45 " should not be less than 0.0.");
55 BaseCorrelationCurveSpec spec,
58 const QuantLib::ext::shared_ptr<ReferenceDataManager>& referenceData)
59 : spec_(spec), referenceData_(referenceData) {
61 DLOG(
"BaseCorrelationCurve: start building base correlation structure with ID " << spec_.curveConfigID());
65 const auto& config = *
curveConfigs.baseCorrelationCurveConfig(spec_.curveConfigID());
72 const vector<string>& termStrs = config.terms();
73 QL_REQUIRE(!termStrs.empty(),
"BaseCorrelationCurve: need at least one term.");
75 bool termWc = find(termStrs.begin(), termStrs.end(),
"*") != termStrs.end();
77 QL_REQUIRE(termStrs.size() == 1,
"BaseCorrelationCurve: only one wild card term can be specified.");
78 DLOG(
"Have term wildcard pattern " << termStrs[0]);
80 for (
const string& termStr : termStrs) {
83 DLOG(
"Parsed " << terms.size() <<
" unique configured term(s).");
89 auto cmp = [](Real dp_1, Real dp_2) {
return !
close_enough(dp_1, dp_2) && dp_1 < dp_2; };
90 set<Real,
decltype(cmp)> dps(cmp);
92 const vector<string>& dpStrs = config.detachmentPoints();
93 bool dpsWc = find(dpStrs.begin(), dpStrs.end(),
"*") != dpStrs.end();
95 QL_REQUIRE(dpStrs.size() == 1,
"BaseCorrelationCurve: only one wild card "
96 <<
"detachment point can be specified.");
97 DLOG(
"Have detachment point wildcard pattern " << dpStrs[0]);
99 for (
const string& dpStr : dpStrs) {
102 DLOG(
"Parsed " << dps.size() <<
" unique configured detachment points.");
103 QL_REQUIRE(dps.size() > 1,
"BaseCorrelationCurve: need at least 2 unique detachment points.");
108 auto mpCmp = [](pair<Period, Real> k_1, pair<Period, Real> k_2) {
109 if (k_1.first != k_2.first)
110 return k_1.first < k_2.first;
112 return !
close_enough(k_1.second, k_2.second) && k_1.second < k_2.second;
114 map<pair<Period, Real>, Handle<Quote>,
decltype(mpCmp)>
data(mpCmp);
116 std::ostringstream ss;
117 ss << MarketDatum::InstrumentType::CDS_INDEX <<
"/" << MarketDatum::QuoteType::BASE_CORRELATION <<
"/*";
118 Wildcard w(ss.str());
119 for (
const auto& md : loader.get(w, asof)) {
121 QL_REQUIRE(md->asofDate() == asof,
"MarketDatum asofDate '" << md->asofDate() <<
"' <> asof '" << asof <<
"'");
123 auto q = QuantLib::ext::dynamic_pointer_cast<BaseCorrelationQuote>(md);
124 QL_REQUIRE(q,
"Internal error: could not downcast MarketDatum '" << md->name() <<
"' to BaseCorrelationQuote");
127 if (config.quoteName() != q->cdsIndexName())
130 TLOG(
"Processing quote " << q->name() <<
": (" << q->term() <<
"," << fixed << setprecision(9)
131 << q->detachmentPoint() <<
"," << q->quote()->value() <<
")");
136 auto it = terms.find(q->term());
137 if (it == terms.end())
144 auto it = dps.find(q->detachmentPoint());
150 auto key = make_pair(q->term(), q->detachmentPoint());
152 DLOG(
"Already added base correlation with term " << q->term() <<
" and detachment point " << fixed
153 << setprecision(9) << q->detachmentPoint()
154 <<
" so skipping quote " << q->name());
160 terms.insert(q->term());
162 dps.insert(q->detachmentPoint());
165 data[key] = q->quote();
167 TLOG(
"Added quote " << q->name() <<
": (" << q->term() <<
"," << fixed << setprecision(9)
168 << q->detachmentPoint() <<
"," << q->quote()->value() <<
")");
171 DLOG(
"After processing the quotes, we have " << terms.size() <<
" unique term(s), " << dps.size()
172 <<
" unique detachment points and " <<
data.size() <<
" quotes.");
173 QL_REQUIRE(dps.size() > 1,
"BaseCorrelationCurve: need at least 2 unique detachment points.");
174 QL_REQUIRE(dps.size() * terms.size() ==
data.size(),
175 "BaseCorrelationCurve: number of quotes ("
176 <<
data.size() <<
") should equal number of detachment points (" << dps.size()
177 <<
") times the number of terms (" << terms.size() <<
").");
180 vector<vector<Handle<Quote>>> quotes;
183 vector<Period> tmpTerms(terms.begin(), terms.end());
184 vector<Real> tmpDps(dps.begin(), dps.end());
186 if (config.indexTerm() != 0 * Days) {
187 vector<Time> termTimes;
188 for (
auto const& h : tmpTerms) {
193 Size termIndex_m, termIndex_p;
197 for (
const auto& dp : dps) {
198 quotes.push_back({});
199 for (
const auto& term : terms) {
200 auto key_m = make_pair(tmpTerms[termIndex_m], dp);
201 auto it_m =
data.find(key_m);
202 auto key_p = make_pair(tmpTerms[termIndex_p], dp);
203 auto it_p =
data.find(key_p);
204 QL_REQUIRE(it_m !=
data.end() && it_p !=
data.end(),
205 "BaseCorrelationCurve: do not have a quote for term "
206 << term <<
" and detachment point " << fixed << setprecision(9) << dp <<
".");
207 quotes.back().push_back(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(
208 alpha * it_m->second->value() + (1 - alpha) * it_p->second->value())));
215 for (
const auto& dp : dps) {
216 quotes.push_back({});
217 for (
const auto& term : terms) {
218 auto key = make_pair(term, dp);
219 auto it =
data.find(key);
220 QL_REQUIRE(it !=
data.end(),
"BaseCorrelationCurve: do not have a quote for term "
221 << term <<
" and detachment point " << fixed << setprecision(9)
223 quotes.back().push_back(it->second);
228 if (config.adjustForLosses()) {
229 DLOG(
"Adjust for losses is true for base correlation " << spec_.curveConfigID());
230 DLOG(
"Detachment points before: " << Array(tmpDps.begin(), tmpDps.end()));
231 tmpDps = adjustForLosses(tmpDps);
232 DLOG(
"Detachment points after: " << Array(tmpDps.begin(), tmpDps.end()));
236 QL_REQUIRE(!tmpTerms.empty(),
"No terms found");
237 tmpTerms.push_back(tmpTerms.back() + 1 * tmpTerms.back().units());
238 for (Size i = 0; i < tmpDps.size(); ++i)
239 quotes[i].push_back(quotes[i][tmpTerms.size() - 2]);
241 baseCorrelation_ = QuantLib::ext::make_shared<QuantExt::InterpolatedBaseCorrelationTermStructure<QuantExt::BilinearFlat>>(
242 config.settlementDays(), config.calendar(), config.businessDayConvention(), tmpTerms, tmpDps, quotes,
243 config.dayCounter(), config.startDate(), config.rule());
245 baseCorrelation_->enableExtrapolation(config.extrapolate());
248 }
catch (std::exception& e) {
249 QL_FAIL(
"BaseCorrelationCurve: curve building failed :" << e.what());
251 QL_FAIL(
"BaseCorrelationCurve: curve building failed: unknown error");
254 DLOG(
"BaseCorrelationCurve: finished building base correlation structure with ID " << spec_.curveConfigID());
257vector<Real> BaseCorrelationCurve::adjustForLosses(
const vector<Real>& detachPoints)
const {
259 const auto& cId = spec_.curveConfigID();
263 DLOG(
"BaseCorrelationCurve::adjustForLosses: start adjusting for losses for base correlation " << qualifier);
265 if (!referenceData_) {
266 DLOG(
"Reference data manager is null so cannot adjust for losses.");
270 if (!referenceData_->hasData(CreditIndexReferenceDatum::TYPE, qualifier)) {
271 DLOG(
"Reference data manager does not have index credit data for " << qualifier
272 <<
" so cannot adjust for losses.");
276 auto crd = QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(
277 referenceData_->getData(CreditIndexReferenceDatum::TYPE, qualifier));
279 DLOG(
"Index credit data for " << qualifier <<
" is not of correct type so cannot adjust for losses.");
284 Real totalRemainingWeight = 0.0;
285 Real totalPriorWeight = 0.0;
287 Real recovered = 0.0;
289 for (
const auto& c : crd->constituents()) {
291 const auto&
name = c.name();
292 auto weight = c.weight();
293 validateWeightRec(weight,
name,
"weight");
295 if (!close(0.0, weight)) {
296 totalRemainingWeight += weight;
298 auto priorWeight = c.priorWeight();
299 QL_REQUIRE(priorWeight != Null<Real>(),
"Expecting a valid prior weight for name " <<
name <<
".");
300 validateWeightRec(priorWeight,
name,
"prior weight");
301 auto recovery = c.recovery();
302 QL_REQUIRE(recovery != Null<Real>(),
"Expecting a valid recovery for name " <<
name <<
".");
303 validateWeightRec(recovery,
name,
"recovery");
304 lost += (1.0 - recovery) * priorWeight;
305 recovered += recovery * priorWeight;
306 totalPriorWeight += priorWeight;
310 Real totalWeight = totalRemainingWeight + totalPriorWeight;
311 TLOG(
"Total remaining weight = " << totalRemainingWeight);
312 TLOG(
"Total prior weight = " << totalPriorWeight);
313 TLOG(
"Total weight = " << totalWeight);
315 if (!close(totalRemainingWeight, 1.0) && totalRemainingWeight > 1.0) {
316 ALOG(
"Total remaining weight is greater than 1, possible error in CreditIndexReferenceDatum for " << qualifier);
319 if (!close(totalWeight, 1.0)) {
320 ALOG(
"Expected the total weight (" << totalWeight <<
" = " << totalRemainingWeight <<
" + " << totalPriorWeight
321 <<
") to equal 1, possible error in CreditIndexReferenceDatum for "
325 if (close(totalRemainingWeight, 0.0)) {
326 ALOG(
"The total remaining weight is 0 so cannot adjust for losses.");
330 if (close(totalRemainingWeight, 1.0)) {
331 DLOG(
"Index factor for " << qualifier <<
" is 1 so adjustment for losses not required.");
337 for (Size i = 0; i < detachPoints.size(); ++i) {
340 Real below = i == 0 ? 0.0 : detachPoints[i-1];
341 Real tranche = detachPoints[i] - below;
342 Real above = 1.0 - detachPoints[i];
343 Real newBelow =
max(below - lost, 0.0);
344 Real newTranche = tranche -
max(
min(recovered - above, tranche), 0.0) -
max(
min(lost - below, tranche), 0.0);
345 Real newDetach = (newBelow + newTranche) / totalRemainingWeight;
347 TLOG(
"Quoted detachment point " << detachPoints[i] <<
" adjusted to " << newDetach <<
".");
349 if (i > 0 && (newDetach < result.back() || close(newDetach, result.back()))) {
350 ALOG(
"The " << io::ordinal(i + 1) <<
" adjusted detachment point is not greater than the previous " <<
351 "adjusted detachment point so cannot adjust for losses.");
355 result.push_back(newDetach);
358 DLOG(
"BaseCorrelationCurve::adjustForLosses: finished adjusting for losses for base correlation " << qualifier);
Wrapper class for building base correlation structures.
SafeStack< ValueType > value
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Real parseReal(const string &s)
Convert text to Real.
leg data model and serialization
Classes and functions for log message handling.
#define DLOG(text)
Logging Macro (Level = Debug)
#define ALOG(text)
Logging Macro (Level = Alert)
#define TLOG(text)
Logging Macro (Level = Data)
market data related utilties
RandomVariable max(RandomVariable x, const RandomVariable &y)
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)
RandomVariable min(RandomVariable x, const RandomVariable &y)
std::pair< std::string, QuantLib::Period > splitCurveIdWithTenor(const std::string &creditCurveId)
Serializable Credit Default Swap.
Map text representations to QuantLib/QuantExt types.
Reference data model and serialization.
vector< string > curveConfigs