Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
discountingbondtrsengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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
23#include <ql/cashflows/cashflows.hpp>
24#include <ql/cashflows/floatingratecoupon.hpp>
25#include <ql/cashflows/simplecashflow.hpp>
26#include <ql/event.hpp>
27#include <ql/indexes/interestrateindex.hpp>
28#include <ql/quotes/compositequote.hpp>
29#include <ql/termstructures/credit/flathazardrate.hpp>
30#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
31
32#include <boost/date_time.hpp>
33#include <boost/make_shared.hpp>
34
35namespace QuantExt {
36
37namespace {
38std::string ccyStr(const Currency& c) {
39 if (c.empty())
40 return "NA";
41 else
42 return c.code();
43}
44} // namespace
45
46DiscountingBondTRSEngine::DiscountingBondTRSEngine(const Handle<YieldTermStructure>& discountCurve)
47 : discountCurve_(discountCurve) {
48 registerWith(discountCurve_);
49}
50
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 =
62 arguments_.bondIndex->defaultCurve().empty()
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 // 1 initialise additional result vectors
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 // 2 do some checks on data
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 // 3 handle funding leg(s) (leg #2, 3, ...)
86
87 Real fundingLeg = 0.0;
88 Size fundingLegNo = 2;
89 for (auto const& l : arguments_.fundingLeg) {
90 fundingLeg += CashFlows::npv(l, **discountCurve_, false);
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 // 4 handle total return leg (leg #0)
117
118 Real returnLeg = CashFlows::npv(arguments_.returnLeg, **discountCurve_, false);
119
120 for (auto const& c : arguments_.returnLeg) {
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 // 5 handle bond cashflows (leg #1)
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 // 5a skip bond cashflows that are outside the total return valuation schedule
161
162 if (bd->cashflows()[i]->date() <= start || bd->cashflows()[i]->date() > end)
163 continue;
164
165 // 5b determine bond cf pay date
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 // 5c skip cashflows that are paid <= today
198
199 if (bondFlowPayDate <= today)
200 continue;
201
202 hasLiveCashFlow = true;
203
204 // 5d determine survivial prob S and fx conversion rate for bond cashflow
205
206 Probability S = bondDefaultCurve->survivalProbability(bondFlowPayDate);
207 // FIXME which fixing date should we use for the fx conversion
208 Date fxFixingDate = bondFlowValuationDate;
209 if (arguments_.fxIndex)
210 fxFixingDate = arguments_.fxIndex->fixingCalendar().adjust(fxFixingDate, Preceding);
211 Real fx = arguments_.fxIndex ? arguments_.fxIndex->fixing(fxFixingDate) : 1.0;
212
213 // 5e set bond cashflow and additional results
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 // 5f bond cashflow npv contribution
243
244 // Real spreadFactor = exp(- bondSpread->value() * discountCurve_->timeFromReference(bondFlowPayDate));
245 bondPayments +=
246 bd->cashflows()[i]->amount() * S * discountCurve_->discount(bondFlowPayDate) * fx; // * spreadFactor;
247
248 // 5g bond cashflow recovery contribution
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 // FIXME which fixing date should we use for the fx conversion?
260 Real fx = arguments_.fxIndex ? arguments_.fxIndex->fixing(arguments_.fxIndex->fixingCalendar().adjust(
261 coupon->date(), Preceding))
262 : 1.0;
263 bondRecovery += coupon->nominal() * recoveryVal * P * discountCurve_->discount(defaultDate) * fx;
264 }
265 ++numCoupons;
266 }
267
268 } // loop over bond cashflows
269
270 // 5h multiply bond payments and recovery contributions by bond notional
271 bondPayments *= arguments_.bondNotional;
272 bondRecovery *= arguments_.bondNotional;
273
274 // 5i bond recovery value (special treatment for zero bonds)
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 /* If there are no coupon, as in a Zero Bond, we must integrate over the entire period from npv date to
281 maturity. The timestepPeriod specified is used as provide the steps for the integration. This only
282 applies to bonds with 1 cashflow, identified as a final redemption payment. */
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; // hardcoded period
289 Date endDate = (stepDate > redemption->date()) ? redemption->date() : stepDate;
290 Date defaultDate = startDate + (endDate - startDate) / 2;
291 Probability P = bondDefaultCurve->defaultProbability(startDate, endDate);
292 // FIXME which fixing date should we use for the fx conversion?
293 Real fx = arguments_.fxIndex
294 ? arguments_.fxIndex->fixing(
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 // 6 set results
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;
331 results_.additionalResults["bondCurrency"] = ccyStr(arguments_.bondCurrency);
332 results_.additionalResults["returnCurrency"] = ccyStr(arguments_.fundingCurrency);
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}
339
340} // namespace QuantExt
cashflow paying the total return of a bond
class holding cashflow-related results
const Instrument::results * results_
Definition: cdsoption.cpp:81
const Handle< YieldTermStructure > discountCurve_
DiscountingBondTRSEngine(const Handle< YieldTermStructure > &discountCurve)
Engine to value a Bond TRS.
const P2_< E1, E2 > P(const E1 &e1, const E2 &e2)
Swap::arguments * arguments_