42 {
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
54
55 for (Size i = 0; i <
arguments_.legs.size(); ++i) {
56
57 Real multiplier = (
arguments_.legPayers[i] ? -1.0 : 1.0);
58
62 if (c->date() <= today)
63 continue;
64 npv_independent += multiplier * c->amount() *
irCurve_->discount(c->date());
66 cfResults.push_back(
68 }
69 }
70 break;
71 }
72
75 if (c->date() <= today)
76 continue;
77 Real dsc =
irCurve_->discount(c->date());
79 npv_contingent += multiplier * c->amount() * dsc *
P;
80
83 cfResults.back().amount *=
P;
84 cfResults.back().discountFactor = dsc;
85 cfResults.back().presentValue = cfResults.back().amount * dsc;
86 }
87
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);
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
123
124 std::set<Real> defaultPaymentTimes, recoveryPaymentTimes, allTimes;
125 for (Size i = 0; i <
arguments_.legs.size(); ++i) {
128 if (c->date() > today) {
129 defaultPaymentTimes.insert(
creditCurve_->timeFromReference(c->date()));
130 }
131 }
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);
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 }
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 =
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
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
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 }
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 }
237 QuantExt::CreditDefaultSwap::ProtectionPaymentTime::atMaturity) {
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
273
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}
const Instrument::results * results_
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 ¤cy, const Handle< YieldTermStructure > &discountCurve)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Swap::arguments * arguments_