39#include <ql/cashflows/simplecashflow.hpp>
40#include <ql/instruments/compositeinstrument.hpp>
41#include <ql/math/interpolations/backwardflatinterpolation.hpp>
42#include <ql/math/interpolations/loginterpolation.hpp>
43#include <ql/math/optimization/costfunction.hpp>
44#include <ql/math/optimization/levenbergmarquardt.hpp>
45#include <ql/quotes/compositequote.hpp>
46#include <ql/termstructures/credit/flathazardrate.hpp>
63void validateWeightRec(Real
value,
const string&
name,
const string& varName) {
64 QL_REQUIRE(
value <= 1.0,
65 "The " << varName <<
" value (" <<
value <<
") for name " <<
name <<
" should not be greater than 1.0.");
66 QL_REQUIRE(
value >= 0.0,
67 "The " << varName <<
" value (" <<
value <<
") for name " <<
name <<
" should not be less than 0.0.");
77 DLOG(
"SyntheticCDO::build() called for trade " <<
id());
80 additionalData_[
"isdaAssetClass"] = string(
"Credit");
81 additionalData_[
"isdaBaseProduct"] = string(
"Index Tranche");
82 QuantLib::ext::shared_ptr<ReferenceDataManager> refData = engineFactory->referenceData();
83 if (refData && refData->hasData(
"CreditIndex", qualifier_)) {
84 auto refDatum = refData->getData(
"CreditIndex", qualifier_);
85 QuantLib::ext::shared_ptr<CreditIndexReferenceDatum> creditIndexRefDatum =
86 QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(refDatum);
87 additionalData_[
"isdaSubProduct"] = creditIndexRefDatum->indexFamily();
88 if (creditIndexRefDatum->indexFamily() ==
"") {
89 ALOG(
"IndexFamily is blank in credit index reference data for entity " << qualifier_);
92 ALOG(
"Credit index reference data missing for entity " << qualifier_ <<
", isdaSubProduct left blank");
95 additionalData_[
"isdaTransaction"] = string(
"");
100 Protection::Side side = legData_.isPayer() ? Protection::Buyer : Protection::Seller;
103 auto fixedLegData = QuantLib::ext::dynamic_pointer_cast<FixedLegData>(legData_.concreteLegData());
104 QL_REQUIRE(fixedLegData,
"Expected FixedLegData but got " << legData_.legType());
105 Real runningRate = fixedLegData->rates().front();
112 Actual360 standardDayCounter;
113 DayCounter lastPeriodDayCounter = dayCounter == standardDayCounter ? Actual360(
true) : dayCounter;
117 npvCurrency_ = legData_.currency();
118 maturity_ = leg.back()->date();
119 notionalCurrency_ = legData_.currency();
121 legPayers_ = {legData_.isPayer()};
122 legCurrencies_ = {legData_.currency()};
125 QL_REQUIRE(upfrontDate == Date() || upfrontFee_ != Null<Real>(),
126 "If upfront date is given (" << upfrontDate <<
"), upfront fee must be given.");
127 QL_REQUIRE(upfrontDate != Date() || upfrontFee_ == Null<Real>() ||
close_enough(upfrontFee_, 0.0),
128 "If no upfront date is given, no upfront fee should be given but got " << upfrontFee_ <<
".");
132 QL_REQUIRE(attachmentPoint_ < detachmentPoint_,
"Detachment point should be greater than attachment point.");
133 Real origTrancheNtl = legData_.notionals().front();
134 Real origTotalNtl = origTrancheNtl / (detachmentPoint_ - attachmentPoint_);
135 Real origEquityNtl = origTotalNtl * attachmentPoint_;
136 Real origSeniorNtl = origTotalNtl * (1.0 - detachmentPoint_);
138 DLOG(
"Original tranche notional: " << origTrancheNtl);
139 DLOG(
"Original equity notional: " << origEquityNtl);
140 DLOG(
"Original senior notional: " << origSeniorNtl);
141 DLOG(
"Original attachment point: " << attachmentPoint_);
142 DLOG(
"Original detachment point: " << detachmentPoint_);
143 DLOG(
"Original total notional: " << origTotalNtl);
146 Real lostNotional = 0.0;
147 Real recoveredNotional = 0.0;
150 vector<Real> basketNotionals;
152 vector<string> creditCurves;
156 if (basketData_.constituents().size() > 0) {
158 const auto& constituents = basketData_.constituents();
159 DLOG(
"Building constituents from basket data containing " << constituents.size() <<
" elements.");
161 Real totalRemainingNtl = 0.0;
162 Real totalPriorNtl = 0.0;
164 for (
const auto& c : constituents) {
165 Real ntl = Null<Real>(), priorNotional = Null<Real>();
166 const auto& creditCurve = c.creditCurveId();
167 if (c.weightInsteadOfNotional()) {
168 ntl = c.weight() * origTotalNtl;
169 priorNotional = c.priorWeight();
170 if (priorNotional != Null<Real>()) {
171 priorNotional *= origTotalNtl;
175 priorNotional = c.priorNotional();
176 QL_REQUIRE(c.currency() == npvCurrency_,
"The currency of basket constituent "
177 << creditCurve <<
" is " << c.currency()
178 <<
" and does not equal the trade leg currency "
182 if (!close(0.0, ntl) && ntl > 0.0) {
183 if (std::find(creditCurves.begin(), creditCurves.end(), creditCurve) == creditCurves.end()) {
184 DLOG(
"Adding underlying " << creditCurve <<
" with notional " << ntl);
185 creditCurves.push_back(creditCurve);
186 basketNotionals.push_back(ntl);
187 totalRemainingNtl += ntl;
190 (
"Invalid Basket: found a duplicate credit curve " + creditCurve +
191 ", skip it. Check the basket data for possible errors.")
196 DLOG(
"Underlying " << creditCurve <<
" notional is " << ntl <<
" so assuming a credit event occured.");
197 QL_REQUIRE(priorNotional != Null<Real>(),
198 "Expecting a valid prior notional for name " << creditCurve <<
".");
199 auto recovery = c.recovery();
200 QL_REQUIRE(recovery != Null<Real>(),
"Expecting a valid recovery for name " << creditCurve <<
".");
201 validateWeightRec(recovery, creditCurve,
"recovery");
202 lostNotional += (1.0 - recovery) * priorNotional;
203 recoveredNotional += recovery * priorNotional;
204 totalPriorNtl += priorNotional;
208 Real totalNtl = totalRemainingNtl + totalPriorNtl;
209 DLOG(
"All Underlyings added, total remaining notional = " << totalRemainingNtl);
210 DLOG(
"All Underlyings added, total prior notional = " << totalPriorNtl);
211 DLOG(
"All Underlyings added, total notional = " << totalNtl);
213 QL_REQUIRE(creditCurves.size() == basketNotionals.size(),
"numbers of defaults curves ("
214 << creditCurves.size() <<
") and notionals ("
215 << basketNotionals.size() <<
") doesnt match");
216 Real notionalCorrectionFactor = origTotalNtl / totalNtl;
218 if (!close(totalNtl, origTotalNtl) && (
abs(notionalCorrectionFactor - 1.0) <= 1e-4)) {
219 ALOG(
"Trade " <<
id() <<
", sum of notionals(" << totalNtl <<
") is very close to total original notional ("
220 << origTotalNtl <<
"), will scale each notional by " << notionalCorrectionFactor
221 <<
", check the basket data for possible errors.");
222 totalRemainingNtl = 0;
223 for (Size i = 0; i < basketNotionals.size(); i++) {
224 Real scaledNotional = basketNotionals[i] * notionalCorrectionFactor;
225 TLOG(
"Trade " <<
id() <<
", Issuer" << creditCurves[i] <<
" unscaled Notional: " << basketNotionals[i]
226 <<
", scaled Notional: " << scaledNotional);
227 basketNotionals[i] = scaledNotional;
228 totalRemainingNtl += scaledNotional;
230 lostNotional *= notionalCorrectionFactor;
231 recoveredNotional *= notionalCorrectionFactor;
232 totalNtl *= notionalCorrectionFactor;
235 if (!close(totalRemainingNtl, origTotalNtl) && totalRemainingNtl > origTotalNtl) {
237 (
"Total remaining notional (" + std::to_string(totalRemainingNtl) +
238 ") is greater than total original notional (" +
239 std::to_string(origTotalNtl) +
240 "), check the basket data for possible errors.")
245 if (!close(totalNtl, origTotalNtl)) {
247 (
"Expected the total notional (" + std::to_string(totalNtl) +
" = " +
248 std::to_string(totalRemainingNtl) +
" + " + std::to_string(totalPriorNtl) +
249 ") to equal the total original notional (" + std::to_string(origTotalNtl) +
250 "), check the basket data for possible errors.")
255 DLOG(
"Finished building constituents using basket data.");
259 DLOG(
"Building constituents using CreditIndexReferenceDatum for ID " << qualifier_);
261 QL_REQUIRE(engineFactory->referenceData(),
262 "Trade " <<
id() <<
" has no basket data and there is no reference data manager.");
263 QL_REQUIRE(engineFactory->referenceData()->hasData(CreditIndexReferenceDatum::TYPE, qualifier_),
264 "Trade " <<
id() <<
" needs credit index reference data for ID " << qualifier_);
265 auto crd = QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(
266 engineFactory->referenceData()->getData(CreditIndexReferenceDatum::TYPE, qualifier_));
268 Real totalRemainingWeight = 0.0;
269 Real totalPriorWeight = 0.0;
270 for (
const auto& c : crd->constituents()) {
272 const auto&
name = c.name();
273 auto weight = c.weight();
274 validateWeightRec(weight,
name,
"weight");
276 if (!close(0.0, weight)) {
277 DLOG(
"Adding underlying " <<
name <<
" with weight " << weight);
278 creditCurves.push_back(
name);
279 basketNotionals.push_back(weight * origTotalNtl);
280 totalRemainingWeight += weight;
282 DLOG(
"Underlying " <<
name <<
" has weight " << weight <<
" so assuming a credit event occured.");
283 auto priorWeight = c.priorWeight();
284 QL_REQUIRE(priorWeight != Null<Real>(),
"Expecting a valid prior weight for name " <<
name <<
".");
285 validateWeightRec(priorWeight,
name,
"prior weight");
286 auto recovery = c.recovery();
287 QL_REQUIRE(recovery != Null<Real>(),
"Expecting a valid recovery for name " <<
name <<
".");
288 validateWeightRec(recovery,
name,
"recovery");
289 lostNotional += (1.0 - recovery) * priorWeight * origTotalNtl;
290 recoveredNotional += recovery * priorWeight * origTotalNtl;
291 totalPriorWeight += priorWeight;
295 Real totalWeight = totalRemainingWeight + totalPriorWeight;
296 DLOG(
"All Underlyings added, total remaining weight = " << totalRemainingWeight);
297 DLOG(
"All Underlyings added, total prior weight = " << totalPriorWeight);
298 DLOG(
"All Underlyings added, total weight = " << totalWeight);
300 if (!close(totalRemainingWeight, 1.0) && totalRemainingWeight > 1.0) {
301 ALOG(
"Total remaining weight is greater than 1, possible error in CreditIndexReferenceDatum");
304 if (!close(totalWeight, 1.0)) {
305 ALOG(
"Expected the total weight (" << totalWeight <<
" = " << totalRemainingWeight <<
" + "
307 <<
") to equal 1, possible error in CreditIndexReferenceDatum");
310 DLOG(
"Finished building constituents using CreditIndexReferenceDatum for ID " << qualifier_);
316 Real currTotalNtl = accumulate(basketNotionals.begin(), basketNotionals.end(), 0.0);
317 QL_REQUIRE(!close(currTotalNtl, 0.0),
"Trade " <<
id() <<
" has a current total notional of 0.0.");
318 Real currEquityNtl =
max(origEquityNtl - lostNotional, 0.0);
319 Real currSeniorNtl =
max(origSeniorNtl - recoveredNotional, 0.0);
320 Real currTrancheNtl = origTrancheNtl -
max(
min(recoveredNotional - origSeniorNtl, origTrancheNtl), 0.0) -
321 max(
min(lostNotional - origEquityNtl, origTrancheNtl), 0.0);
322 QL_REQUIRE(!close(currTrancheNtl, 0.0),
"Trade " <<
id() <<
" has a current tranche notional of 0.0.");
323 Real adjAttachPoint = currEquityNtl / currTotalNtl;
324 Real adjDetachPoint = (currEquityNtl + currTrancheNtl) / currTotalNtl;
325 notional_ = currTrancheNtl;
327 DLOG(
"Current tranche notional: " << currTrancheNtl);
328 DLOG(
"Current equity notional: " << currEquityNtl);
329 DLOG(
"Current senior notional: " << currSeniorNtl);
330 DLOG(
"Current attachment point: " << adjAttachPoint);
331 DLOG(
"Current detachment point: " << adjDetachPoint);
332 DLOG(
"Current total notional: " << currTotalNtl);
335 const auto& market = engineFactory->market();
336 auto cdoEngineBuilder = QuantLib::ext::dynamic_pointer_cast<CdoEngineBuilder>(engineFactory->builder(
"SyntheticCDO"));
337 QL_REQUIRE(cdoEngineBuilder,
"Trade " <<
id() <<
" needs a valid CdoEngineBuilder.");
338 const string& config = cdoEngineBuilder->configuration(MarketContext::pricing);
341 std::vector<Handle<DefaultProbabilityTermStructure>> dpts;
342 vector<Real> recoveryRates;
344 if (fixedRecovery != Null<Real>()) {
345 LOG(
"Set all recovery rates to " << fixedRecovery);
347 for (Size i = 0; i < creditCurves.size(); ++i) {
348 const string& cc = creditCurves[i];
349 Real mktRecoveryRate = market->recoveryRate(cc, config)->value();
350 recoveryRates.push_back(fixedRecovery != Null<Real>() ? fixedRecovery : mktRecoveryRate);
351 auto originalCurve = market->defaultCurve(cc, config)->curve();
352 dpts.push_back(originalCurve);
360 QuantLib::ext::shared_ptr<SimpleQuote> calibrationFactor = QuantLib::ext::make_shared<SimpleQuote>(1.0);
362 bool calibrateConstiuentCurves = cdoEngineBuilder->calibrateConstituentCurve() && isIndexTranche();
364 if (calibrateConstiuentCurves) {
368 LOG(
"Use calibrated constiuent curves");
369 QuantLib::ext::shared_ptr<IndexCreditDefaultSwap> indexCDS;
370 Real cdsFairSpreads = 0.0;
373 Handle<YieldTermStructure> yts =
374 market->discountCurve(ccy.code(), cdoEngineBuilder->configuration(MarketContext::pricing));
376 Date cdsStartDate = indexStartDateHint() == Date() ? schedule.dates().front() : indexStartDateHint();
378 Schedule cdsSchedule(cdsStartDate, schedule.dates().back(),
379 schedule.tenor(), schedule.calendar(), Following,
380 schedule.terminationDateBusinessDayConvention(), schedule.rule(),
false);
381 indexCDS = QuantLib::ext::make_shared<QuantExt::IndexCreditDefaultSwap>(
382 side, currTotalNtl, basketNotionals, 0.0, runningRate, cdsSchedule, bdc, dayCounter,
settlesAccrual_,
384 lastPeriodDayCounter, rebatesAccrual_);
385 Handle<DefaultProbabilityTermStructure> indexCreditCurve =
387 Handle<Quote> indexCdsRecovery = market->recoveryRate(creditCurveIdWithTerm(), config);
388 auto indexPricingEngine =
389 QuantLib::ext::make_shared<QuantExt::MidPointIndexCdsEngine>(indexCreditCurve, indexCdsRecovery->value(), yts);
390 indexCDS->setPricingEngine(indexPricingEngine);
391 }
catch (
const std::exception& e) {
393 WLOG(
"CDO constiuent calibration failed to build index cds. Got "
397 cdsFairSpreads = indexCDS->fairSpreadClean();
398 cdsNpvs = indexCDS->NPV();
401 auto it = engineFactory->engineData()->globalParameters().find(
"RunType");
402 if (it != engineFactory->engineData()->globalParameters().end() && it->second !=
"PortfolioAnalyser") {
404 Handle<YieldTermStructure> yts =
405 market->discountCurve(ccy.code(), cdoEngineBuilder->configuration(MarketContext::pricing));
407 std::vector<Handle<DefaultProbabilityTermStructure>> wrapperCurves;
408 DLOG(
"Building wrapper curves for calibration");
409 for (
size_t i = 0; i < creditCurves.size(); ++i) {
411 const string& cc = creditCurves[i];
412 auto originalCurve = market->defaultCurve(cc, config)->curve();
413 wrapperCurves.push_back(
414 buildCalibratedConstiuentCurve(originalCurve, calibrationFactor));
415 }
catch (
const std::exception& e) {
417 WLOG(
"CDO constiuent calibration failed during building wrapper curve for "
418 << creditCurves[i] <<
", skip this curve. Got "
423 if (wrapperCurves.size() == dpts.size() && indexCDS) {
424 LOG(
"Start bootstraping of the calibration factors");
425 dpts.swap(wrapperCurves);
427 auto cdsPricingEngineUnderlyingCurves =
428 QuantLib::ext::make_shared<QuantExt::MidPointIndexCdsEngine>(dpts, recoveryRates, yts);
430 indexCDS->setPricingEngine(cdsPricingEngineUnderlyingCurves);
433 auto targetFunction = [&cdsNpvs, &calibrationFactor, &indexCDS](
const double& factor) {
434 calibrationFactor->setValue(factor);
435 return cdsNpvs - indexCDS->NPV();
439 double indexAdjustmentForUnderlyingCurves =
440 solver.solve(targetFunction, 1e-8, cdsFairSpreads / indexCDS->fairSpreadClean(), 0.001, 2);
442 DLOG(
"Calibration of indexterm " << io::iso_date(indexCDS->maturity())
443 <<
"successful, found solution "
444 << indexAdjustmentForUnderlyingCurves);
445 calibrationFactor->setValue(indexAdjustmentForUnderlyingCurves);
446 }
catch (
const std::exception& e) {
447 WLOG(
"Calibration failed, at pillar " << io::iso_date(indexCDS->maturity())
448 <<
", set calibration factor to 1 (uncalibrated), got "
450 calibrationFactor->setValue(1.0);
453 LOG(
"Calibration results for creditCurve:" << creditCurveIdWithTerm());
454 LOG(
"Expiry \t CalibrationFactor \t NpvIntrinsic \t NpvIndexCurve \t NpvError \t "
455 "FairSpreadIntrinsic "
456 "\t FairSpreadIndexCurve \t FairSreadError");
458 LOG(io::iso_date(indexCDS->maturity())
459 <<
"\t" << calibrationFactor->value() <<
"\t" << indexCDS->NPV() <<
"\t" << cdsNpvs <<
"\t"
460 << indexCDS->NPV() - cdsNpvs <<
"\t" << indexCDS->fairSpreadClean() <<
"\t" << cdsFairSpreads
461 <<
"\t" << indexCDS->fairSpreadClean() - cdsFairSpreads);
467 auto pool = QuantLib::ext::make_shared<Pool>();
470 useSensitivitySimplification_ = sensitivityDecomposition != CreditPortfolioSensitivityDecomposition::Underlying;
471 Handle<DefaultProbabilityTermStructure> clientCurve;
472 Handle<DefaultProbabilityTermStructure> baseCurve;
473 vector<Time> baseCurveTimes;
474 vector<Real> expLoss;
476 if (cdoEngineBuilder->optimizedSensitivityCalculation()) {
480 for (Size i = 0; i < creditCurves.size(); ++i) {
481 const string& cc = creditCurves[i];
482 DefaultProbKey key = NorthAmericaCorpDefaultKey(ccy, SeniorSec, Period(), 1.0);
484 auto defaultCurve = dpts[i];
485 expLoss.push_back((1 -
recoveryRate) * defaultCurve->defaultProbability(maturity_,
true) * basketNotionals[i]);
486 std::pair<DefaultProbKey, Handle<DefaultProbabilityTermStructure>> p(key, defaultCurve);
487 vector<pair<DefaultProbKey, Handle<DefaultProbabilityTermStructure>>> probabilities(1, p);
489 Issuer issuer(probabilities, DefaultEventSet());
490 pool->add(cc, issuer, key);
491 DLOG(
"Issuer " << cc <<
" added to the pool.");
496 if (sensitivityDecomposition == CreditPortfolioSensitivityDecomposition::LossWeighted) {
497 basketConstituents_.clear();
498 Real totalWeight = std::accumulate(expLoss.begin(), expLoss.end(), 0.0);
499 for (Size basketIdx = 0; basketIdx < creditCurves.size(); basketIdx++) {
500 string& creditCurve = creditCurves[basketIdx];
501 Real weight = expLoss[basketIdx];
502 if (basketConstituents_.count(creditCurve) == 0) {
503 basketConstituents_[creditCurve] = weight / totalWeight;
505 basketConstituents_[creditCurve] += weight / totalWeight;
508 }
else if (sensitivityDecomposition == CreditPortfolioSensitivityDecomposition::NotionalWeighted) {
509 basketConstituents_.clear();
510 Real totalWeight = std::accumulate(basketNotionals.begin(), basketNotionals.end(), 0.0);
511 for (Size basketIdx = 0; basketIdx < creditCurves.size(); basketIdx++) {
512 string& creditCurve = creditCurves[basketIdx];
513 Real weight = basketNotionals[basketIdx];
514 if (basketConstituents_.count(creditCurve) == 0) {
515 basketConstituents_[creditCurve] = weight / totalWeight;
517 basketConstituents_[creditCurve] += weight / totalWeight;
520 }
else if (sensitivityDecomposition == CreditPortfolioSensitivityDecomposition::DeltaWeighted) {
521 basketConstituents_.clear();
522 Real totalWeight = 0;
523 for (Size basketIdx = 0; basketIdx < creditCurves.size(); basketIdx++) {
524 string& creditCurve = creditCurves[basketIdx];
525 Real notional = basketNotionals[basketIdx];
526 auto defaultCurve = market->defaultCurve(creditCurve, config)->curve();
527 Real constituentSurvivalProb = defaultCurve->survivalProbability(maturity_);
528 Time t = defaultCurve->timeFromReference(maturity_);
529 Real CR01 = t * constituentSurvivalProb * notional;
530 if (basketConstituents_.find(creditCurve) == basketConstituents_.end())
531 basketConstituents_.emplace(creditCurve, CR01);
535 for (
auto& decompWeight : basketConstituents_) {
536 decompWeight.second /= totalWeight;
541 bool homogeneous = all_of(basketNotionals.begin(), basketNotionals.end(),
542 [&basketNotionals](Real ntl) { return close_enough(ntl, basketNotionals[0]); });
543 homogeneous = homogeneous && all_of(recoveryRates.begin(), recoveryRates.end(),
544 [&recoveryRates](Real rr) { return close_enough(rr, recoveryRates[0]); });
549 QuantLib::ext::shared_ptr<Instrument> vanilla;
553 QuantLib::ext::shared_ptr<Instrument> cdoD;
555 DLOG(
"Building detachment tranche [0," << adjDetachPoint <<
"].");
558 QuantLib::ext::make_shared<QuantExt::Basket>(schedule[0], creditCurves, basketNotionals, pool, 0.0, adjDetachPoint);
560 cdoEngineBuilder->lossModel(qualifier(), recoveryRates, adjDetachPoint,
561 maturity_, homogeneous));
564 QuantLib::ext::make_shared<QuantExt::SyntheticCDO>(
basket, side, schedule, 0.0, runningRate, dayCounter, bdc,
567 cdoDetach->setPricingEngine(
568 cdoEngineBuilder->engine(ccy,
false, {}, calibrationFactor, fixedRecovery));
569 setSensitivityTemplate(*cdoEngineBuilder);
572 DLOG(
"Detachment tranche [0," << adjDetachPoint <<
"] built.");
575 DLOG(
"Detachment point is 1.0 so building an index CDS for [0,1.0] 'tranche'.");
581 auto cds = QuantLib::ext::make_shared<QuantExt::IndexCreditDefaultSwap>(
582 side, currTotalNtl, basketNotionals, 0.0, runningRate, schedule, bdc, dayCounter,
settlesAccrual_,
584 lastPeriodDayCounter, rebatesAccrual_, protectionStartDate, 3);
586 cds->setPricingEngine(
587 cdoEngineBuilder->engine(ccy,
true, creditCurves, calibrationFactor, fixedRecovery));
588 setSensitivityTemplate(*cdoEngineBuilder);
591 DLOG(
"Index CDS for [0,1.0] 'tranche' built.");
599 DLOG(
"Attachment point is 0 so the instrument is built.");
602 DLOG(
"Building attachment tranche [0," << adjAttachPoint <<
"].");
606 QuantLib::ext::make_shared<QuantExt::Basket>(schedule[0], creditCurves, basketNotionals, pool, 0.0, adjAttachPoint);
607 basket->setLossModel(cdoEngineBuilder->lossModel(qualifier(), recoveryRates, adjAttachPoint,
608 maturity_, homogeneous));
614 cdoA->setPricingEngine(
615 cdoEngineBuilder->engine(ccy,
false, {}, calibrationFactor, fixedRecovery));
616 setSensitivityTemplate(*cdoEngineBuilder);
618 DLOG(
"Attachment tranche [0," << adjAttachPoint <<
"] built.");
620 DLOG(
"Building attachment and detachment composite instrument.");
622 auto composite = QuantLib::ext::make_shared<CompositeInstrument>();
623 composite->add(cdoD);
624 composite->subtract(cdoA);
627 DLOG(
"Attachment and detachment composite instrument built.");
633 if (upfrontDate != Date()) {
634 vector<QuantLib::ext::shared_ptr<Instrument>> insts;
636 Real upfrontAmount = upfrontFee_ * origTrancheNtl;
637 string configuration = cdoEngineBuilder->configuration(MarketContext::pricing);
639 std::max(maturity_, addPremiums(insts, mults, 1.0,
PremiumData(upfrontAmount, ccy.code(), upfrontDate),
640 side == Protection::Buyer ? -1.0 : 1.0, ccy, engineFactory, configuration));
642 instrument_ = QuantLib::ext::make_shared<VanillaInstrument>(vanilla, 1.0, insts, mults);
646 instrument_ = QuantLib::ext::make_shared<VanillaInstrument>(vanilla);
649 additionalData_[
"originalNotional"] = origTrancheNtl;
650 additionalData_[
"currentNotional"] = currTrancheNtl;
652 DLOG(
"CDO instrument built");
656 Trade::fromXML(node);
657 XMLNode* cdoNode = XMLUtils::getChildNode(node,
"CdoData");
658 QL_REQUIRE(cdoNode,
"No CdoData Node");
659 qualifier_ = XMLUtils::getChildValue(cdoNode,
"Qualifier",
true);
660 protectionStart_ = XMLUtils::getChildValue(cdoNode,
"ProtectionStart",
true);
661 upfrontDate_ = XMLUtils::getChildValue(cdoNode,
"UpfrontDate",
false);
664 upfrontFee_ = Null<Real>();
665 string strUpfrontFee = XMLUtils::getChildValue(cdoNode,
"UpfrontFee",
false);
666 if (!strUpfrontFee.empty()) {
669 settlesAccrual_ = XMLUtils::getChildValueAsBool(cdoNode,
"SettlesAccrual",
false);
670 rebatesAccrual_ = XMLUtils::getChildValueAsBool(cdoNode,
"RebatesAccrual",
false);
675 string strRecoveryRate = XMLUtils::getChildValue(node,
"FixedRecoveryRate",
false);
676 if (!strRecoveryRate.empty()) {
681 if (
auto c = XMLUtils::getChildNode(cdoNode,
"PaysAtDefaultTime"))
682 if (!
parseBool(XMLUtils::getNodeValue(c)))
685 if (
auto c = XMLUtils::getChildNode(cdoNode,
"ProtectionPaymentTime")) {
686 if (XMLUtils::getNodeValue(c) ==
"atDefault")
688 else if (XMLUtils::getNodeValue(c) ==
"atPeriodEnd")
690 else if (XMLUtils::getNodeValue(c) ==
"atMaturity")
693 QL_FAIL(
"protection payment time '" << XMLUtils::getNodeValue(c)
694 <<
"' not known, expected atDefault, atPeriodEnd, atMaturity");
697 attachmentPoint_ = XMLUtils::getChildValueAsDouble(cdoNode,
"AttachmentPoint",
true);
698 detachmentPoint_ = XMLUtils::getChildValueAsDouble(cdoNode,
"DetachmentPoint",
true);
699 XMLNode* legNode = XMLUtils::getChildNode(cdoNode,
"LegData");
700 legData_.fromXML(legNode);
701 XMLNode* basketNode = XMLUtils::getChildNode(cdoNode,
"BasketData");
703 basketData_.fromXML(basketNode);
707 XMLNode* node = Trade::toXML(doc);
709 XMLUtils::appendNode(node, cdoNode);
710 XMLUtils::addChild(doc, cdoNode,
"Qualifier", qualifier_);
713 XMLUtils::addChild(doc, cdoNode,
"UpfrontDate",
upfrontDate_);
715 if (upfrontFee_ != Null<Real>()) {
716 XMLUtils::addChild(doc, cdoNode,
"UpfrontFee", upfrontFee_);
719 if (!rebatesAccrual_)
720 XMLUtils::addChild(doc, node,
"RebatesAccrual", rebatesAccrual_);
722 XMLUtils::addChild(doc, cdoNode,
"ProtectionPaymentTime",
"atDefault");
724 XMLUtils::addChild(doc, cdoNode,
"ProtectionPaymentTime",
"atPeriodEnd");
726 XMLUtils::addChild(doc, cdoNode,
"ProtectionPaymentTime",
"atMaturity");
728 QL_FAIL(
"CreditDefaultSwapData::toXML(): unexpected protectionPaymentTime_");
731 XMLUtils::addChild(doc, node,
"FixedRecoveryRate",
recoveryRate_);
732 XMLUtils::addChild(doc, cdoNode,
"AttachmentPoint", attachmentPoint_);
733 XMLUtils::addChild(doc, cdoNode,
"DetachmentPoint", detachmentPoint_);
734 XMLUtils::appendNode(cdoNode, legData_.toXML(doc));
735 XMLUtils::appendNode(cdoNode, basketData_.toXML(doc));
739SyntheticCDO::extractTimeGridDefaultCurve(
const QuantLib::Handle<QuantLib::DefaultProbabilityTermStructure> &dpts) {
740 if (
auto c = QuantLib::ext::dynamic_pointer_cast<SpreadedSurvivalProbabilityTermStructure>(*dpts)) {
741 return SyntheticCDO::extractTimeGridDefaultCurve(c->referenceCurve());
749 WLOG(
"Extraction of time points failed, unsupported default probability termstructure");
750 return vector<double>();
754QuantLib::Handle<QuantLib::DefaultProbabilityTermStructure>
755SyntheticCDO::buildCalibratedConstiuentCurve(
const QuantLib::Handle<QuantLib::DefaultProbabilityTermStructure>& curve,
756 const QuantLib::ext::shared_ptr<SimpleQuote>& calibrationFactor) {
757 if (!calibrationFactor) {
760 auto curveTimes = extractTimeGridDefaultCurve(curve);
761 vector<Handle<Quote>> spreads;
762 for (
size_t timeIdx = 0; timeIdx < curveTimes.size(); ++timeIdx) {
763 auto sp = curve->survivalProbability(curveTimes[timeIdx]);
764 auto compQuote = QuantLib::ext::make_shared<CompositeQuote<std::function<double(
double,
double)>>>(
765 Handle<Quote>(calibrationFactor),
766 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(sp)),
767 [](
const double q1,
const double q2) ->
double {
return std::exp(-(1 - q1) * std::log(q2)); });
768 spreads.push_back(Handle<Quote>(compQuote));
770 Handle<DefaultProbabilityTermStructure> targetCurve = Handle<DefaultProbabilityTermStructure>(
771 QuantLib::ext::make_shared<SpreadedSurvivalProbabilityTermStructure>(curve, curveTimes, spreads));
772 if (curve->allowsExtrapolation()) {
773 targetCurve->enableExtrapolation();
778std::string SyntheticCDO::creditCurveIdWithTerm()
const {
780 if (p.second != 0 * Days || !isIndexTranche())
784 if (s.dates().empty())
787 indexStartDateHint_ == Date() ? s.dates().front() : indexStartDateHint_, s.dates().back());
Mid point CDO engines cached by currency.
QuantLib::CreditDefaultSwap::ProtectionPaymentTime protectionPaymentTime_
const boost::shared_ptr< QuantExt::Basket > & basket() const
Real recoveryRate() const
void log() const
generate Boost log record to pass to corresponding sinks
Serializable object holding premium data.
Utility class for Structured Trade errors, contains the Trade ID and Type.
virtual void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Small XML Document wrapper class.
XMLNode * allocNode(const string &nodeName)
util functions that wrap rapidxml
SafeStack< ValueType > value
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
BusinessDayConvention parseBusinessDayConvention(const string &s)
Convert text to QuantLib::BusinessDayConvention.
bool parseBool(const string &s)
Convert text to bool.
Real parseReal(const string &s)
Convert text to Real.
DayCounter parseDayCounter(const string &s)
Convert text to QuantLib::DayCounter.
Map text representations to QuantLib/QuantExt types.
leg data model and serialization
Classes and functions for log message handling.
#define LOG(text)
Logging Macro (Level = Notice)
#define DLOG(text)
Logging Macro (Level = Debug)
#define ALOG(text)
Logging Macro (Level = Alert)
#define WLOG(text)
Logging Macro (Level = Warning)
#define TLOG(text)
Logging Macro (Level = Data)
market data related utilties
RandomVariable max(RandomVariable x, const RandomVariable &y)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
RandomVariable abs(RandomVariable x)
QuantLib::Period implyIndexTerm(const Date &startDate, const Date &endDate)
RandomVariable min(RandomVariable x, const RandomVariable &y)
QuantLib::Handle< QuantExt::CreditCurve > indexCdsDefaultCurve(const QuantLib::ext::shared_ptr< Market > &market, const std::string &creditCurveId, const std::string &config)
std::string to_string(const LocationInfo &l)
std::vector< Handle< DefaultProbabilityTermStructure > > buildPerformanceOptimizedDefaultCurves(const std::vector< Handle< DefaultProbabilityTermStructure > > &curves)
CreditPortfolioSensitivityDecomposition
Enumeration CreditPortfolioSensitivityDecomposition.
std::pair< std::string, QuantLib::Period > splitCurveIdWithTenor(const std::string &creditCurveId)
Leg makeFixedLeg(const LegData &data, const QuantLib::Date &openEndDateReplacement)
Schedule makeSchedule(const ScheduleDates &data)
Serializable Credit Default Swap.
Map text representations to QuantLib/QuantExt types.
Structured Trade Error class.
Swap trade data model and serialization.
string conversion utilities