Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
discountingforwardbondengine.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
19#include <ql/cashflows/cashflows.hpp>
20#include <ql/cashflows/coupon.hpp>
21#include <ql/cashflows/simplecashflow.hpp>
22#include <ql/event.hpp>
23#include <ql/pricingengines/bond/bondfunctions.hpp>
24#include <ql/quotes/compositequote.hpp>
25#include <ql/termstructures/credit/flathazardrate.hpp>
26#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
27
30
31#include <boost/date_time.hpp>
32#include <boost/make_shared.hpp>
33
34namespace QuantExt {
35
37 const Handle<YieldTermStructure>& discountCurve, const Handle<YieldTermStructure>& incomeCurve,
38 const Handle<YieldTermStructure>& bondReferenceYieldCurve, const Handle<Quote>& bondSpread,
39 const Handle<DefaultProbabilityTermStructure>& bondDefaultCurve, const Handle<Quote>& bondRecoveryRate,
40 Period timestepPeriod, boost::optional<bool> includeSettlementDateFlows, const Date& settlementDate,
41 const Date& npvDate)
42 : discountCurve_(discountCurve), incomeCurve_(incomeCurve), bondReferenceYieldCurve_(bondReferenceYieldCurve),
43 bondSpread_(bondSpread), bondDefaultCurve_(bondDefaultCurve), bondRecoveryRate_(bondRecoveryRate),
44 timestepPeriod_(timestepPeriod), includeSettlementDateFlows_(includeSettlementDateFlows),
45 settlementDate_(settlementDate), npvDate_(npvDate) {
46
49 : Handle<YieldTermStructure>(
50 QuantLib::ext::make_shared<ZeroSpreadedTermStructure>(bondReferenceYieldCurve, bondSpread_));
51 registerWith(discountCurve_); // curve for discounting of the forward derivative contract. OIS, usually.
52 registerWith(incomeCurve_); // this is a curve for compounding of the bond
53 registerWith(bondReferenceYieldCurve_); // this is the bond reference curve, for discounting, usually RePo
54 registerWith(bondSpread_);
55 registerWith(bondDefaultCurve_);
56 registerWith(bondRecoveryRate_);
57}
58
60 // Do some checks on data
61 QL_REQUIRE(!discountCurve_.empty(), "discounting term structure handle is empty");
62 QL_REQUIRE(!incomeCurve_.empty(), "income term structure handle is empty");
63 QL_REQUIRE(!bondReferenceYieldCurve_.empty(), "bond reference term structure handle is empty");
64
65 Date npvDate = npvDate_; // this is today when the valuation occurs
66 if (npvDate == Null<Date>()) {
67 npvDate = (*discountCurve_)->referenceDate();
68 }
69 Date settlementDate = settlementDate_; //
70 if (settlementDate == Null<Date>()) {
71 settlementDate = (*discountCurve_)->referenceDate();
72 }
73
74 Date maturityDate =
75 arguments_.fwdMaturityDate; // this is the date when the forward is executed, i.e. cash and bond change hands
76
77 Real cmpPayment = arguments_.compensationPayment;
78 if (cmpPayment == Null<Real>()) {
79 cmpPayment = 0.0;
80 }
81 Date cmpPaymentDate = arguments_.compensationPaymentDate;
82 if (cmpPaymentDate == Null<Date>()) {
83 cmpPaymentDate = npvDate;
84 }
85
86 // in case that the premium payment has occurred in the past, we set the amount to 0. the date itself is set to the
87 // npvDate to have a valid date for "discounting"
88 Date cmpPaymentDate_use = cmpPaymentDate >= npvDate ? cmpPaymentDate : maturityDate;
89 cmpPayment = cmpPaymentDate >= npvDate ? cmpPayment : 0.0; // premium cashflow is not relevant for npv if in the
90 // past
91
92 // initialize
93 results_.value = 0.0; // this is today's npv of the forward contract
94 results_.underlyingSpotValue = 0.0; // this is today's value of the "restricted bond". Restricted means that only
95 // cashflows after maturity are taken into account.
96 results_.forwardValue = 0.0; // this the value of the forward contract just before maturity
97
98 bool dirty = arguments_.settlementDirty;
99
100 results_.underlyingSpotValue = calculateBondNpv(npvDate, maturityDate); // cashflows before maturity will be ignored
101
102 boost::tie(results_.forwardValue, results_.value) = calculateForwardContractPresentValue(
103 results_.underlyingSpotValue, cmpPayment, npvDate, maturityDate, arguments_.fwdSettlementDate,
104 !arguments_.isPhysicallySettled, cmpPaymentDate_use, dirty);
105}
106
107Real DiscountingForwardBondEngine::calculateBondNpv(Date npvDate, Date computeDate) const {
108 Real npvValue = 0.0;
109 Size numCoupons = 0;
110 bool hasLiveCashFlow = false;
111
112 // handle case where we wish to price simply with benchmark curve and scalar security spread
113 // i.e. credit curve term structure (and recovery) have not been specified
114 // we set the default probability and recovery rate to zero in this instance (issuer credit worthiness already
115 // captured within security spread)
116 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> creditCurvePtr =
117 bondDefaultCurve_.empty()
118 ? QuantLib::ext::make_shared<QuantLib::FlatHazardRate>(npvDate, 0.0, bondReferenceYieldCurve_->dayCounter())
119 : bondDefaultCurve_.currentLink();
120 Rate recoveryVal = bondRecoveryRate_.empty() ? 0.0 : bondRecoveryRate_->value(); // setup default bond recovery rate
121
122 std::vector<Date> bondCashflowPayDates;
123 std::vector<Real> bondCashflows, bondCashflowSurvivalProbabilities, bondCashflowDiscountFactors;
124
125 // load the shared pointer into bd
126 QuantLib::ext::shared_ptr<Bond> bd = arguments_.underlying;
127
128 std::vector<CashFlowResults> cashFlowResults;
129 for (Size i = 0; i < bd->cashflows().size(); i++) {
130 // Recovery amount is computed over the whole time interval (npvDate,maturityOfBond)
131
132 if (bd->cashflows()[i]->hasOccurred(
133 computeDate, includeSettlementDateFlows_)) // Cashflows before computeDate not relevant for npv
134 continue;
135
136 /* The amount recovered in the case of default is the recoveryrate*Notional*Probability of
137 Default; this is added to the NPV value. For coupon bonds the coupon periods are taken
138 as the timesteps for integrating over the probability of default.
139 */
140 QuantLib::ext::shared_ptr<Coupon> coupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[i]);
141
142 if (coupon) {
143 numCoupons++;
144 Date startDate = coupon->accrualStartDate();
145 Date endDate = coupon->accrualEndDate();
146 Date effectiveStartDate = (startDate <= computeDate && computeDate <= endDate) ? computeDate : startDate;
147 Date defaultDate = effectiveStartDate + (endDate - effectiveStartDate) / 2;
148 Probability P = creditCurvePtr->defaultProbability(effectiveStartDate, endDate);
149
150 Real cpnRecovery = coupon->nominal() * recoveryVal * P * bondReferenceYieldCurve_->discount(defaultDate);
151 npvValue += cpnRecovery;
152 if (!close_enough(cpnRecovery, 0.0)) {
153 CashFlowResults recoveryFlow;
154 recoveryFlow.payDate = defaultDate;
155 recoveryFlow.accrualStartDate = effectiveStartDate;
156 recoveryFlow.accrualEndDate = endDate;
157 recoveryFlow.amount = coupon->nominal() * recoveryVal * arguments_.bondNotional;
158 recoveryFlow.discountFactor = P * bondReferenceYieldCurve_->discount(defaultDate);
159 recoveryFlow.presentValue = recoveryFlow.amount * recoveryFlow.discountFactor;
160 recoveryFlow.legNumber = 0;
161 recoveryFlow.type = "Bond_ExpectedRecovery";
162 cashFlowResults.push_back(recoveryFlow);
163 }
164 }
165
166 hasLiveCashFlow = true; // check if a cashflow is available after the date of valuation.
167
168 // Coupon value is discounted future payment times the survival probability
169 Probability S = creditCurvePtr->survivalProbability(bd->cashflows()[i]->date()) /
170 creditCurvePtr->survivalProbability(computeDate);
171 npvValue += bd->cashflows()[i]->amount() * S * bondReferenceYieldCurve_->discount(bd->cashflows()[i]->date());
172
173 bondCashflows.push_back(bd->cashflows()[i]->amount());
174 bondCashflowPayDates.push_back(bd->cashflows()[i]->date());
175 bondCashflowSurvivalProbabilities.push_back(S);
176 bondCashflowDiscountFactors.push_back(bondReferenceYieldCurve_->discount(bd->cashflows()[i]->date()));
177 CashFlowResults cfResult = populateCashFlowResultsFromCashflow(bd->cashflows()[i], arguments_.bondNotional);
178 cfResult.type = "Bond_" + cfResult.type;
179 cfResult.discountFactor = S * bondReferenceYieldCurve_->discount(bd->cashflows()[i]->date());
180 cfResult.presentValue = cfResult.amount * cfResult.discountFactor;
181 cashFlowResults.push_back(cfResult);
182 }
183
184 // the ql instrument might not yet be expired and still have not anything to value if
185 // the computeDate > evaluation date
186 if (!hasLiveCashFlow)
187 return 0.0;
188
189 if (bd->cashflows().size() > 1 && numCoupons == 0) {
190 QL_FAIL("DiscountingForwardBondEngine does not support bonds with multiple cashflows but no coupons");
191 }
192
193 Real bondRecovery = 0;
194 QuantLib::ext::shared_ptr<Coupon> firstCoupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[0]);
195 if (firstCoupon) {
196 Date startDate = computeDate; // face value recovery starting with computeDate
197 while (startDate < bd->cashflows()[0]->date()) {
198 Date stepDate = startDate + timestepPeriod_;
199 Date endDate = (stepDate > bd->cashflows()[0]->date()) ? bd->cashflows()[0]->date() : stepDate;
200 Date defaultDate = startDate + (endDate - startDate) / 2;
201 Probability P = creditCurvePtr->defaultProbability(startDate, endDate);
202
203 bondRecovery += firstCoupon->nominal() * recoveryVal * P * bondReferenceYieldCurve_->discount(defaultDate);
204 startDate = stepDate;
205 }
206 if (!close_enough(bondRecovery, 0.0)) {
207 CashFlowResults recoveryFlow;
208 recoveryFlow.payDate = bd->cashflows()[0]->date();
209 recoveryFlow.accrualStartDate = computeDate;
210 recoveryFlow.accrualEndDate = bd->cashflows()[0]->date();
211 recoveryFlow.amount = firstCoupon->nominal() * recoveryVal * arguments_.bondNotional;
212 recoveryFlow.discountFactor = bondRecovery * arguments_.bondNotional / recoveryFlow.amount;
213 recoveryFlow.presentValue = bondRecovery * arguments_.bondNotional;
214 recoveryFlow.legNumber = 0;
215 recoveryFlow.type = "Bond_ExpectedRecovery";
216 cashFlowResults.push_back(recoveryFlow);
217 }
218 }
219
220 /* If there are no coupon, as in a Zero Bond, we must integrate over the entire period from npv date to
221 maturity. The timestepPeriod specified is used as provide the steps for the integration. This only applies
222 to bonds with 1 cashflow, identified as a final redemption payment.
223 */
224 if (bd->cashflows().size() == 1) {
225 QuantLib::ext::shared_ptr<Redemption> redemption = QuantLib::ext::dynamic_pointer_cast<Redemption>(bd->cashflows()[0]);
226 Real redemptionRecovery = 0;
227 if (redemption) {
228 Date startDate = computeDate;
229 while (startDate < redemption->date()) {
230 Date stepDate = startDate + timestepPeriod_;
231 Date endDate = (stepDate > redemption->date()) ? redemption->date() : stepDate;
232 Date defaultDate = startDate + (endDate - startDate) / 2;
233 Probability P = creditCurvePtr->defaultProbability(startDate, endDate);
234
235 redemptionRecovery +=
236 redemption->amount() * recoveryVal * P * bondReferenceYieldCurve_->discount(defaultDate);
237 startDate = stepDate;
238 }
239 bondRecovery += redemptionRecovery;
240 if (!close_enough(redemptionRecovery, 0.0)) {
241 CashFlowResults recoveryFlow;
242 recoveryFlow.payDate = bd->cashflows()[0]->date();
243 recoveryFlow.accrualStartDate = computeDate;
244 recoveryFlow.accrualEndDate = bd->cashflows()[0]->date();
245 recoveryFlow.amount = redemption->amount() * recoveryVal * arguments_.bondNotional;
246 recoveryFlow.discountFactor = redemptionRecovery * arguments_.bondNotional / recoveryFlow.amount;
247 recoveryFlow.presentValue = redemptionRecovery * arguments_.bondNotional;
248 recoveryFlow.legNumber = 0;
249 recoveryFlow.type = "Bond_ExpectedRecovery";
250 cashFlowResults.push_back(recoveryFlow);
251 }
252 }
253 }
254
255 npvValue += bondRecovery;
256
257 // Add cashflowResults
258 if (results_.additionalResults.find("cashFlowResults") != results_.additionalResults.end()) {
259 auto tmp = results_.additionalResults["cashFlowResults"];
260 QL_REQUIRE(tmp.type() == typeid(std::vector<CashFlowResults>), "internal error: cashflowResults type not handlded");
261 std::vector<CashFlowResults> prevCfResults = boost::any_cast<std::vector<CashFlowResults>>(tmp);
262 prevCfResults.insert(prevCfResults.end(), cashFlowResults.begin(), cashFlowResults.end());
263 results_.additionalResults["cashFlowResults"] = prevCfResults;
264 } else {
265 results_.additionalResults["cashFlowResults"] = cashFlowResults;
266 }
267
268 results_.additionalResults["bondCashflow"] = bondCashflows;
269 results_.additionalResults["bondCashflowPayDates"] = bondCashflowPayDates;
270 results_.additionalResults["bondCashflowSurvivalProbabilities"] = bondCashflowSurvivalProbabilities;
271 results_.additionalResults["bondCashflowDiscountFactors"] = bondCashflowDiscountFactors;
272 results_.additionalResults["bondRecovery"] = bondRecovery;
273
274 return npvValue * arguments_.bondNotional;
275}
276
278 Real spotValue, Real cmpPayment, Date npvDate, Date computeDate, Date settlementDate, bool cashSettlement,
279 Date cmpPaymentDate, bool dirty) const {
280
281 // here we go with the true forward computation
282 Real forwardBondValue = 0.0;
283 Real forwardContractPresentValue = 0.0;
284 Real forwardContractForwardValue = 0.0;
285
286 std::vector<Date> fwdBondCashflowPayDates;
287 std::vector<Real> fwdBondCashflows, fwdBondCashflowSurvivalProbabilities, fwdBondCashflowDiscountFactors;
288
289 // handle case where we wish to price simply with benchmark curve and scalar security spread
290 // i.e. credit curve term structure (and recovery) have not been specified
291 // we set the default probability and recovery rate to zero in this instance (issuer credit worthiness already
292 // captured within security spread)
293 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> creditCurvePtr =
294 bondDefaultCurve_.empty()
295 ? QuantLib::ext::make_shared<QuantLib::FlatHazardRate>(npvDate, 0.0, bondReferenceYieldCurve_->dayCounter())
296 : bondDefaultCurve_.currentLink();
297 Rate recoveryVal = bondRecoveryRate_.empty() ? 0.0 : bondRecoveryRate_->value(); // setup default bond recovery rate
298 // load the shared pointer into bd
299 QuantLib::ext::shared_ptr<Bond> bd = arguments_.underlying;
300
301 // the case of dirty strike corresponds here to an accrual of 0.0. This will be convenient in the code.
302 Date bondSettlementDate = bd->settlementDate(computeDate);
303 Real accruedAmount = dirty ? 0.0
304 : bd->accruedAmount(bondSettlementDate) * bd->notional(bondSettlementDate) / 100.0 *
305 arguments_.bondNotional;
306
307 /* Discounting and compounding, taking account of possible bond default before delivery*/
308
309 if (cashSettlement) {
310 forwardBondValue = spotValue / (incomeCurve_->discount(bondSettlementDate));
311 results_.additionalResults["incomeCompoundingDate"] = bondSettlementDate;
312 } else {
313 forwardBondValue = spotValue / (incomeCurve_->discount(settlementDate));
314 results_.additionalResults["incomeCompoundingDate"] = settlementDate;
315 }
316
317 results_.additionalResults["spotForwardBondValue"] = spotValue;
318 results_.additionalResults["forwardForwardBondValue"] = forwardBondValue;
319 results_.additionalResults["incomeCompounding"] = 1.0 / incomeCurve_->discount(bondSettlementDate);
320
321 results_.additionalResults["bondSettlementDate"] = bondSettlementDate;
322 results_.additionalResults["forwardSettlementDate"] = settlementDate;
323
324 results_.additionalResults["bondNotionalSettlementDate"] =
325 bd->notional(bondSettlementDate) * arguments_.bondNotional;
326 results_.additionalResults["accruedAmount"] = accruedAmount;
327
328 // Subtract strike at maturity. Regarding accrual (i.e. strike is given clean vs dirty) there are two
329 // cases: long or short.
330
331 // Long: forwardBondValue - strike_dirt = (forwardBondValue - accrual) - strike_clean
332 // Short: strike_dirt - forwardBondValue = strike_clean - (forwardBondValue - accrual)
333 // In total:
334 QuantLib::ext::shared_ptr<Payoff> effectivePayoff;
335 if (arguments_.payoff) {
336 // vanilla forward bond calculation
337 forwardContractForwardValue = (*arguments_.payoff)(forwardBondValue - accruedAmount);
338 effectivePayoff = arguments_.payoff;
339 } else if (arguments_.lockRate != Null<Real>()) {
340 // lock rate specified forward bond calculation, use hardcoded conventions (compounded / semi annual) here, from
341 // treasury bonds
342 Real price = forwardBondValue / arguments_.bondNotional / bd->notional(bondSettlementDate) * 100.0;
343 Real yield = BondFunctions::yield(*bd, price, arguments_.lockRateDayCounter, Compounded, Semiannual,
344 bondSettlementDate, 1E-10, 100, 0.05, Bond::Price::Dirty);
345 Real dv01, modDur = Null<Real>();
346 if (arguments_.dv01 != Null<Real>()) {
347 dv01 = arguments_.dv01;
348 } else {
349 modDur = BondFunctions::duration(*bd, yield, arguments_.lockRateDayCounter, Compounded, Semiannual,
350 Duration::Modified, bondSettlementDate);
351 dv01 = price / 100.0 * modDur;
352 }
353
354 QL_REQUIRE(arguments_.longInForward, "DiscountingForwardBondEngine: internal error, longInForward must be "
355 "populated if payoff is specified via lock-rate");
356 Real multiplier = (*arguments_.longInForward) ? 1.0 : -1.0;
357 forwardContractForwardValue = multiplier * (yield - arguments_.lockRate) * dv01 * arguments_.bondNotional *
358 bd->notional(bondSettlementDate);
359
360 effectivePayoff = QuantLib::ext::make_shared<ForwardBondTypePayoff>(
361 (*arguments_.longInForward) ? Position::Long : Position::Short,
362 arguments_.lockRate * dv01 * arguments_.bondNotional * bd->notional(bondSettlementDate));
363
364 results_.additionalResults["dv01"] = dv01;
365 results_.additionalResults["modifiedDuration"] = modDur;
366 results_.additionalResults["yield"] = yield;
367 results_.additionalResults["price"] = price;
368 results_.additionalResults["lockRate"] = arguments_.lockRate;
369 } else {
370 QL_FAIL("DiscountingForwardBondEngine: internal error, no payoff and no lock rate given, expected exactly one "
371 "of them to be populated.");
372 }
373
374 // forwardContractPresentValue adjusted for potential default before computeDate:
375 forwardContractPresentValue =
376 forwardContractForwardValue * (discountCurve_->discount(settlementDate)) *
377 creditCurvePtr->survivalProbability(computeDate) -
378 cmpPayment *
379 (discountCurve_->discount(cmpPaymentDate)); // The forward is a derivative. We use "OIS curve" to discount.
380 // We subtract the potential payment due to Premium.
381
382 results_.additionalResults["forwardContractForwardValue"] = forwardContractForwardValue;
383 results_.additionalResults["forwardContractDiscountFactor"] = discountCurve_->discount(settlementDate);
384 results_.additionalResults["forwardContractSurvivalProbability"] = creditCurvePtr->survivalProbability(computeDate);
385 results_.additionalResults["compensationPayment"] = cmpPayment;
386 results_.additionalResults["compensationPaymentDate"] = cmpPaymentDate;
387 results_.additionalResults["compensationPaymentDiscount"] = discountCurve_->discount(cmpPaymentDate);
388
389 fwdBondCashflows.push_back(forwardContractForwardValue);
390 fwdBondCashflowPayDates.push_back(computeDate);
391 fwdBondCashflowSurvivalProbabilities.push_back(creditCurvePtr->survivalProbability(computeDate));
392 fwdBondCashflowDiscountFactors.push_back(discountCurve_->discount(computeDate));
393
394 fwdBondCashflows.push_back(-1 * cmpPayment);
395 fwdBondCashflowPayDates.push_back(cmpPaymentDate);
396 fwdBondCashflowSurvivalProbabilities.push_back(1);
397 fwdBondCashflowDiscountFactors.push_back(discountCurve_->discount(cmpPaymentDate));
398
399 // Write Cashflow Results
400 std::vector<CashFlowResults> forwardCashFlowResults;
401 forwardCashFlowResults.reserve(2);
402
403 // Forward Leg
404 CashFlowResults fwdCfResult;
405 fwdCfResult.payDate = settlementDate;
406 fwdCfResult.legNumber = 1;
407 fwdCfResult.amount = forwardContractForwardValue;
408 fwdCfResult.discountFactor =
409 discountCurve_->discount(settlementDate) * creditCurvePtr->survivalProbability(computeDate);
410 fwdCfResult.presentValue = fwdCfResult.amount * fwdCfResult.discountFactor;
411 fwdCfResult.type = "ForwardValue";
412 forwardCashFlowResults.push_back(fwdCfResult);
413
414 if (!close_enough(cmpPayment, 0.0)) {
415 CashFlowResults cmpCfResult;
416 cmpCfResult.payDate = cmpPaymentDate;
417 cmpCfResult.legNumber = 2;
418 cmpCfResult.amount = -1 * cmpPayment;
419 cmpCfResult.discountFactor = discountCurve_->discount(cmpPaymentDate);
420 cmpCfResult.presentValue = cmpCfResult.amount * cmpCfResult.discountFactor;
421 cmpCfResult.type = "Premium";
422 forwardCashFlowResults.push_back(cmpCfResult);
423 }
424
425 Real fwdBondRecovery = 0;
426 // Take account of face value recovery:
427 // A) Recovery for time period when coupons are present
428 for (Size i = 0; i < bd->cashflows().size(); i++) {
429 if (bd->cashflows()[i]->hasOccurred(
430 npvDate, includeSettlementDateFlows_)) // Cashflows before npvDate not relevant for npv
431 continue;
432 if (bd->cashflows()[i]->date() >=
433 computeDate) // Cashflows after computeDate do not fall into the forward period
434 continue;
435 QuantLib::ext::shared_ptr<Coupon> coupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[i]);
436
437 if (coupon) {
438 Date startDate = coupon->accrualStartDate();
439 Date endDate = coupon->accrualEndDate();
440 Date effectiveStartDate = (startDate <= npvDate && npvDate <= endDate) ? npvDate : startDate;
441 Date effectiveEndDate = (startDate <= computeDate && computeDate <= endDate) ? computeDate : endDate;
442 Date defaultDate = effectiveStartDate + (effectiveEndDate - effectiveStartDate) / 2;
443 Probability P = creditCurvePtr->defaultProbability(effectiveStartDate, effectiveEndDate);
444
445 Real couponRecovery =
446 (*effectivePayoff)(coupon->nominal() * arguments_.bondNotional * recoveryVal - accruedAmount) * P *
447 (discountCurve_->discount(defaultDate));
448 fwdBondRecovery += couponRecovery;
449 if (!close_enough(couponRecovery, 0.0)) {
450 CashFlowResults recoveryFlow;
451 recoveryFlow.payDate = defaultDate;
452 recoveryFlow.accrualStartDate = effectiveStartDate;
453 recoveryFlow.accrualEndDate = endDate;
454 recoveryFlow.amount =
455 (*effectivePayoff)(coupon->nominal() * arguments_.bondNotional * recoveryVal - accruedAmount);
456 recoveryFlow.discountFactor = P * (discountCurve_->discount(defaultDate));
457 recoveryFlow.presentValue = recoveryFlow.amount * recoveryFlow.discountFactor;
458 recoveryFlow.legNumber = 3;
459 recoveryFlow.type = "Forward_ExpectedRecovery";
460 forwardCashFlowResults.push_back(recoveryFlow);
461 }
462 }
463 }
464
465 // B) Recovery for time period before coupons are present
466 QuantLib::ext::shared_ptr<Coupon> firstCoupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[0]);
467 if (firstCoupon) {
468 Date startDate = npvDate; // face value recovery starting with npvDate
469 Date stopDate = std::min(bd->cashflows()[0]->date(), computeDate);
470 Real recoveryBeforeCoupons = 0.0;
471 while (startDate < stopDate) {
472 Date stepDate = startDate + timestepPeriod_;
473 Date endDate = (stepDate > stopDate) ? stopDate : stepDate;
474 Date defaultDate = startDate + (endDate - startDate) / 2;
475 Probability P = creditCurvePtr->defaultProbability(startDate, endDate);
476
477 recoveryBeforeCoupons +=
478 (*effectivePayoff)(firstCoupon->nominal() * arguments_.bondNotional * recoveryVal - accruedAmount) * P *
479 (discountCurve_->discount(defaultDate));
480 startDate = stepDate;
481 }
482 fwdBondRecovery += recoveryBeforeCoupons;
483 if (!close_enough(recoveryBeforeCoupons, 0.0)) {
484 CashFlowResults recoveryFlow;
485 recoveryFlow.payDate = stopDate;
486 recoveryFlow.accrualStartDate = startDate;
487 recoveryFlow.accrualEndDate = stopDate;
488 recoveryFlow.amount =
489 (*effectivePayoff)(firstCoupon->nominal() * arguments_.bondNotional * recoveryVal - accruedAmount);
490 recoveryFlow.discountFactor = recoveryBeforeCoupons / recoveryFlow.amount;
491 recoveryFlow.presentValue = recoveryBeforeCoupons;
492 recoveryFlow.legNumber = 4;
493 recoveryFlow.type = "Forward_ExpectedRecovery";
494 forwardCashFlowResults.push_back(recoveryFlow);
495 }
496 }
497 // C) ZCB
498 /* If there are no coupon, as in a Zero Bond, we must integrate over the entire period from npv date to
499 maturity. The timestepPeriod specified is used as provide the steps for the integration. This only applies
500 to bonds with 1 cashflow, identified as a final redemption payment.
501 */
502 if (bd->cashflows().size() == 1) {
503 QuantLib::ext::shared_ptr<Redemption> redemption = QuantLib::ext::dynamic_pointer_cast<Redemption>(bd->cashflows()[0]);
504 if (redemption) {
505 Real redemptionRecovery = 0;
506 Date startDate = npvDate;
507 while (startDate < redemption->date()) {
508 Date stepDate = startDate + timestepPeriod_;
509 Date endDate = (stepDate > redemption->date()) ? redemption->date() : stepDate;
510 Date defaultDate = startDate + (endDate - startDate) / 2;
511 Probability P = creditCurvePtr->defaultProbability(startDate, endDate);
512 redemptionRecovery +=
513 (*effectivePayoff)(redemption->amount() * arguments_.bondNotional * recoveryVal - accruedAmount) *
514 P * (discountCurve_->discount(defaultDate));
515 startDate = stepDate;
516 }
517 fwdBondRecovery += redemptionRecovery;
518 if (!close_enough(redemptionRecovery, 0.0)) {
519 CashFlowResults recoveryFlow;
520 recoveryFlow.payDate = redemption->date();
521 recoveryFlow.accrualStartDate = startDate;
522 recoveryFlow.accrualEndDate = redemption->date();
523 recoveryFlow.amount =
524 (*effectivePayoff)(redemption->amount() * arguments_.bondNotional * recoveryVal - accruedAmount);
525 recoveryFlow.discountFactor = redemptionRecovery / recoveryFlow.amount;
526 recoveryFlow.presentValue = redemptionRecovery;
527 recoveryFlow.legNumber = 5;
528 recoveryFlow.type = "Forward_ExpectedRecovery";
529 forwardCashFlowResults.push_back(recoveryFlow);
530 }
531 }
532 }
533
534 results_.additionalResults["forwardBondRecovery"] = fwdBondRecovery;
535
536 // Add cashflowResults
537 if (results_.additionalResults.find("cashFlowResults") != results_.additionalResults.end()) {
538 auto tmp = results_.additionalResults["cashFlowResults"];
539 QL_REQUIRE(tmp.type() == typeid(std::vector<CashFlowResults>), "internal error: cashflowResults type not handlded");
540 std::vector<CashFlowResults> prevCfResults = boost::any_cast<std::vector<CashFlowResults>>(tmp);
541 prevCfResults.insert(prevCfResults.end(), forwardCashFlowResults.begin(), forwardCashFlowResults.end());
542 results_.additionalResults["cashFlowResults"] = prevCfResults;
543 } else {
544 results_.additionalResults["cashFlowResults"] = forwardCashFlowResults;
545 }
546
547 forwardContractPresentValue += fwdBondRecovery;
548
549 return QuantLib::ext::make_tuple(forwardContractForwardValue, forwardContractPresentValue);
550}
551
552} // namespace QuantExt
class holding cashflow-related results
const Instrument::results * results_
Definition: cdsoption.cpp:81
DiscountingForwardBondEngine(const Handle< YieldTermStructure > &discountCurve, const Handle< YieldTermStructure > &incomeCurve, const Handle< YieldTermStructure > &bondReferenceYieldCurve, const Handle< Quote > &bondSpread, const Handle< DefaultProbabilityTermStructure > &defaultCurve, const Handle< Quote > &recoveryRate, Period timestepPeriod, boost::optional< bool > includeSettlementDateFlows=boost::none, const Date &settlementDate=Date(), const Date &npvDate=Date())
Handle< DefaultProbabilityTermStructure > bondDefaultCurve_
const Handle< YieldTermStructure > & bondReferenceYieldCurve() const
QuantLib::ext::tuple< Real, Real > calculateForwardContractPresentValue(Real spotValue, Real cmpPayment, Date npvDate, Date computeDate, Date settlementDate, bool cashSettlement, Date cmpPaymentDate, bool dirty) const
Engine to value a Forward Bond contract.
const P2_< E1, E2 > P(const E1 &e1, const E2 &e2)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
CashFlowResults populateCashFlowResultsFromCashflow(const QuantLib::ext::shared_ptr< QuantLib::CashFlow > &c, const QuantLib::Real multiplier, const QuantLib::Size legNo, const QuantLib::Currency &currency)
Swap::arguments * arguments_