31#include <ql/time/daycounters/actual360.hpp>
46 :
Trade(
"IndexCreditDefaultSwapOption"), strike_(Null<Real>()) {}
50 const string& indexTerm,
const string& strikeType,
51 const Date& tradeDate,
const Date& fepStartDate)
52 :
Trade(
"IndexCreditDefaultSwapOption", env), swap_(swap), option_(option), strike_(strike),
53 indexTerm_(indexTerm), strikeType_(strikeType), tradeDate_(tradeDate), fepStartDate_(fepStartDate) {}
57 DLOG(
"IndexCreditDefaultSwapOption::build() called for trade " <<
id());
63 QuantLib::ext::shared_ptr<ReferenceDataManager> refData = engineFactory->referenceData();
64 if (refData && refData->hasData(
"CreditIndex", entity)) {
65 auto refDatum = refData->getData(
"CreditIndex", entity);
66 QuantLib::ext::shared_ptr<CreditIndexReferenceDatum> creditIndexRefDatum =
67 QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(refDatum);
69 if (creditIndexRefDatum->indexFamily() ==
"") {
70 ALOG(
"IndexFamily is blank in credit index reference data for entity " << entity);
73 ALOG(
"Credit index reference data missing for entity " << entity <<
", isdaSubProduct left blank");
79 const QuantLib::ext::shared_ptr<Market>& market = engineFactory->market();
80 Date asof = market->asofDate();
81 if (asof == Null<Date>() || asof == Date()) {
82 asof = Settings::instance().evaluationDate();
88 QL_REQUIRE(
tradeDate_ <= asof,
"Trade date (" << io::iso_date(
tradeDate_) <<
") should be on or "
89 <<
"before the valuation date (" << io::iso_date(asof) <<
")");
97 <<
") should be on or before the trade date ("
105 QL_REQUIRE(ntls.size() == 1,
"IndexCreditDefaultSwapOption requires a single notional.");
113 QL_REQUIRE(legData.legType() ==
"Fixed",
"Index CDS option " <<
id() <<
" requires fixed leg.");
114 auto fixedLegData = QuantLib::ext::dynamic_pointer_cast<FixedLegData>(legData.concreteLegData());
115 QL_REQUIRE(fixedLegData->rates().size() == 1,
"Index CDS option " <<
id() <<
" requires single fixed rate.");
116 auto runningCoupon = fixedLegData->rates().front();
122 "invalid StrikeType (" <<
strikeType_ <<
"), expected 'Spread' or 'Price' or empty value");
126 }
else if (
strike_ == Null<Real>() &&
strikeType_ ==
"Spread" && upfrontFee == Null<Real>()) {
129 }
else if (
strike_ == Null<Real>() &&
strikeType_ ==
"Price" && upfrontFee == Null<Real>()) {
132 }
else if (
strike_ != Null<Real>() &&
strikeType_ ==
"" && upfrontFee == Null<Real>()) {
135 }
else if (
strike_ != Null<Real>() &&
strikeType_ ==
"Spread" && upfrontFee == Null<Real>()) {
138 }
else if (
strike_ != Null<Real>() &&
strikeType_ ==
"Price" && upfrontFee == Null<Real>()) {
141 }
else if (
strike_ == Null<Real>() &&
strikeType_ ==
"" && upfrontFee != Null<Real>()) {
144 }
else if (
strike_ == Null<Real>() &&
strikeType_ ==
"Spread" && upfrontFee != Null<Real>()) {
149 QL_FAIL(
"StrikeType 'Spread' and non-zero upfront fee can not be combined.");
151 }
else if (
strike_ == Null<Real>() &&
strikeType_ ==
"Price" && upfrontFee != Null<Real>()) {
154 }
else if (
strike_ != Null<Real>() &&
strikeType_ ==
"" && upfrontFee != Null<Real>()) {
159 QL_FAIL(
"Strike and non-zero upfront can not be combined.");
161 }
else if (
strike_ != Null<Real>() &&
strikeType_ ==
"Spread" && upfrontFee != Null<Real>()) {
166 QL_FAIL(
"Strike and non-zero upfront can not be combined.");
168 }
else if (
strike_ != Null<Real>() &&
strikeType_ ==
"Price" && upfrontFee != Null<Real>()) {
173 QL_FAIL(
"Strike and non-zero upfront can not be combined.");
176 QL_FAIL(
"internal error, impossible branch in strike / strike type deduction.");
181 auto side = legData.isPayer() ? Protection::Side::Buyer : Protection::Side::Seller;
192 vector<string> constituentIds;
194 vector<Real> constituentNtls;
197 constituentIds.push_back(kv.first);
198 constituentNtls.push_back(kv.second);
204 Actual360 standardDayCounter;
205 DayCounter lastPeriodDayCounter = dc == standardDayCounter ? Actual360(
true) : dc;
208 QL_REQUIRE(
option_.
style() ==
"European",
"IndexCreditDefaultSwapOption option style must"
210 QL_REQUIRE(
option_.
exerciseFees().empty(),
"IndexCreditDefaultSwapOption cannot handle exercise fees.");
214 QL_REQUIRE(exerciseDates.size() == 1,
"IndexCreditDefaultSwapOption expects one exercise date"
215 <<
" but got " << exerciseDates.size() <<
" exercise dates.");
216 Date exerciseDate =
parseDate(exerciseDates.front());
217 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
219 QL_REQUIRE(
parseDate(legData.schedule().rules().front().endDate()) > exerciseDate,
220 "IndexCreditDefaultSwapOption: ExerciseDate must be before EndDate");
224 if (legData.schedule().rules().size() == 1 && legData.schedule().dates().empty()) {
228 if (
parseDate(legData.schedule().rules().front().startDate()) < exerciseDate) {
229 legData.schedule().modifyRules().front().modifyStartDate() =
ore::data::to_string(exerciseDate);
238 QL_REQUIRE(!schedule.dates().empty(),
239 "IndexCreditDefaultSwapOption: underlying swap schedule does not contain any dates");
240 Date underlyingTradeDate =
242 Date underlyingProtectionStart;
245 }
else if (legData.schedule().rules().size() == 1 && legData.schedule().dates().empty()) {
247 if (rule == DateGeneration::CDS || rule == DateGeneration::CDS2015) {
248 underlyingProtectionStart = std::max(exerciseDate, schedule.dates().front());
250 underlyingProtectionStart = schedule.dates().front();
253 underlyingProtectionStart = std::max(exerciseDate, schedule.dates().front());
257 auto iCdsOptionEngineBuilder = QuantLib::ext::dynamic_pointer_cast<IndexCreditDefaultSwapOptionEngineBuilder>(
258 engineFactory->builder(
"IndexCreditDefaultSwapOption"));
259 QL_REQUIRE(iCdsOptionEngineBuilder,
260 "IndexCreditDefaultSwapOption: internal error, expected IndexCreditDefaultSwapOptionEngineBuilder");
261 auto iCdsEngineBuilder = QuantLib::ext::dynamic_pointer_cast<IndexCreditDefaultSwapEngineBuilder>(
262 engineFactory->builder(
"IndexCreditDefaultSwap"));
263 QL_REQUIRE(iCdsEngineBuilder,
264 "IndexCreditDefaultSwap: internal error, expected IndexCreditDefaultSwapEngineBuilder");
269 auto cds = QuantLib::ext::make_shared<QuantExt::IndexCreditDefaultSwap>(
276 std::string overrideCurve = iCdsOptionEngineBuilder->engineParameter(
"Curve", {},
false,
"Underlying");
282 "Index CDS term could not be derived from start, end date, are these "
283 "dates correct (credit curve id is '" +
290 cds->setPricingEngine(iCdsEngineBuilder->engine(ccy,
creditCurveId, constituentIds, overrideCurve,
306 schedule.dates().back());
321 maturity_ = cds->coupons().back()->date();
324 legs_ = {cds->coupons()};
330 Real indicatorLongShort = positionType == Position::Long ? 1.0 : -1.0;
333 vector<QuantLib::ext::shared_ptr<Instrument>> additionalInstruments;
334 vector<Real> additionalMultipliers;
337 std::max(
maturity_,
addPremiums(additionalInstruments, additionalMultipliers, indicatorLongShort,
345 if (settleType == Settlement::Cash || exerciseDate <= Settings::instance().evaluationDate()) {
346 instrument_ = QuantLib::ext::make_shared<VanillaInstrument>(
option, indicatorLongShort, additionalInstruments,
347 additionalMultipliers);
349 bool isLong = positionType == Position::Long;
350 bool isPhysical = settleType == Settlement::Physical;
351 instrument_ = QuantLib::ext::make_shared<EuropeanOptionWrapper>(
option, isLong, exerciseDate, isPhysical, cds, 1.0, 1.0,
352 additionalInstruments, additionalMultipliers);
361 ALOG(
"Error retrieving current notional for index credit default swap option "
362 <<
id() <<
" as of " << Settings::instance().evaluationDate());
373 QL_REQUIRE(iCdsOptionData,
"Expected IndexCreditDefaultSwapOptionData node on trade " <<
id() <<
".");
387 QL_REQUIRE(iCdsData,
"Expected IndexCreditDefaultSwapData node on trade " <<
id() <<
".");
391 QL_REQUIRE(optionData,
"Expected OptionData node on trade " <<
id() <<
".");
401 XMLNode* iCdsOptionData = doc.
allocNode(
"IndexCreditDefaultSwapOptionData");
431 if (
swap().leg().isPayer())
432 return QuantLib::Option::Type::Call;
434 return QuantLib::Option::Type::Put;
446 DLOG(
"Building constituents from basket data containing " <<
constituents.size() <<
" elements.");
451 Real ntl = Null<Real>();
452 Real priorNotional = Null<Real>();
454 const auto& creditCurve = c.creditCurveId();
456 if (c.weightInsteadOfNotional()) {
457 ntl = c.weight() * fullNtl;
462 if (ntl == 0.0 || close(0.0, ntl)) {
463 if (c.weightInsteadOfNotional()) {
464 priorNotional = c.priorWeight();
465 if (priorNotional != Null<Real>()) {
466 priorNotional *= fullNtl;
469 priorNotional = c.priorNotional();
472 QL_REQUIRE(priorNotional != Null<Real>(),
"Constituent "
473 << creditCurve <<
" in index CDS option trade " <<
id()
474 <<
" has defaulted so expecting a prior notional.");
475 QL_REQUIRE(c.recovery() != Null<Real>(),
"Constituent " << creditCurve <<
" in index CDS option trade "
477 <<
" has defaulted so expecting a recovery.");
478 QL_REQUIRE(c.auctionDate() != Date(),
"Constituent " << creditCurve <<
" in index CDS option trade " <<
id()
479 <<
" has defaulted so expecting an auction date.");
480 QL_REQUIRE(c.auctionDate() <= asof,
"Constituent " << creditCurve <<
" in index CDS option trade " <<
id()
481 <<
" has defaulted so expecting the auction date ("
482 << io::iso_date(c.auctionDate())
483 <<
") to be before or on the valuation date ("
484 << io::iso_date(asof) <<
".");
489 TLOG(
"Trade date (" << io::iso_date(
tradeDate_) <<
") is before auction date ("
490 << io::iso_date(c.auctionDate()) <<
") of " << creditCurve
491 <<
" so updating trade date "
492 <<
"notional by amount " << priorNotional);
498 Real fepAmount = (1 - recovery) * priorNotional;
499 TLOG(
"FEP start date (" << io::iso_date(
fepStartDate_) <<
") is before auction date ("
500 << io::iso_date(c.auctionDate()) <<
") of " << creditCurve
501 <<
" so updating realised "
502 <<
"FEP by amount " << fepAmount);
506 }
else if (ntl > 0.0) {
510 auto it = outConstituents.find(creditCurve);
511 if (it == outConstituents.end()) {
512 outConstituents[creditCurve] = ntl;
513 TLOG(
"Adding underlying " << creditCurve <<
" with notional " << ntl);
519 (
"Invalid Basket: found a duplicate credit curve " + creditCurve +
520 ".Skip it. Check the basket data for possible errors.")
525 QL_FAIL(
"Constituent " << creditCurve <<
" in index CDS option trade " <<
id()
526 <<
" has a negative notional " << ntl <<
".");
530 Real correctionFactor = fullNtl / totalNtl;
532 if (!close(fullNtl, totalNtl) && (std::abs(correctionFactor - 1.0) <= 1e-4)) {
533 DLOG(
"Trade " <<
id() <<
", sum of notionals (" << totalNtl <<
") is very close to " << fullNtl
534 <<
",will scale it by " << correctionFactor <<
". Check the basket data for possible errors.");
536 for (
auto& name_notional : outConstituents) {
537 TLOG(
"Trade " <<
id() <<
", Issuer" << name_notional.first <<
" unscaled Notional: " << name_notional.second
538 <<
", scaled Notional: " << name_notional.second * correctionFactor);
539 name_notional.second *= correctionFactor;
542 totalNtl *= correctionFactor;
548 DLOG(
"All underlyings added, total notional = " << totalNtl);
549 if (!close(fullNtl, totalNtl) && totalNtl > fullNtl) {
551 (
"Sum of basket notionals (" + std::to_string(totalNtl) +
552 ") is greater than trade notional (" + std::to_string(fullNtl) +
553 "). Check the basket data for possible errors.")
558 DLOG(
"Finished building constituents using basket data.");
562 const QuantLib::ext::shared_ptr<ReferenceDataManager>& refData) {
565 DLOG(
"Start building constituents using credit reference data for " << iCdsId <<
".");
567 QL_REQUIRE(refData,
"Building index CDS option " <<
id() <<
" ReferenceDataManager is null.");
569 "No CreditIndex reference data for " << iCdsId);
570 auto referenceData = QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(
572 DLOG(
"Got CreditIndexReferenceDatum for id " << iCdsId);
575 Real totalWeight = 0.0;
576 for (
const auto& c : referenceData->constituents()) {
578 const auto&
name = c.name();
579 auto weight = c.weight();
581 if (weight == 0.0 || close(0.0, weight)) {
584 QL_REQUIRE(c.priorWeight() != Null<Real>(),
"Constituent "
585 <<
name <<
" in index CDS option trade " <<
id()
586 <<
" has defaulted so expecting a prior weight.");
587 QL_REQUIRE(c.recovery() != Null<Real>(),
"Constituent " <<
name <<
" in index CDS option trade " <<
id()
588 <<
" has defaulted so expecting a recovery.");
589 QL_REQUIRE(c.auctionDate() != Date(),
"Constituent " <<
name <<
" in index CDS option trade " <<
id()
590 <<
" has defaulted so expecting an auction date.");
591 QL_REQUIRE(c.auctionDate() <= asof,
"Constituent " <<
name <<
" in index CDS option trade " <<
id()
592 <<
" has defaulted so expecting the auction date ("
593 << io::iso_date(c.auctionDate())
594 <<
") to be before or on the valuation date ("
595 << io::iso_date(asof) <<
").");
597 totalWeight += c.priorWeight();
600 Real entityNtl = c.priorWeight() * fullNtl;
601 TLOG(
"Trade date (" << io::iso_date(
tradeDate_) <<
") is before auction date ("
602 << io::iso_date(c.auctionDate()) <<
") of " <<
name <<
" so updating trade date "
603 <<
"notional by amount " << entityNtl);
609 Real fepAmount = (1 - recovery) * c.priorWeight() * fullNtl;
610 TLOG(
"FEP start date (" << io::iso_date(
fepStartDate_) <<
") is before auction date ("
611 << io::iso_date(c.auctionDate()) <<
") of " <<
name <<
" so updating realised "
612 <<
"FEP by amount " << fepAmount);
616 }
else if (weight > 0.0) {
620 Real entityNtl = weight * fullNtl;
621 auto it = outConstituents.find(
name);
622 if (it == outConstituents.end()) {
623 outConstituents[
name] = entityNtl;
624 TLOG(
"Adding underlying " <<
name <<
" with weight " << weight <<
" (notional = " << entityNtl <<
")");
626 it->second += entityNtl;
627 TLOG(
"Updating underlying " <<
name <<
" with weight " << weight <<
" (notional = " << entityNtl
631 totalWeight += weight;
636 QL_FAIL(
"Constituent " <<
name <<
" in index CDS option trade " <<
id() <<
" has a negative weight "
641 DLOG(
"All underlyings added, total weight = " << totalWeight);
642 if (!close(1.0, totalWeight) && totalWeight > 1.0) {
643 ALOG(
"Total weight is greater than 1, possible error in CreditIndexReferenceDatum for "
644 << iCdsId <<
" while building constituents for trade " <<
id() <<
".");
647 DLOG(
"Finished building constituents using credit reference data.");
662 if (p.second != 0 * Days) {
const std::vector< BasketConstituent > & constituents() const
QuantLib::Real recoveryRate() const
QuantLib::Natural cashSettlementDays() const
const LegData & leg() const
bool settlesAccrual() const
const QuantLib::Date & tradeDate() const
PPT protectionPaymentTime() const
const string & creditCurveId() const
const Date & protectionStart() const
static constexpr const char * TYPE
Serializable object holding generic trade data, reporting dimensions.
const BasketData & basket() const
void fromXML(ore::data::XMLNode *node) override
ore::data::XMLNode * toXML(ore::data::XMLDocument &doc) const override
const QuantLib::Date & indexStartDateHint() const
std::string creditCurveIdWithTerm() const
const std::string & strikeType() const
const ore::data::OptionData & option() const
QuantLib::Real effectiveStrike() const
QuantLib::Real effectiveStrike_
const QuantLib::Date & tradeDate() const
const IndexCreditDefaultSwapData & swap() const
const std::string & indexTerm() const
QuantLib::Option::Type callPut() const
const CreditPortfolioSensitivityDecomposition sensitivityDecomposition() const
ore::data::OptionData option_
void fromXML(ore::data::XMLNode *node) override
void fromBasket(const QuantLib::Date &asof, std::map< std::string, QuantLib::Real > &constituents)
Populate constituent notionals and curve IDs from basket data.
const QuantLib::Date & fepStartDate() const
IndexCreditDefaultSwapData swap_
std::string effectiveStrikeType_
std::string creditCurveId() const
ore::data::XMLNode * toXML(ore::data::XMLDocument &doc) const override
const std::string & volCurveId() const
QuantLib::Date fepStartDate_
QuantLib::Real strike() const
void fromReferenceData(const QuantLib::Date &asof, std::map< std::string, QuantLib::Real > &constituents, const QuantLib::ext::shared_ptr< ReferenceDataManager > &refData)
Populate constituent notionals and curve IDs from reference data.
const std::map< std::string, QuantLib::Real > & constituents() const
CreditPortfolioSensitivityDecomposition sensitivityDecomposition_
QuantLib::Period effectiveIndexTerm_
QuantLib::Date tradeDate_
Notionals notionals_
Populated during trade building.
map< string, Real > constituents_
map of all the constituents to notionals
const std::string & effectiveStrikeType() const
const QuantLib::Period & effectiveIndexTerm() const
IndexCreditDefaultSwapOption()
Default constructor.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
QuantLib::Real notional() const override
Return the current notional in npvCurrency. See individual sub-classes for the precise definition.
void log() const
generate Boost log record to pass to corresponding sinks
const vector< double > & notionals() const
Serializable object holding option data.
const string & longShort() const
const string & style() const
const string & settlement() const
virtual void fromXML(XMLNode *node) override
virtual XMLNode * toXML(XMLDocument &doc) const override
const PremiumData & premiumData() const
const vector< double > & exerciseFees() const
const vector< string > & exerciseDates() const
Interface for Reference Data lookups.
Utility class for Structured Trade errors, contains the Trade ID and Type.
Utility classes for Structured warnings, contains the Trade ID and Type.
std::vector< bool > legPayers_
std::vector< string > legCurrencies_
std::vector< QuantLib::Leg > legs_
virtual void fromXML(XMLNode *node) override
Date addPremiums(std::vector< QuantLib::ext::shared_ptr< Instrument > > &instruments, std::vector< Real > &multipliers, const Real tradeMultiplier, const PremiumData &premiumData, const Real premiumMultiplier, const Currency &tradeCurrency, const QuantLib::ext::shared_ptr< EngineFactory > &factory, const string &configuration)
void setSensitivityTemplate(const EngineBuilder &builder)
virtual XMLNode * toXML(XMLDocument &doc) const override
QuantLib::ext::shared_ptr< InstrumentWrapper > instrument_
const string & tradeType() const
std::map< std::string, boost::any > additionalData_
Small XML Document wrapper class.
XMLNode * allocNode(const string &nodeName)
util functions that wrap rapidxml
static Real getChildValueAsDouble(XMLNode *node, const string &name, bool mandatory=false, double defaultValue=0.0)
static string getChildValue(XMLNode *node, const string &name, bool mandatory=false, const string &defaultValue=string())
static XMLNode * getChildNode(XMLNode *n, const string &name="")
static string getNodeValue(XMLNode *node)
Get a node's value.
static XMLNode * addChild(XMLDocument &doc, XMLNode *n, const string &name)
static void appendNode(XMLNode *parent, XMLNode *child)
DateGeneration::Rule parseDateGenerationRule(const string &s)
Convert text to QuantLib::DateGeneration::Rule.
CdsOption::StrikeType parseCdsOptionStrikeType(const string &s)
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Position::Type parsePositionType(const std::string &s)
Convert text to QuantLib::Position::Type.
BusinessDayConvention parseBusinessDayConvention(const string &s)
Convert text to QuantLib::BusinessDayConvention.
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Settlement::Type parseSettlementType(const std::string &s)
Convert text to QuantLib::Settlement::Type.
DayCounter parseDayCounter(const string &s)
Convert text to QuantLib::DayCounter.
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
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
QuantLib::Period implyIndexTerm(const Date &startDate, const Date &endDate)
std::string to_string(const LocationInfo &l)
CreditPortfolioSensitivityDecomposition
Enumeration CreditPortfolioSensitivityDecomposition.
std::pair< std::string, QuantLib::Period > splitCurveIdWithTenor(const std::string &creditCurveId)
Schedule makeSchedule(const ScheduleDates &data)
Serializable Credit Default Swap.
Wrapper for option instruments, tracks whether option has been exercised or not.
Map text representations to QuantLib/QuantExt types.
Hold related notionals that are known on valuation date.
QuantLib::Real realisedFep
The realised front end protection amount, as of the valuation date, that would be due on option exerc...
QuantLib::Real tradeDate
Outstanding index notional on the trade date of the index CDS option.
QuantLib::Real full
Notional assuming no defaults i.e. an index factor of 1. Equal to notional on swap_.
QuantLib::Real valuationDate
Outstanding index notional on the valuation date of the index CDS option.
Structured Trade Error class.
Classes for structured trade warnings.
string conversion utilities