Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
indexcreditdefaultswap.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
22
30
31#include <ql/time/daycounters/actual360.hpp>
32
33using namespace QuantLib;
34using namespace QuantExt;
35
36namespace ore {
37namespace data {
38
39void IndexCreditDefaultSwap::build(const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory) {
40 DLOG("IndexCreditDefaultSwap::build() called for trade " << id());
41
42 // ISDA taxonomy
43 additionalData_["isdaAssetClass"] = string("Credit");
44 additionalData_["isdaBaseProduct"] = string("Index");
45 string entity = swap_.creditCurveId();
46 QuantLib::ext::shared_ptr<ReferenceDataManager> refData = engineFactory->referenceData();
47 if (refData && refData->hasData("CreditIndex", entity)) {
48 auto refDatum = refData->getData("CreditIndex", entity);
49 QuantLib::ext::shared_ptr<CreditIndexReferenceDatum> creditIndexRefDatum =
50 QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(refDatum);
51 additionalData_["isdaSubProduct"] = creditIndexRefDatum->indexFamily();
52 if (creditIndexRefDatum->indexFamily() == "") {
53 ALOG("IndexFamily is blank in credit index reference data for entity " << entity);
54 }
55 } else {
56 ALOG("Credit index reference data missing for entity " << entity << ", isdaSubProduct left blank");
57 }
58 // skip the transaction level mapping for now
59 additionalData_["isdaTransaction"] = string("");
60
61 const QuantLib::ext::shared_ptr<Market> market = engineFactory->market();
62 QuantLib::ext::shared_ptr<EngineBuilder> builder = engineFactory->builder("IndexCreditDefaultSwap");
63
64 QL_REQUIRE(swap_.leg().legType() == "Fixed", "IndexCreditDefaultSwap requires Fixed leg");
65 QuantLib::ext::shared_ptr<FixedLegData> fixedLegData =
66 QuantLib::ext::dynamic_pointer_cast<FixedLegData>(swap_.leg().concreteLegData());
67
68 auto configuration = builder->configuration(MarketContext::pricing);
70 legs_.resize(1);
71 legs_[0] = flb.buildLeg(swap_.leg(), engineFactory, requiredFixings_, configuration);
72
73 Schedule schedule = makeSchedule(swap_.leg().schedule());
74 BusinessDayConvention payConvention = parseBusinessDayConvention(swap_.leg().paymentConvention());
75 Protection::Side prot = swap_.leg().isPayer() ? Protection::Side::Buyer : Protection::Side::Seller;
76
77 notional_ = notional();
78 DayCounter dc = parseDayCounter(swap_.leg().dayCounter());
79
80 // In general for CDS and CDS index trades, the standard day counter is Actual/360 and the final
81 // period coupon accrual includes the maturity date.
82 Actual360 standardDayCounter;
83 DayCounter lastPeriodDayCounter = dc == standardDayCounter ? Actual360(true) : dc;
84
85 QL_REQUIRE(fixedLegData->rates().size() == 1, "IndexCreditDefaultSwap requires single rate");
86
87 Real indexFactor = 1.0;
88
89 // From the basket data or reference data, we need a vector of notionals and a vector of creditCurves
90 vector<Real> basketNotionals;
91 vector<string> basketCreditCurves;
92
93 if (!swap_.basket().constituents().empty()) {
94
95 const auto& constituents = swap_.basket().constituents();
96 DLOG("Building constituents from basket data containing " << constituents.size() << " elements.");
97
98 Real totalNtl = 0.0;
99 for (const auto& c : constituents) {
100 Real ntl = Null<Real>();
101
102 const auto& creditCurve = c.creditCurveId();
103
104 if (c.weightInsteadOfNotional()) {
105 ntl = c.weight() * notional_;
106 } else {
107 ntl = c.notional();
108 }
109
110 if (!close(0.0, ntl) && ntl > 0.0) {
111 if (std::find(basketCreditCurves.begin(), basketCreditCurves.end(), creditCurve) ==
112 basketCreditCurves.end()) {
113 DLOG("Adding underlying " << creditCurve << " with notional " << ntl);
114 constituents_[creditCurve] = ntl;
115 basketCreditCurves.push_back(creditCurve);
116 basketNotionals.push_back(ntl);
117 totalNtl += ntl;
118 } else {
119 StructuredTradeErrorMessage(id(), "IndexCDS", "Error building trade",
120 ("Invalid Basket: found a duplicate credit curve " + creditCurve +
121 ", skip it. Check the basket data for possible errors.")
122 .c_str()).log();
123 }
124
125 } else {
126 DLOG("Skipped adding underlying, " << creditCurve << ", because its notional, " << ntl
127 << ", was non-positive.");
128 }
129 }
130 QL_REQUIRE(basketCreditCurves.size() == basketNotionals.size(),
131 "numbers of defaults curves (" << basketCreditCurves.size() << ") and notionals ("
132 << basketNotionals.size() << ") doesnt match");
133 DLOG("All underlyings added, total notional = " << totalNtl);
134
135 if (totalNtl > notional_ * (1.0 + 1.0E-4)) {
136 StructuredTradeErrorMessage(id(), "IndexCDS", "Error building trade",
137 ("Sum of basket notionals (" + std::to_string(totalNtl) +
138 ") is greater than trade notional (" + std::to_string(notional_) +
139 "). Check the basket data for possible errors.")
140 .c_str())
141 .log();
142 }
143
144 indexFactor = totalNtl / notional_;
145
146 DLOG("Finished building constituents using basket data.");
147
148 } else {
149 // get data from ReferenceDatum
150 string id = ore::data::splitCurveIdWithTenor(swap_.creditCurveId()).first;
151 DLOG("Getting CreditIndexReferenceDatum for id " << id);
152 QL_REQUIRE(engineFactory->referenceData(), "No BasketData or ReferenceDataManager");
153 QL_REQUIRE(engineFactory->referenceData()->hasData(CreditIndexReferenceDatum::TYPE, id),
154 "No CreditIndex reference data for " << id);
155 QuantLib::ext::shared_ptr<ReferenceDatum> refData =
156 engineFactory->referenceData()->getData(CreditIndexReferenceDatum::TYPE, id);
157 QuantLib::ext::shared_ptr<CreditIndexReferenceDatum> creditRefData =
158 QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(refData);
159 DLOG("Got CreditIndexReferenceDatum for id " << id);
160
161 Real totalWeight = 0.0;
162 for (const auto& c : creditRefData->constituents()) {
163
164 const auto& name = c.name();
165 auto weight = c.weight();
166
167 if (!close(0.0, weight) && weight > 0.0) {
168 DLOG("Adding underlying " << name << " with weight " << weight);
169 constituents_[name] = notional_ * weight;
170 basketCreditCurves.push_back(name);
171 basketNotionals.push_back(notional_ * weight);
172 totalWeight += weight;
173 } else {
174 DLOG("Skipped adding underlying, " << name << ", because its weight, " << weight
175 << ", was non-positive.");
176 }
177 }
178
179 indexFactor = totalWeight;
180
181 DLOG("All underlyings added, total weight = " << totalWeight);
182
183 if (!close(1.0, totalWeight) && totalWeight > 1.0) {
184 ALOG("Total weight is greater than 1, possible error in CreditIndexReferenceDatum");
185 }
186 }
187
188 QuantLib::ext::shared_ptr<QuantExt::IndexCreditDefaultSwap> cds;
189 if (swap_.upfrontFee() == Null<Real>()) {
190 cds = QuantLib::ext::make_shared<QuantExt::IndexCreditDefaultSwap>(
191 prot, indexFactor * notional_, basketNotionals, fixedLegData->rates().front(), schedule, payConvention, dc,
192 swap_.settlesAccrual(), swap_.protectionPaymentTime(), swap_.protectionStart(), QuantLib::ext::shared_ptr<Claim>(),
193 lastPeriodDayCounter, true, swap_.tradeDate(), swap_.cashSettlementDays());
194 } else {
195 cds = QuantLib::ext::make_shared<QuantExt::IndexCreditDefaultSwap>(
196 prot, indexFactor * notional_, basketNotionals, swap_.upfrontFee(), fixedLegData->rates().front(), schedule,
197 payConvention, dc, swap_.settlesAccrual(), swap_.protectionPaymentTime(), swap_.protectionStart(),
198 swap_.upfrontDate(), QuantLib::ext::shared_ptr<Claim>(), lastPeriodDayCounter, true, swap_.tradeDate(),
199 swap_.cashSettlementDays());
200 }
201
202 QuantLib::ext::shared_ptr<IndexCreditDefaultSwapEngineBuilder> cdsBuilder =
203 QuantLib::ext::dynamic_pointer_cast<IndexCreditDefaultSwapEngineBuilder>(builder);
204
205 npvCurrency_ = swap_.leg().currency();
206 notionalCurrency_ = swap_.leg().currency();
207
208 QL_REQUIRE(cdsBuilder, "No Builder found for IndexCreditDefaultSwap: " << id());
209 std::string curveIdWithTerm = swap_.creditCurveIdWithTerm();
210 // warn if the term can not be implied, except when a custom baskets is defined
211 if (swap_.basket().constituents().empty() && splitCurveIdWithTenor(curveIdWithTerm).second == 0 * Days) {
213 id(), tradeType(), "Could not imply Index CDS term.",
214 "Index CDS term could not be derived from start, end date, are these dates correct (credit curve id is '" +
215 swap_.creditCurveId() + "')").log();
216 }
217
218 maturity_ = cds->coupons().back()->date();
219
220 cds->setPricingEngine(cdsBuilder->engine(parseCurrency(npvCurrency_), swap_.creditCurveIdWithTerm(),
221 basketCreditCurves, boost::none, swap_.recoveryRate(), false));
222 setSensitivityTemplate(*cdsBuilder);
223
224 instrument_.reset(new VanillaInstrument(cds));
225
226
227 legs_ = {cds->coupons()};
228 legCurrencies_ = {npvCurrency_};
229 legPayers_ = {swap_.leg().isPayer()};
230
231 if (swap_.protectionStart() != Date())
232 additionalData_["startDate"] = to_string(swap_.protectionStart());
233 else
234 additionalData_["startDate"] = to_string(schedule.dates().front());
235
236 sensitivityDecomposition_ = cdsBuilder->sensitivityDecomposition();
237}
238
239const std::map<std::string, boost::any>& IndexCreditDefaultSwap::additionalData() const {
240 setLegBasedAdditionalData(0, 2);
241 additionalData_["legNPV[1]"] = instrument_->qlInstrument()->result<Real>("protectionLegNPV");
242 additionalData_["legNPV[2]"] = instrument_->qlInstrument()->result<Real>("premiumLegNPVDirty") +
243 instrument_->qlInstrument()->result<Real>("upfrontPremiumNPV") +
244 instrument_->qlInstrument()->result<Real>("accrualRebateNPV");
245 additionalData_["isPayer[1]"] = !swap_.leg().isPayer();
246 additionalData_["isPayer[2]"] = swap_.leg().isPayer();
247 additionalData_["legType[2]"] = swap_.leg().legType();
248 additionalData_["legType[1]"] = std::string("Protection");
249 additionalData_["currentNotional[1]"] = additionalData_["currentNotional[2]"];
250 additionalData_["originalNotional[1]"] = additionalData_["originalNotional[2]"];
251 additionalData_["notionalCurrency[1]"] = notionalCurrency_;
252 additionalData_["notionalCurrency[2]"] = notionalCurrency_;
253 return additionalData_;
254}
255
256
257QuantLib::Real IndexCreditDefaultSwap::notional() const {
258 Date asof = Settings::instance().evaluationDate();
259 // get the current notional from the premium leg
260 for (Size i = 0; i < legs_[0].size(); ++i) {
261 QuantLib::ext::shared_ptr<Coupon> coupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(legs_[0][i]);
262 if (coupon->date() > asof)
263 return coupon->nominal();
264 }
265
266 // if not provided, return null
267 ALOG("Error retrieving current notional for index credit default swap " << id() << " as of " << io::iso_date(asof));
268 return Null<Real>();
269}
270
271void IndexCreditDefaultSwap::fromXML(XMLNode* node) {
272 Trade::fromXML(node);
273 XMLNode* cdsNode = XMLUtils::getChildNode(node, "IndexCreditDefaultSwapData");
274 QL_REQUIRE(cdsNode, "No IndexCreditDefaultSwapData Node");
275 swap_.fromXML(cdsNode);
276}
277
278XMLNode* IndexCreditDefaultSwap::toXML(XMLDocument& doc) const {
279 XMLNode* node = Trade::toXML(doc);
280 XMLUtils::appendNode(node, swap_.toXML(doc));
281 return node;
282}
283} // namespace data
284} // namespace ore
Leg buildLeg(const LegData &data, const QuantLib::ext::shared_ptr< EngineFactory > &engineFactory, RequiredFixings &requiredFixings, const string &configuration, const QuantLib::Date &openEndDateReplacement=Null< Date >(), const bool useXbsCurves=false) const override
Definition: legbuilders.cpp:33
virtual void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
void log() const
generate Boost log record to pass to corresponding sinks
Definition: log.cpp:491
Utility class for Structured Trade errors, contains the Trade ID and Type.
Utility classes for Structured warnings, contains the Trade ID and Type.
Vanilla Instrument Wrapper.
Small XML Document wrapper class.
Definition: xmlutils.hpp:65
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Definition: parsers.cpp:290
BusinessDayConvention parseBusinessDayConvention(const string &s)
Convert text to QuantLib::BusinessDayConvention.
Definition: parsers.cpp:173
DayCounter parseDayCounter(const string &s)
Convert text to QuantLib::DayCounter.
Definition: parsers.cpp:209
Leg Builders.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define ALOG(text)
Logging Macro (Level = Alert)
Definition: log.hpp:544
market data related utilties
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
std::pair< std::string, QuantLib::Period > splitCurveIdWithTenor(const std::string &creditCurveId)
Definition: marketdata.cpp:231
Schedule makeSchedule(const ScheduleDates &data)
Definition: schedule.cpp:263
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.
Structured Trade Error class.
Classes for structured trade warnings.
string conversion utilities
string name