Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
indexcreditdefaultswapoption.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
31#include <ql/time/daycounters/actual360.hpp>
33
38using namespace QuantLib;
39using namespace QuantExt;
40using std::string;
41
42namespace ore {
43namespace data {
44
46 : Trade("IndexCreditDefaultSwapOption"), strike_(Null<Real>()) {}
47
49 const OptionData& option, Real strike,
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) {}
54
55void IndexCreditDefaultSwapOption::build(const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory) {
56
57 DLOG("IndexCreditDefaultSwapOption::build() called for trade " << id());
58
59 // ISDA taxonomy
60 additionalData_["isdaAssetClass"] = string("Credit");
61 additionalData_["isdaBaseProduct"] = string("Swaptions");
62 string entity = swap_.creditCurveId();
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);
68 additionalData_["isdaSubProduct"] = creditIndexRefDatum->indexFamily();
69 if (creditIndexRefDatum->indexFamily() == "") {
70 ALOG("IndexFamily is blank in credit index reference data for entity " << entity);
71 }
72 } else {
73 ALOG("Credit index reference data missing for entity " << entity << ", isdaSubProduct left blank");
74 }
75 // skip the transaction level mapping for now
76 additionalData_["isdaTransaction"] = string("");
77
78 // Dates
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();
83 }
84
85 if (tradeDate_ == Date()) {
86 tradeDate_ = asof;
87 } else {
88 QL_REQUIRE(tradeDate_ <= asof, "Trade date (" << io::iso_date(tradeDate_) << ") should be on or "
89 << "before the valuation date (" << io::iso_date(asof) << ")");
90 }
91
92 if (fepStartDate_ == Date()) {
94 } else {
95 QL_REQUIRE(fepStartDate_ <= tradeDate_, "Front end protection start date ("
96 << io::iso_date(fepStartDate_)
97 << ") should be on or before the trade date ("
98 << io::iso_date(tradeDate_) << ")");
99 }
100
101 // Option trade notional. This is the full notional of the index that is being traded not reduced by any
102 // defaults. The notional on the trade date will be a fraction of this if there are defaults by trade date.
103 auto legData = swap_.leg();
104 const auto& ntls = legData.notionals();
105 QL_REQUIRE(ntls.size() == 1, "IndexCreditDefaultSwapOption requires a single notional.");
107 notionals_.full = ntls.front();
108 notionalCurrency_ = legData.currency();
109 npvCurrency_ = legData.currency();
110
111 // Need fixed leg data with one rate. This should be the standard running coupon on the index CDS e.g.
112 // 100bp for CDX IG and 500bp for CDX HY.
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();
117 Real upfrontFee = swap_.upfrontFee();
118
119 // Usually, we expect a Strike and StrikeType. However, for backwards compatibility we also allow for
120 // empty values and populate Strike, StrikeType from the underlying upfront and coupon.
121 QL_REQUIRE(strikeType_ == "" || strikeType_ == "Spread" || strikeType_ == "Price",
122 "invalid StrikeType (" << strikeType_ << "), expected 'Spread' or 'Price' or empty value");
123 if (strike_ == Null<Real>() && strikeType_ == "" && upfrontFee == Null<Real>()) {
124 effectiveStrike_ = runningCoupon;
125 effectiveStrikeType_ = "Spread";
126 } else if (strike_ == Null<Real>() && strikeType_ == "Spread" && upfrontFee == Null<Real>()) {
127 effectiveStrike_ = runningCoupon;
128 effectiveStrikeType_ = "Spread";
129 } else if (strike_ == Null<Real>() && strikeType_ == "Price" && upfrontFee == Null<Real>()) {
130 effectiveStrike_ = 1.0;
131 effectiveStrikeType_ = "Price";
132 } else if (strike_ != Null<Real>() && strikeType_ == "" && upfrontFee == Null<Real>()) {
134 effectiveStrikeType_ = "Spread";
135 } else if (strike_ != Null<Real>() && strikeType_ == "Spread" && upfrontFee == Null<Real>()) {
137 effectiveStrikeType_ = "Spread";
138 } else if (strike_ != Null<Real>() && strikeType_ == "Price" && upfrontFee == Null<Real>()) {
140 effectiveStrikeType_ = "Price";
141 } else if (strike_ == Null<Real>() && strikeType_ == "" && upfrontFee != Null<Real>()) {
142 effectiveStrike_ = 1.0 - upfrontFee;
143 effectiveStrikeType_ = "Price";
144 } else if (strike_ == Null<Real>() && strikeType_ == "Spread" && upfrontFee != Null<Real>()) {
145 if (close_enough(upfrontFee, 0.0)) {
146 effectiveStrike_ = runningCoupon;
147 effectiveStrikeType_ = "Spread";
148 } else {
149 QL_FAIL("StrikeType 'Spread' and non-zero upfront fee can not be combined.");
150 }
151 } else if (strike_ == Null<Real>() && strikeType_ == "Price" && upfrontFee != Null<Real>()) {
152 effectiveStrike_ = 1.0 - upfrontFee;
153 effectiveStrikeType_ = "Price";
154 } else if (strike_ != Null<Real>() && strikeType_ == "" && upfrontFee != Null<Real>()) {
155 if (close_enough(upfrontFee, 0.0)) {
157 effectiveStrikeType_ = "Spread";
158 } else {
159 QL_FAIL("Strike and non-zero upfront can not be combined.");
160 }
161 } else if (strike_ != Null<Real>() && strikeType_ == "Spread" && upfrontFee != Null<Real>()) {
162 if (close_enough(upfrontFee, 0.0)) {
164 effectiveStrikeType_ = "Spread";
165 } else {
166 QL_FAIL("Strike and non-zero upfront can not be combined.");
167 }
168 } else if (strike_ != Null<Real>() && strikeType_ == "Price" && upfrontFee != Null<Real>()) {
169 if (close_enough(upfrontFee, 0.0)) {
171 effectiveStrikeType_ = "Price";
172 } else {
173 QL_FAIL("Strike and non-zero upfront can not be combined.");
174 }
175 } else {
176 QL_FAIL("internal error, impossible branch in strike / strike type deduction.");
177 }
178 DLOG("Will use strike = " << effectiveStrike_ << ", strikeType = " << effectiveStrikeType_);
179
180 // Payer (Receiver) swaption if the leg is paying (receiving).
181 auto side = legData.isPayer() ? Protection::Side::Buyer : Protection::Side::Seller;
182
183 // Populate the constituents and determine the various notional amounts.
184 constituents_.clear();
185 if (swap_.basket().constituents().size() > 1) {
187 } else {
188 fromReferenceData(asof, constituents_, engineFactory->referenceData());
189 }
190
191 // Transfer to vectors for ctors below
192 vector<string> constituentIds;
193 constituentIds.reserve(constituents_.size());
194 vector<Real> constituentNtls;
195 constituentNtls.reserve(constituents_.size());
196 for (const auto& kv : constituents_) {
197 constituentIds.push_back(kv.first);
198 constituentNtls.push_back(kv.second);
199 }
200
201 // Day counter. In general for CDS and CDS index trades, the standard day counter is Actual/360 and the final
202 // period coupon accrual includes the maturity date.
203 DayCounter dc = parseDayCounter(legData.dayCounter());
204 Actual360 standardDayCounter;
205 DayCounter lastPeriodDayCounter = dc == standardDayCounter ? Actual360(true) : dc;
206
207 // Checks on the option data
208 QL_REQUIRE(option_.style() == "European", "IndexCreditDefaultSwapOption option style must"
209 << " be European but got " << option_.style() << ".");
210 QL_REQUIRE(option_.exerciseFees().empty(), "IndexCreditDefaultSwapOption cannot handle exercise fees.");
211
212 // Exercise must be European
213 const auto& exerciseDates = option_.exerciseDates();
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);
218
219 QL_REQUIRE(parseDate(legData.schedule().rules().front().endDate()) > exerciseDate,
220 "IndexCreditDefaultSwapOption: ExerciseDate must be before EndDate");
221
222 // We apply an automatic correction to a common mistake in the input data, where the full index underlying
223 // is provided and not only the part of the underlying into which we exercise.
224 if (legData.schedule().rules().size() == 1 && legData.schedule().dates().empty()) {
225 // The start date should be >= exercise date, this will produce correct coupons for both
226 // - post big bang rules CDS, CDS2015 (full first coupon) and
227 // - pre big bang rules (short first coupon)
228 if (parseDate(legData.schedule().rules().front().startDate()) < exerciseDate) {
229 legData.schedule().modifyRules().front().modifyStartDate() = ore::data::to_string(exerciseDate);
230 }
231 }
232
233 // Schedule
234 Schedule schedule = makeSchedule(legData.schedule());
235 BusinessDayConvention payConvention = parseBusinessDayConvention(legData.paymentConvention());
236
237 // Populate trade date and protection start date of underlying swap
238 QL_REQUIRE(!schedule.dates().empty(),
239 "IndexCreditDefaultSwapOption: underlying swap schedule does not contain any dates");
240 Date underlyingTradeDate =
241 swap_.tradeDate() == Date() ? std::max(exerciseDate, schedule.dates().front()) : swap_.tradeDate();
242 Date underlyingProtectionStart;
243 if (swap_.protectionStart() != Date()) {
244 underlyingProtectionStart = swap_.protectionStart();
245 } else if (legData.schedule().rules().size() == 1 && legData.schedule().dates().empty()) {
246 auto rule = parseDateGenerationRule(legData.schedule().rules().front().rule());
247 if (rule == DateGeneration::CDS || rule == DateGeneration::CDS2015) {
248 underlyingProtectionStart = std::max(exerciseDate, schedule.dates().front());
249 } else {
250 underlyingProtectionStart = schedule.dates().front();
251 }
252 } else {
253 underlyingProtectionStart = std::max(exerciseDate, schedule.dates().front());
254 }
255
256 // get engine builders for option and underlying swap
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");
265
266 // The underlying index CDS as it looks on the valuation date i.e. outstanding notional is the valuation
267 // date notional and the basket of notionals contains only those reference entities not defaulted (i.e.
268 // those with auction date in the future to be more precise).
269 auto cds = QuantLib::ext::make_shared<QuantExt::IndexCreditDefaultSwap>(
270 side, notionals_.valuationDate, constituentNtls, runningCoupon, schedule, payConvention, dc,
271 swap_.settlesAccrual(), swap_.protectionPaymentTime(), underlyingProtectionStart, QuantLib::ext::shared_ptr<Claim>(),
272 lastPeriodDayCounter, true, underlyingTradeDate, swap_.cashSettlementDays());
273
274 // Set engine on the underlying CDS.
275 auto ccy = parseCurrency(npvCurrency_);
276 std::string overrideCurve = iCdsOptionEngineBuilder->engineParameter("Curve", {}, false, "Underlying");
277
278 auto creditCurveId = this->creditCurveId();
279 // warn if that is not possible, except for trades on custom baskets
280 if (swap_.basket().constituents().empty() && splitCurveIdWithTenor(creditCurveId).second == 0 * Days) {
281 StructuredTradeWarningMessage(id(), tradeType(), "Could not imply Index CDS term.",
282 "Index CDS term could not be derived from start, end date, are these "
283 "dates correct (credit curve id is '" +
284 swap_.creditCurveId() + "')")
285 .log();
286 }
287
288 // for cash settlement build the underlying swap with the inccy discount curve
289 Settlement::Type settleType = parseSettlementType(option_.settlement());
290 cds->setPricingEngine(iCdsEngineBuilder->engine(ccy, creditCurveId, constituentIds, overrideCurve,
291 swap_.recoveryRate(), settleType == Settlement::Cash));
292 setSensitivityTemplate(*iCdsEngineBuilder);
293
294 // Strike may be in terms of spread or price
296
297 // Determine the index term;
298 effectiveIndexTerm_ = 5 * Years;
299 if (!indexTerm_.empty()) {
300 // if the option has an explicit index term set, we use that
302 } else {
303 // otherwise we derive the index term from the start date (or an externally set hint for that)
304 effectiveIndexTerm_ = QuantExt::implyIndexTerm(swap_.indexStartDateHint() == Date() ? schedule.dates().front()
306 schedule.dates().back());
307 }
308
309 // Build the option
310 auto option = QuantLib::ext::make_shared<QuantExt::IndexCdsOption>(cds, exercise, effectiveStrike_, strikeType, settleType,
313 // the vol curve id is the credit curve id stripped by a term, if the credit curve id should contain one
315 volCurveId_ = p.first;
316 option->setPricingEngine(iCdsOptionEngineBuilder->engine(ccy, creditCurveId, volCurveId_, constituentIds));
317 setSensitivityTemplate(*iCdsOptionEngineBuilder);
318
319 // Keep this comment about the maturity date being the underlying maturity instead of the option expiry.
320 // [RL] Align option product maturities with ISDA AANA/GRID guidance as of November 2020.
321 maturity_ = cds->coupons().back()->date();
322
323 // Set Trade members _before_ possibly adding the premium payment below.
324 legs_ = {cds->coupons()};
326 legPayers_ = {legData.isPayer()};
327
328 // Long or short the option
329 Position::Type positionType = parsePositionType(option_.longShort());
330 Real indicatorLongShort = positionType == Position::Long ? 1.0 : -1.0;
331
332 // Include premium if enough information is provided
333 vector<QuantLib::ext::shared_ptr<Instrument>> additionalInstruments;
334 vector<Real> additionalMultipliers;
335 string configuration = iCdsOptionEngineBuilder->configuration(MarketContext::pricing);
336 maturity_ =
337 std::max(maturity_, addPremiums(additionalInstruments, additionalMultipliers, indicatorLongShort,
338 option_.premiumData(), -indicatorLongShort, ccy, engineFactory, configuration));
339
340 // Instrument wrapper depends on the settlement type.
341 // The instrument build should be indpednent of the evaluation date. However, the general behavior
342 // in ORE (e.g. IR swaptions) for normal pricing runs is that the option is considered expired on
343 // the expiry date with no assumptions on an (automatic) exercise. Therefore we build a vanilla
344 // instrument if the exercise date is <= the eval date at build time.
345 if (settleType == Settlement::Cash || exerciseDate <= Settings::instance().evaluationDate()) {
346 instrument_ = QuantLib::ext::make_shared<VanillaInstrument>(option, indicatorLongShort, additionalInstruments,
347 additionalMultipliers);
348 } else {
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);
353 }
354
355 sensitivityDecomposition_ = iCdsOptionEngineBuilder->sensitivityDecomposition();
356}
357
359
360 if (notionals_.valuationDate == Null<Real>()) {
361 ALOG("Error retrieving current notional for index credit default swap option "
362 << id() << " as of " << Settings::instance().evaluationDate());
363 }
364
366}
367
369
370 Trade::fromXML(node);
371
372 XMLNode* iCdsOptionData = XMLUtils::getChildNode(node, "IndexCreditDefaultSwapOptionData");
373 QL_REQUIRE(iCdsOptionData, "Expected IndexCreditDefaultSwapOptionData node on trade " << id() << ".");
374 strike_ = XMLUtils::getChildValueAsDouble(iCdsOptionData, "Strike", false, Null<Real>());
375 indexTerm_ = XMLUtils::getChildValue(iCdsOptionData, "IndexTerm", false);
376 strikeType_ = XMLUtils::getChildValue(iCdsOptionData, "StrikeType", false);
377 tradeDate_ = Date();
378 if (auto n = XMLUtils::getChildNode(iCdsOptionData, "TradeDate")) {
380 }
381 fepStartDate_ = Date();
382 if (auto n = XMLUtils::getChildNode(iCdsOptionData, "FrontEndProtectionStartDate")) {
384 }
385
386 XMLNode* iCdsData = XMLUtils::getChildNode(iCdsOptionData, "IndexCreditDefaultSwapData");
387 QL_REQUIRE(iCdsData, "Expected IndexCreditDefaultSwapData node on trade " << id() << ".");
388 swap_.fromXML(iCdsData);
389
390 XMLNode* optionData = XMLUtils::getChildNode(iCdsOptionData, "OptionData");
391 QL_REQUIRE(optionData, "Expected OptionData node on trade " << id() << ".");
392 option_.fromXML(optionData);
393}
394
396
397 // Trade node
398 XMLNode* node = Trade::toXML(doc);
399
400 // IndexCreditDefaultSwapOptionData node
401 XMLNode* iCdsOptionData = doc.allocNode("IndexCreditDefaultSwapOptionData");
402 if (strike_ != Null<Real>())
403 XMLUtils::addChild(doc, iCdsOptionData, "Strike", strike_);
404 if (!indexTerm_.empty())
405 XMLUtils::addChild(doc, iCdsOptionData, "IndexTerm", indexTerm_);
406 if (strikeType_ != "")
407 XMLUtils::addChild(doc, iCdsOptionData, "StrikeType", strikeType_);
408 if (tradeDate_ != Date())
409 XMLUtils::addChild(doc, iCdsOptionData, "TradeDate", to_string(tradeDate_));
410 if (fepStartDate_ != Date())
411 XMLUtils::addChild(doc, iCdsOptionData, "FrontEndProtectionStartDate", to_string(fepStartDate_));
412
413 XMLUtils::appendNode(iCdsOptionData, swap_.toXML(doc));
414 XMLUtils::appendNode(iCdsOptionData, option_.toXML(doc));
415
416 // Add IndexCreditDefaultSwapOptionData node to Trade node
417 XMLUtils::appendNode(node, iCdsOptionData);
418
419 return node;
420}
421
423
425
426const std::string& IndexCreditDefaultSwapOption::indexTerm() const { return indexTerm_; }
427
429
430QuantLib::Option::Type IndexCreditDefaultSwapOption::callPut() const {
431 if (swap().leg().isPayer())
432 return QuantLib::Option::Type::Call;
433 else
434 return QuantLib::Option::Type::Put;
435}
436
438
440
442
443void IndexCreditDefaultSwapOption::fromBasket(const Date& asof, map<string, Real>& outConstituents) {
444
445 const auto& constituents = swap_.basket().constituents();
446 DLOG("Building constituents from basket data containing " << constituents.size() << " elements.");
447
448 Real totalNtl = 0.0;
449 Real fullNtl = notionals_.full;
450 for (const auto& c : constituents) {
451 Real ntl = Null<Real>();
452 Real priorNotional = Null<Real>();
453
454 const auto& creditCurve = c.creditCurveId();
455
456 if (c.weightInsteadOfNotional()) {
457 ntl = c.weight() * fullNtl;
458 } else {
459 ntl = c.notional();
460 }
461
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;
467 }
468 } else {
469 priorNotional = c.priorNotional();
470 }
471 // Entity is not in the index. Its auction date is in the past.
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 "
476 << id()
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) << ".");
485
486 totalNtl += priorNotional * notional_;
487
488 if (tradeDate_ < c.auctionDate()) {
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);
493 notionals_.tradeDate += priorNotional;
494 }
495
496 if (fepStartDate_ < c.auctionDate()) {
497 Real recovery = swap_.recoveryRate() != Null<Real>() ? swap_.recoveryRate() : c.recovery();
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);
503 notionals_.realisedFep += fepAmount;
504 }
505
506 } else if (ntl > 0.0) {
507
508 // Entity is still in the index.
509 // Note that it may have defaulted but its auction date is still in the future.
510 auto it = outConstituents.find(creditCurve);
511 if (it == outConstituents.end()) {
512 outConstituents[creditCurve] = ntl;
513 TLOG("Adding underlying " << creditCurve << " with notional " << ntl);
514 totalNtl += ntl;
515 notionals_.tradeDate += ntl;
517 } else {
518 StructuredTradeErrorMessage(id(), "IndexCDSOption", "Error building trade",
519 ("Invalid Basket: found a duplicate credit curve " + creditCurve +
520 ".Skip it. Check the basket data for possible errors.")
521 .c_str())
522 .log();
523 }
524 } else {
525 QL_FAIL("Constituent " << creditCurve << " in index CDS option trade " << id()
526 << " has a negative notional " << ntl << ".");
527 }
528 }
529
530 Real correctionFactor = fullNtl / totalNtl;
531 // Scaling to Notional if relative error is close less than 10^-4
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.");
535
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;
540 }
541
542 totalNtl *= correctionFactor;
543 notionals_.tradeDate *= correctionFactor;
544 notionals_.valuationDate *= correctionFactor;
545 notionals_.realisedFep *= correctionFactor;
546 }
547
548 DLOG("All underlyings added, total notional = " << totalNtl);
549 if (!close(fullNtl, totalNtl) && totalNtl > fullNtl) {
550 StructuredTradeErrorMessage(id(), "IndexCDSOption", "Error building trade",
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.")
554 .c_str())
555 .log();
556 }
557
558 DLOG("Finished building constituents using basket data.");
559}
560
561void IndexCreditDefaultSwapOption::fromReferenceData(const Date& asof, map<string, Real>& outConstituents,
562 const QuantLib::ext::shared_ptr<ReferenceDataManager>& refData) {
563
564 const string& iCdsId = swap_.creditCurveId();
565 DLOG("Start building constituents using credit reference data for " << iCdsId << ".");
566
567 QL_REQUIRE(refData, "Building index CDS option " << id() << " ReferenceDataManager is null.");
568 QL_REQUIRE(refData->hasData(CreditIndexReferenceDatum::TYPE, iCdsId),
569 "No CreditIndex reference data for " << iCdsId);
570 auto referenceData = QuantLib::ext::dynamic_pointer_cast<CreditIndexReferenceDatum>(
571 refData->getData(CreditIndexReferenceDatum::TYPE, iCdsId));
572 DLOG("Got CreditIndexReferenceDatum for id " << iCdsId);
573
574 Real fullNtl = notionals_.full;
575 Real totalWeight = 0.0;
576 for (const auto& c : referenceData->constituents()) {
577
578 const auto& name = c.name();
579 auto weight = c.weight();
580
581 if (weight == 0.0 || close(0.0, weight)) {
582
583 // Entity is not in the index. Its auction date is in the past.
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) << ").");
596
597 totalWeight += c.priorWeight();
598
599 if (tradeDate_ < c.auctionDate()) {
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);
604 notionals_.tradeDate += entityNtl;
605 }
606
607 if (fepStartDate_ < c.auctionDate()) {
608 Real recovery = swap_.recoveryRate() != Null<Real>() ? swap_.recoveryRate() : c.recovery();
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);
613 notionals_.realisedFep += fepAmount;
614 }
615
616 } else if (weight > 0.0) {
617
618 // Entity is still in the index.
619 // Note that it may have defaulted but its auction date is still in the future.
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 << ")");
625 } else {
626 it->second += entityNtl;
627 TLOG("Updating underlying " << name << " with weight " << weight << " (notional = " << entityNtl
628 << ")");
629 }
630
631 totalWeight += weight;
632 notionals_.tradeDate += entityNtl;
633 notionals_.valuationDate += entityNtl;
634
635 } else {
636 QL_FAIL("Constituent " << name << " in index CDS option trade " << id() << " has a negative weight "
637 << weight << ".");
638 }
639 }
640
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() << ".");
645 }
646
647 DLOG("Finished building constituents using credit reference data.");
648}
649
652}
653
655
657
659
662 if (p.second != 0 * Days) {
663 // if the credit curve id has a suffix "_5Y" already, we use that
664 return swap_.creditCurveId();
665 } else if (!indexTerm_.empty()) {
666 // if not and we have a term we use that
667 return p.first + "_" + indexTerm_;
668 } else {
669 // if not we imply the term from the swap schedule
671 }
672}
673
674const std::string& IndexCreditDefaultSwapOption::volCurveId() const { return volCurveId_; }
675
676const map<string, Real>& IndexCreditDefaultSwapOption::constituents() const { return constituents_; };
677
678} // namespace data
679} // namespace ore
const std::vector< BasketConstituent > & constituents() const
Definition: basketdata.cpp:263
QuantLib::Natural cashSettlementDays() const
const QuantLib::Date & tradeDate() const
static constexpr const char * TYPE
Serializable object holding generic trade data, reporting dimensions.
Definition: envelope.hpp:51
void fromXML(ore::data::XMLNode *node) override
ore::data::XMLNode * toXML(ore::data::XMLDocument &doc) const override
const QuantLib::Date & indexStartDateHint() const
const ore::data::OptionData & option() const
const IndexCreditDefaultSwapData & swap() const
const CreditPortfolioSensitivityDecomposition sensitivityDecomposition() const
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.
ore::data::XMLNode * toXML(ore::data::XMLDocument &doc) const override
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_
Notionals notionals_
Populated during trade building.
map< string, Real > constituents_
map of all the constituents to notionals
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
Definition: log.cpp:491
const vector< double > & notionals() const
Definition: legdata.hpp:875
Serializable object holding option data.
Definition: optiondata.hpp:42
const string & longShort() const
Definition: optiondata.hpp:70
const string & style() const
Definition: optiondata.hpp:74
const string & settlement() const
Definition: optiondata.hpp:81
virtual void fromXML(XMLNode *node) override
Definition: optiondata.cpp:32
virtual XMLNode * toXML(XMLDocument &doc) const override
Definition: optiondata.cpp:86
const PremiumData & premiumData() const
Definition: optiondata.hpp:83
const vector< double > & exerciseFees() const
Definition: optiondata.hpp:84
const vector< string > & exerciseDates() const
Definition: optiondata.hpp:76
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.
Trade base class.
Definition: trade.hpp:55
string npvCurrency_
Definition: trade.hpp:201
std::vector< bool > legPayers_
Definition: trade.hpp:200
std::vector< string > legCurrencies_
Definition: trade.hpp:199
std::vector< QuantLib::Leg > legs_
Definition: trade.hpp:198
QuantLib::Real notional_
Definition: trade.hpp:202
virtual void fromXML(XMLNode *node) override
Definition: trade.cpp:34
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)
Definition: trade.cpp:58
void setSensitivityTemplate(const EngineBuilder &builder)
Definition: trade.cpp:295
virtual XMLNode * toXML(XMLDocument &doc) const override
Definition: trade.cpp:46
QuantLib::ext::shared_ptr< InstrumentWrapper > instrument_
Definition: trade.hpp:197
string notionalCurrency_
Definition: trade.hpp:203
const string & tradeType() const
Definition: trade.hpp:133
std::map< std::string, boost::any > additionalData_
Definition: trade.hpp:224
Small XML Document wrapper class.
Definition: xmlutils.hpp:65
XMLNode * allocNode(const string &nodeName)
util functions that wrap rapidxml
Definition: xmlutils.cpp:132
static Real getChildValueAsDouble(XMLNode *node, const string &name, bool mandatory=false, double defaultValue=0.0)
Definition: xmlutils.cpp:286
static string getChildValue(XMLNode *node, const string &name, bool mandatory=false, const string &defaultValue=string())
Definition: xmlutils.cpp:277
static XMLNode * getChildNode(XMLNode *n, const string &name="")
Definition: xmlutils.cpp:387
static string getNodeValue(XMLNode *node)
Get a node's value.
Definition: xmlutils.cpp:489
static XMLNode * addChild(XMLDocument &doc, XMLNode *n, const string &name)
Definition: xmlutils.cpp:181
static void appendNode(XMLNode *parent, XMLNode *child)
Definition: xmlutils.cpp:406
DateGeneration::Rule parseDateGenerationRule(const string &s)
Convert text to QuantLib::DateGeneration::Rule.
Definition: parsers.cpp:328
CdsOption::StrikeType parseCdsOptionStrikeType(const string &s)
Definition: parsers.cpp:1240
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Definition: parsers.cpp:51
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Definition: parsers.cpp:290
Position::Type parsePositionType(const std::string &s)
Convert text to QuantLib::Position::Type.
Definition: parsers.cpp:404
BusinessDayConvention parseBusinessDayConvention(const string &s)
Convert text to QuantLib::BusinessDayConvention.
Definition: parsers.cpp:173
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Definition: parsers.cpp:171
Settlement::Type parseSettlementType(const std::string &s)
Convert text to QuantLib::Settlement::Type.
Definition: parsers.cpp:434
DayCounter parseDayCounter(const string &s)
Convert text to QuantLib::DayCounter.
Definition: parsers.cpp:209
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
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
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)
Definition: ast.cpp:28
CreditPortfolioSensitivityDecomposition
Enumeration CreditPortfolioSensitivityDecomposition.
Definition: parsers.hpp:568
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
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
string name