51 {
52
53 Date today = Settings::instance().evaluationDate();
54
55 Handle<Quote> bondSpread =
arguments_.bondIndex->securitySpread();
56 Handle<Quote> bondRecoveryRate =
arguments_.bondIndex->recoveryRate();
57 Handle<YieldTermStructure> bondReferenceYieldCurve =
58 bondSpread.empty() ?
arguments_.bondIndex->discountCurve()
59 : Handle<YieldTermStructure>(QuantLib::ext::make_shared<ZeroSpreadedTermStructure>(
60 arguments_.bondIndex->discountCurve(), bondSpread));
61 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> bondDefaultCurve =
63 ? QuantLib::ext::make_shared<QuantLib::FlatHazardRate>(today, 0.0, Actual365Fixed())
64 :
arguments_.bondIndex->defaultCurve().currentLink();
65 Rate recoveryVal =
66 arguments_.bondIndex->recoveryRate().empty() ? 0.0 :
arguments_.bondIndex->recoveryRate()->value();
67
68
69
70 std::vector<QuantExt::CashFlowResults> cfResults;
71 std::vector<Date> returnStartDates, returnEndDates;
72 std::vector<Real> returnFxStarts, returnFxEnds, returnBondStarts, returnBondEnds, returnBondNotionals;
73 std::vector<Date> bondCashflowOriginalPayDates, bondCashflowReturnPayDates, bondCashflowFxFixingDate;
74 std::vector<Real> bondCashflows, bondCashflowFxRate, bondCashflowSurvivalProbability;
75
76
77
78 QL_REQUIRE(!
discountCurve_.empty(),
"discounting term structure handle is empty");
79 QL_REQUIRE(!
arguments_.bondIndex->conditionalOnSurvival(),
80 "DiscountingBondTRSEngine::calculate(): bondIndex should not be computed with conditionalOnSurvival = "
81 "true in this engine");
82
83 Real mult =
arguments_.payTotalReturnLeg ? -1.0 : 1.0;
84
85
86
87 Real fundingLeg = 0.0;
88 Size fundingLegNo = 2;
91 for (auto const& c : l) {
92 if (c->hasOccurred(today))
93 continue;
94 cfResults.emplace_back();
95 cfResults.back().amount = -mult * c->amount();
96 cfResults.back().payDate = c->date();
97 cfResults.back().currency = ccyStr(
arguments_.fundingCurrency);
98 cfResults.back().legNumber = fundingLegNo;
99 cfResults.back().type = "Funding";
100 if (auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(c)) {
101 cfResults.back().rate = cpn->rate();
102 cfResults.back().accrualPeriod = cpn->accrualPeriod();
103 cfResults.back().accrualStartDate = cpn->accrualStartDate();
104 cfResults.back().accrualEndDate = cpn->accrualEndDate();
105 cfResults.back().accruedAmount = cpn->accruedAmount(today);
106 cfResults.back().notional = cpn->nominal();
107 }
108 if (auto cpn = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(c)) {
109 cfResults.back().fixingDate = cpn->fixingDate();
110 cfResults.back().fixingValue = cpn->index()->fixing(cpn->fixingDate());
111 }
112 }
113 ++fundingLegNo;
114 }
115
116
117
119
121 if (c->hasOccurred(today))
122 continue;
123 cfResults.emplace_back();
124 cfResults.back().amount = mult * c->amount();
125 cfResults.back().payDate = c->date();
126 cfResults.back().currency = ccyStr(
arguments_.fundingCurrency);
127 cfResults.back().legNumber = 0;
128 cfResults.back().type = "Return";
129 if (auto bc = QuantLib::ext::dynamic_pointer_cast<BondTRSCashFlow>(c)) {
130 cfResults.back().fixingDate = bc->fixingEndDate();
131 cfResults.back().fixingValue = bc->assetEnd();
132 cfResults.back().accrualStartDate = bc->fixingStartDate();
133 cfResults.back().accrualEndDate = bc->fixingEndDate();
134 cfResults.back().notional = bc->notional();
135 returnStartDates.push_back(bc->fixingStartDate());
136 returnEndDates.push_back(bc->fixingEndDate());
137 returnFxStarts.push_back(bc->fxStart());
138 returnFxEnds.push_back(bc->fxEnd());
139 returnBondStarts.push_back(bc->assetStart());
140 returnBondEnds.push_back(bc->assetEnd());
141 returnBondNotionals.push_back(bc->notional());
142 }
143 }
144
145
146
147 QuantLib::ext::shared_ptr<Bond> bd =
arguments_.bondIndex->bond();
148
149 Date start = bd->settlementDate(
arguments_.valuationDates.front());
150 Date end = bd->settlementDate(
arguments_.valuationDates.back());
151
152 Real bondPayments = 0.0, bondRecovery = 0.0;
153 bool hasLiveCashFlow = false;
154 Size numCoupons = 0;
155
156
157
158 for (Size i = 0; i < bd->cashflows().size(); i++) {
159
160
161
162 if (bd->cashflows()[i]->date() <= start || bd->cashflows()[i]->date() > end)
163 continue;
164
165
166
167 Date bondFlowPayDate;
168 Date bondFlowValuationDate;
169 bool paymentAfterMaturityButWithinBondSettlement =
170 bd->cashflows()[i]->date() >
arguments_.valuationDates.back() && bd->cashflows()[i]->date() <= end;
171 if (
arguments_.payBondCashFlowsImmediately || paymentAfterMaturityButWithinBondSettlement) {
172 bondFlowPayDate = bd->cashflows()[i]->date();
173 bondFlowValuationDate = bd->cashflows()[i]->date();
174 } else {
175 const auto& payDates =
arguments_.paymentDates;
176 auto nextPayDate = std::lower_bound(payDates.begin(), payDates.end(), bd->cashflows()[i]->date());
177 QL_REQUIRE(nextPayDate != payDates.end(), "DiscountingBondTRSEngine::calculate(): unexpected, could "
178 "not determine next pay date for bond cashflow date "
179 << bd->cashflows()[i]);
180 bondFlowPayDate = *nextPayDate;
181
182 const auto& valDates =
arguments_.valuationDates;
183 auto nextValDate = std::upper_bound(valDates.begin(), valDates.end(), bondFlowPayDate);
184
185 if (nextValDate == valDates.begin()) {
186 nextValDate = valDates.end();
187 } else {
188 nextValDate--;
189 }
190
191 QL_REQUIRE(nextValDate != valDates.end(), "DiscountingBondTRSEngine::calculate(): unexpected, could "
192 "not determine next valuation date for bond cashflow date "
193 << bondFlowPayDate);
194 bondFlowValuationDate = *nextValDate;
195 }
196
197
198
199 if (bondFlowPayDate <= today)
200 continue;
201
202 hasLiveCashFlow = true;
203
204
205
206 Probability S = bondDefaultCurve->survivalProbability(bondFlowPayDate);
207
208 Date fxFixingDate = bondFlowValuationDate;
210 fxFixingDate =
arguments_.fxIndex->fixingCalendar().adjust(fxFixingDate, Preceding);
212
213
214
215 cfResults.emplace_back();
216 cfResults.back().amount = mult * bd->cashflows()[i]->amount() * fx *
arguments_.bondNotional;
217 cfResults.back().discountFactor =
discountCurve_->discount(bondFlowPayDate) * S;
218 cfResults.back().payDate = bondFlowPayDate;
219 cfResults.back().currency = ccyStr(
arguments_.fundingCurrency);
220 cfResults.back().legNumber = 1;
221 cfResults.back().type = "BondCashFlowReturn";
222 if (auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[i])) {
223 cfResults.back().rate = cpn->rate();
224 cfResults.back().accrualPeriod = cpn->accrualPeriod();
225 cfResults.back().accrualStartDate = cpn->accrualStartDate();
226 cfResults.back().accrualEndDate = cpn->accrualEndDate();
227 cfResults.back().accruedAmount = cpn->accruedAmount(today);
228 cfResults.back().notional = cpn->nominal();
229 }
230 if (auto cpn = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(bd->cashflows()[i])) {
231 cfResults.back().fixingDate = cpn->fixingDate();
232 cfResults.back().fixingValue = cpn->index()->fixing(cpn->fixingDate());
233 }
234
235 bondCashflows.push_back(mult * bd->cashflows()[i]->amount() *
arguments_.bondNotional);
236 bondCashflowOriginalPayDates.push_back(bd->cashflows()[i]->date());
237 bondCashflowReturnPayDates.push_back(bondFlowPayDate);
238 bondCashflowFxRate.push_back(fx);
239 bondCashflowFxFixingDate.push_back(fxFixingDate);
240 bondCashflowSurvivalProbability.push_back(S);
241
242
243
244
245 bondPayments +=
246 bd->cashflows()[i]->amount() * S *
discountCurve_->discount(bondFlowPayDate) * fx;
247
248
249
250 if (auto coupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[i])) {
251 Date startDate = coupon->accrualStartDate();
252 Date endDate = coupon->accrualEndDate();
253 Date effectiveStartDate = (startDate <= start && start <= endDate) ? start : startDate;
254 if (effectiveStartDate < today)
255 effectiveStartDate = today;
256 if (endDate > effectiveStartDate) {
257 Probability
P = bondDefaultCurve->defaultProbability(effectiveStartDate, endDate);
258 Date defaultDate = effectiveStartDate + (endDate - effectiveStartDate) / 2;
259
261 coupon->date(), Preceding))
262 : 1.0;
263 bondRecovery += coupon->nominal() * recoveryVal *
P *
discountCurve_->discount(defaultDate) * fx;
264 }
265 ++numCoupons;
266 }
267
268 }
269
270
273
274
275
276 if (hasLiveCashFlow) {
277 if (bd->cashflows().size() > 1 && numCoupons == 0) {
278 QL_FAIL("DiscountingBondTRSEngine: no support of bonds with multiple cashflows but no coupons");
279 }
280
281
282
283 if (bd->cashflows().size() == 1) {
284 QuantLib::ext::shared_ptr<Redemption> redemption = QuantLib::ext::dynamic_pointer_cast<Redemption>(bd->cashflows()[0]);
285 if (redemption) {
286 Date startDate = (start < today ? today : start);
287 while (startDate < redemption->date()) {
288 Date stepDate = startDate + 1 * Months;
289 Date endDate = (stepDate > redemption->date()) ? redemption->date() : stepDate;
290 Date defaultDate = startDate + (endDate - startDate) / 2;
291 Probability
P = bondDefaultCurve->defaultProbability(startDate, endDate);
292
295 arguments_.fxIndex->fixingCalendar().adjust(redemption->date(), Preceding))
296 : 1.0;
297 bondRecovery += redemption->amount() * recoveryVal *
P *
discountCurve_->discount(defaultDate) * fx;
298 startDate = stepDate;
299 }
300 }
301 }
302 }
303
304
305
306 results_.value = mult * (returnLeg + bondPayments + bondRecovery - fundingLeg);
307
308 results_.additionalResults[
"returnLegNpv"] = mult * (returnLeg + bondPayments + bondRecovery);
309 results_.additionalResults[
"returnLegNpvReturnPaymentsContribtion"] = mult * returnLeg;
310 results_.additionalResults[
"returnLegNpvBondPaymentsContribtion"] = mult * bondPayments;
311 results_.additionalResults[
"returnLegNpvBondRecoveryContribution"] = mult * bondRecovery;
312 results_.additionalResults[
"fundingLegNpv"] = -mult * fundingLeg;
313
314 results_.additionalResults[
"cashFlowResults"] = cfResults;
315
316 results_.additionalResults[
"returnStartDate"] = returnStartDates;
317 results_.additionalResults[
"returnEndDate"] = returnEndDates;
318 results_.additionalResults[
"returnFxStart"] = returnFxStarts;
319 results_.additionalResults[
"returnFxEnd"] = returnFxEnds;
320 results_.additionalResults[
"returnBondStart"] = returnBondStarts;
321 results_.additionalResults[
"returnBondEnd"] = returnBondEnds;
322
323 results_.additionalResults[
"bondCashflow"] = bondCashflows;
324 results_.additionalResults[
"bondCashflowOriginalPayDate"] = bondCashflowOriginalPayDates;
325 results_.additionalResults[
"bondCashflowReturnPayDate"] = bondCashflowReturnPayDates;
326 results_.additionalResults[
"bondCashflowFxRate"] = bondCashflowFxRate;
327 results_.additionalResults[
"bondCashflowFxFixingDate"] = bondCashflowFxFixingDate;
328 results_.additionalResults[
"bondCashflowSurvivalProbability"] = bondCashflowSurvivalProbability;
329
330 results_.additionalResults[
"bondNotional"] = returnBondNotionals;
333
334 results_.additionalResults[
"bondCleanPrice"] =
arguments_.bondIndex->bond()->cleanPrice();
335 results_.additionalResults[
"bondDirtyPrice"] =
arguments_.bondIndex->bond()->dirtyPrice();
336 results_.additionalResults[
"bondSpread"] = bondSpread->value();
337 results_.additionalResults[
"bondRecovery"] = recoveryVal;
338}
const Instrument::results * results_
const P2_< E1, E2 > P(const E1 &e1, const E2 &e2)
Swap::arguments * arguments_