Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
discountingcreditlinkedswapengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 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
20
22
23#include <ql/cashflows/coupon.hpp>
24#include <ql/timegrid.hpp>
25
26#include <iostream>
27
28namespace QuantExt {
29
30using namespace QuantLib;
31
33 const Handle<YieldTermStructure>& irCurve, const Handle<DefaultProbabilityTermStructure>& creditCurve,
34 const Handle<Quote>& marketRecovery, const Size timeStepsPerYear, const bool generateAdditionalResults)
35 : irCurve_(irCurve), creditCurve_(creditCurve), marketRecovery_(marketRecovery),
36 timeStepsPerYear_(timeStepsPerYear), generateAdditionalResults_(generateAdditionalResults) {
37 registerWith(irCurve_);
38 registerWith(creditCurve_);
39 registerWith(marketRecovery_);
40}
41
43 QL_REQUIRE(!irCurve_.empty(), "DiscountingCreditLinkedSwapEngine::calculate(): ir curve is empty");
44 QL_REQUIRE(!creditCurve_.empty(), "DiscountingCreditLinkedSwapEngine::calculate(): ir curve is empty");
45 QL_REQUIRE(!marketRecovery_.empty(), "DiscountingCreditLinkedSwapEngine::calculate(): market recovery is empty");
46
47 Date today = Settings::instance().evaluationDate();
48 Real npv_independent = 0.0, npv_contingent = 0.0, default_accrual_contingent = 0.0, npv_default_payments = 0.0,
49 npv_recovery_payments = 0.0;
50
51 std::vector<QuantExt::CashFlowResults> cfResults;
52
53 // handle the independent and contingent payments (including an accrual settlement for the latter, if active)
54
55 for (Size i = 0; i < arguments_.legs.size(); ++i) {
56
57 Real multiplier = (arguments_.legPayers[i] ? -1.0 : 1.0);
58
59 switch (arguments_.legTypes[i]) {
61 for (auto const& c : arguments_.legs[i]) {
62 if (c->date() <= today)
63 continue;
64 npv_independent += multiplier * c->amount() * irCurve_->discount(c->date());
66 cfResults.push_back(
67 standardCashFlowResults(c, multiplier, "Independent", 0, arguments_.currency, irCurve_));
68 }
69 }
70 break;
71 }
72
74 for (auto const& c : arguments_.legs[i]) {
75 if (c->date() <= today)
76 continue;
77 Real dsc = irCurve_->discount(c->date());
78 Real P = creditCurve_->survivalProbability(c->date());
79 npv_contingent += multiplier * c->amount() * dsc * P;
80
82 cfResults.push_back(standardCashFlowResults(c, multiplier, "CreditLinked", 1, arguments_.currency));
83 cfResults.back().amount *= P;
84 cfResults.back().discountFactor = dsc;
85 cfResults.back().presentValue = cfResults.back().amount * dsc;
86 }
87
88 if (arguments_.settlesAccrual) {
89 if (auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(c)) {
90 Date start = std::max(cpn->accrualStartDate(), today);
91 Date end = cpn->accrualEndDate();
92 if (end > today) {
93 Date mid = Date((start.serialNumber() + end.serialNumber()) / 2);
94 Real P = creditCurve_->survivalProbability(start) - creditCurve_->survivalProbability(end);
95 Real dsc = irCurve_->discount(mid);
96 default_accrual_contingent += multiplier * cpn->accruedAmount(mid) * P * dsc;
97
99 cfResults.push_back(CashFlowResults());
100 cfResults.back().amount = multiplier * cpn->accruedAmount(mid) * P;
101 cfResults.back().accrualStartDate = cpn->accrualStartDate();
102 cfResults.back().accrualEndDate = cpn->accrualEndDate();
103 cfResults.back().payDate = mid;
104 cfResults.back().currency = arguments_.currency.code();
105 cfResults.back().legNumber = 2;
106 cfResults.back().type = "CreditLinkedDefaultAccrual";
107 cfResults.back().discountFactor = dsc;
108 cfResults.back().presentValue = cfResults.back().amount * dsc;
109 }
110 }
111 }
112 }
113 }
114 }
115
116 default: {
117 break;
118 }
119 }
120 }
121
122 // handle the default and recovery payments
123
124 std::set<Real> defaultPaymentTimes, recoveryPaymentTimes, allTimes;
125 for (Size i = 0; i < arguments_.legs.size(); ++i) {
127 for (auto const& c : arguments_.legs[i]) {
128 if (c->date() > today) {
129 defaultPaymentTimes.insert(creditCurve_->timeFromReference(c->date()));
130 }
131 }
133 for (auto const& c : arguments_.legs[i]) {
134 if (c->date() > today) {
135 recoveryPaymentTimes.insert(creditCurve_->timeFromReference(c->date()));
136 }
137 }
138 }
139 }
140
141 std::vector<Real> defaultPaymentAmounts(defaultPaymentTimes.size() + 1, 0.0),
142 recoveryPaymentAmounts(recoveryPaymentTimes.size() + 1, 0.0);
143 std::vector<Date> defaultPeriodEndDates(defaultPaymentTimes.size() + 1, Null<Date>()),
144 recoveryPeriodEndDates(recoveryPaymentTimes.size() + 1, Null<Date>());
145
146 for (Size i = 0; i < arguments_.legs.size(); ++i) {
147 Real multiplier = (arguments_.legPayers[i] ? -1.0 : 1.0);
149 for (auto const& c : arguments_.legs[i]) {
150 if (c->date() > today) {
151 Size index = std::distance(defaultPaymentTimes.begin(),
152 defaultPaymentTimes.find(creditCurve_->timeFromReference(c->date())));
153 QL_REQUIRE(
154 index < defaultPaymentTimes.size(),
155 "DiscountingCreditLinkedSwapEngine: internal error: default payment times index out of range");
156 defaultPaymentAmounts[index] += c->amount() * multiplier;
157 defaultPeriodEndDates[index] = c->date();
158 }
159 }
161 for (auto const& c : arguments_.legs[i]) {
162 if (c->date() > today) {
163 Size index = std::distance(recoveryPaymentTimes.begin(),
164 recoveryPaymentTimes.find(creditCurve_->timeFromReference(c->date())));
165 QL_REQUIRE(
166 index < recoveryPaymentTimes.size(),
167 "DiscountingCreditLinkedSwapEngine: internal error: recovery payment times index out of range");
168 recoveryPaymentAmounts[index] += c->amount() * multiplier;
169 recoveryPeriodEndDates[index] = c->date();
170 }
171 }
172 }
173 }
174
175 allTimes.insert(defaultPaymentTimes.begin(), defaultPaymentTimes.end());
176 allTimes.insert(recoveryPaymentTimes.begin(), recoveryPaymentTimes.end());
177
178 if (!allTimes.empty()) {
179 TimeGrid grid(
180 allTimes.begin(), allTimes.end(),
181 std::max<Size>(1, static_cast<Size>(0.5 + static_cast<Real>(timeStepsPerYear_) * (*allTimes.rbegin()))));
182
183 Real rr =
184 arguments_.fixedRecoveryRate != Null<Real>() ? arguments_.fixedRecoveryRate : marketRecovery_->value();
185
186 for (Size i = 0; i < grid.size() - 1; ++i) {
187 Real t0 = grid[i];
188 Real t1 = grid[i + 1];
189
190 auto defaultPayTime = defaultPaymentTimes.lower_bound(t1);
191 auto recoveryPayTime = recoveryPaymentTimes.lower_bound(t1);
192 Size index_d = std::distance(defaultPaymentTimes.begin(), defaultPayTime);
193 Size index_r = std::distance(recoveryPaymentTimes.begin(), recoveryPayTime);
194
195 Real P = creditCurve_->survivalProbability(t0) - creditCurve_->survivalProbability(t1);
196
197 Real dscDefault = 0.0, dscRecovery = 0.0;
198 Date payDateDefault, payDateRecovery;
199 if (arguments_.defaultPaymentTime == QuantExt::CreditDefaultSwap::ProtectionPaymentTime::atDefault) {
200 dscDefault = dscRecovery = irCurve_->discount(0.5 * (t0 + t1));
201 // interpolate a date for display in the cashflow report, since we can not determine it exactly
202 if (defaultPayTime != defaultPaymentTimes.end()) {
203 Date start = today;
204 Real ts = 0;
205 if (index_d > 0) {
206 start = defaultPeriodEndDates[index_d - 1];
207 ts = *(std::next(defaultPayTime, -1));
208 }
209 Date end = defaultPeriodEndDates[index_d];
210 payDateDefault =
211 start + static_cast<Size>(0.5 + static_cast<Real>(end.serialNumber() - start.serialNumber()) *
212 (t1 - ts) / (*defaultPayTime - ts));
213 }
214 if (recoveryPayTime != recoveryPaymentTimes.end()) {
215 Date start = today;
216 Real ts = 0;
217 if (index_r > 0) {
218 start = recoveryPeriodEndDates[index_r - 1];
219 ts = *(std::next(recoveryPayTime, -1));
220 }
221 Date end = recoveryPeriodEndDates[index_r];
222 payDateRecovery =
223 start + static_cast<Size>(0.5 + static_cast<Real>(end.serialNumber() - start.serialNumber()) *
224 (t1 - ts) / (*recoveryPayTime - ts));
225 }
226 } else if (arguments_.defaultPaymentTime ==
227 QuantExt::CreditDefaultSwap::ProtectionPaymentTime::atPeriodEnd) {
228 if (defaultPayTime != defaultPaymentTimes.end()) {
229 dscDefault = irCurve_->discount(*defaultPayTime);
230 payDateDefault = defaultPeriodEndDates[index_d];
231 }
232 if (recoveryPayTime != recoveryPaymentTimes.end()) {
233 dscRecovery = irCurve_->discount(*recoveryPayTime);
234 payDateRecovery = recoveryPeriodEndDates[index_r];
235 }
236 } else if (arguments_.defaultPaymentTime ==
237 QuantExt::CreditDefaultSwap::ProtectionPaymentTime::atMaturity) {
238 dscDefault = dscRecovery = irCurve_->discount(arguments_.maturityDate);
239 payDateDefault = payDateRecovery = arguments_.maturityDate;
240 } else {
241 QL_FAIL("DiscountingCreditLinkedSwapEngine: internal error: unhandled default payment time");
242 }
243
244 npv_default_payments += defaultPaymentAmounts[index_d] * P * (1.0 - rr) * dscDefault;
245 npv_recovery_payments += recoveryPaymentAmounts[index_r] * P * rr * dscRecovery;
246
248 if (!close_enough(defaultPaymentAmounts[index_d], 0.0)) {
249 cfResults.push_back(CashFlowResults());
250 cfResults.back().amount = defaultPaymentAmounts[index_d] * P * (1.0 - rr);
251 cfResults.back().payDate = payDateDefault;
252 cfResults.back().currency = arguments_.currency.code();
253 cfResults.back().legNumber = 3;
254 cfResults.back().type = "DefaultPayment";
255 cfResults.back().discountFactor = dscDefault;
256 cfResults.back().presentValue = cfResults.back().discountFactor * cfResults.back().amount;
257 }
258 if (!close_enough(recoveryPaymentAmounts[index_r], 0.0)) {
259 cfResults.push_back(CashFlowResults());
260 cfResults.back().amount = recoveryPaymentAmounts[index_r] * P * rr;
261 cfResults.back().payDate = payDateRecovery;
262 cfResults.back().currency = arguments_.currency.code();
263 cfResults.back().legNumber = 3;
264 cfResults.back().type = "RecoveryPayment";
265 cfResults.back().discountFactor = dscRecovery;
266 cfResults.back().presentValue = cfResults.back().discountFactor * cfResults.back().amount;
267 }
268 }
269 }
270 }
271
272 // set results
273
274 results_.value =
275 npv_independent + npv_contingent + default_accrual_contingent + npv_default_payments + npv_recovery_payments;
276
278 results_.additionalResults["npv_independent"] = npv_independent;
279 results_.additionalResults["npv_credit_linked"] = npv_contingent;
280 results_.additionalResults["npv_credit_linked_accruals"] = default_accrual_contingent;
281 results_.additionalResults["npv_default_payments"] = npv_default_payments;
282 results_.additionalResults["npv_recovery_payments"] = npv_recovery_payments;
283 results_.additionalResults["cashFlowResults"] = cfResults;
284 }
285
286} // calculate()
287
288} // namespace QuantExt
class holding cashflow-related results
const Instrument::results * results_
Definition: cdsoption.cpp:81
Handle< DefaultProbabilityTermStructure > creditCurve_
DiscountingCreditLinkedSwapEngine(const Handle< YieldTermStructure > &irCurve, const Handle< DefaultProbabilityTermStructure > &creditCurve, const Handle< Quote > &marketRecovery, const Size timeStepsPerYear, const bool generateAdditionalResults)
credit linked swap pricing engine
const P2_< E1, E2 > P(const E1 &e1, const E2 &e2)
CashFlowResults standardCashFlowResults(const QuantLib::ext::shared_ptr< CashFlow > &c, const Real multiplier, const std::string &type, const Size legNo, const Currency &currency, const Handle< YieldTermStructure > &discountCurve)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Swap::arguments * arguments_