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>
32#include <boost/date_time.hpp>
33#include <boost/make_shared.hpp>
38std::string ccyStr(
const Currency& c) {
47 : discountCurve_(discountCurve) {
53 Date today = Settings::instance().evaluationDate();
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();
66 arguments_.bondIndex->recoveryRate().empty() ? 0.0 :
arguments_.bondIndex->recoveryRate()->value();
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;
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");
83 Real mult =
arguments_.payTotalReturnLeg ? -1.0 : 1.0;
87 Real fundingLeg = 0.0;
88 Size fundingLegNo = 2;
91 for (
auto const& c : l) {
92 if (c->hasOccurred(today))
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();
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());
121 if (c->hasOccurred(today))
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());
147 QuantLib::ext::shared_ptr<Bond> bd =
arguments_.bondIndex->bond();
149 Date start = bd->settlementDate(
arguments_.valuationDates.front());
150 Date end = bd->settlementDate(
arguments_.valuationDates.back());
152 Real bondPayments = 0.0, bondRecovery = 0.0;
153 bool hasLiveCashFlow =
false;
158 for (Size i = 0; i < bd->cashflows().size(); i++) {
162 if (bd->cashflows()[i]->date() <= start || bd->cashflows()[i]->date() > end)
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();
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;
182 const auto& valDates =
arguments_.valuationDates;
183 auto nextValDate = std::upper_bound(valDates.begin(), valDates.end(), bondFlowPayDate);
185 if (nextValDate == valDates.begin()) {
186 nextValDate = valDates.end();
191 QL_REQUIRE(nextValDate != valDates.end(),
"DiscountingBondTRSEngine::calculate(): unexpected, could "
192 "not determine next valuation date for bond cashflow date "
194 bondFlowValuationDate = *nextValDate;
199 if (bondFlowPayDate <= today)
202 hasLiveCashFlow =
true;
206 Probability S = bondDefaultCurve->survivalProbability(bondFlowPayDate);
208 Date fxFixingDate = bondFlowValuationDate;
210 fxFixingDate =
arguments_.fxIndex->fixingCalendar().adjust(fxFixingDate, Preceding);
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();
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());
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);
246 bd->cashflows()[i]->amount() * S *
discountCurve_->discount(bondFlowPayDate) * fx;
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;
261 coupon->date(), Preceding))
263 bondRecovery += coupon->nominal() * recoveryVal *
P *
discountCurve_->discount(defaultDate) * fx;
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");
283 if (bd->cashflows().size() == 1) {
284 QuantLib::ext::shared_ptr<Redemption> redemption = QuantLib::ext::dynamic_pointer_cast<Redemption>(bd->cashflows()[0]);
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);
295 arguments_.fxIndex->fixingCalendar().adjust(redemption->date(), Preceding))
297 bondRecovery += redemption->amount() * recoveryVal *
P *
discountCurve_->discount(defaultDate) * fx;
298 startDate = stepDate;
306 results_.value = mult * (returnLeg + bondPayments + bondRecovery - fundingLeg);
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;
314 results_.additionalResults[
"cashFlowResults"] = cfResults;
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;
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;
330 results_.additionalResults[
"bondNotional"] = returnBondNotionals;
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;
cashflow paying the total return of a bond
class holding cashflow-related results
const Instrument::results * results_
const Handle< YieldTermStructure > discountCurve_
void calculate() const override
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_