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>
31#include <boost/date_time.hpp>
32#include <boost/make_shared.hpp>
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,
42 : discountCurve_(discountCurve), incomeCurve_(incomeCurve), bondReferenceYieldCurve_(bondReferenceYieldCurve),
43 bondSpread_(bondSpread), bondDefaultCurve_(bondDefaultCurve), bondRecoveryRate_(bondRecoveryRate),
44 timestepPeriod_(timestepPeriod), includeSettlementDateFlows_(includeSettlementDateFlows),
45 settlementDate_(settlementDate), npvDate_(npvDate) {
49 : Handle<YieldTermStructure>(
61 QL_REQUIRE(!
discountCurve_.empty(),
"discounting term structure handle is empty");
62 QL_REQUIRE(!
incomeCurve_.empty(),
"income term structure handle is empty");
66 if (npvDate == Null<Date>()) {
67 npvDate = (*discountCurve_)->referenceDate();
70 if (settlementDate == Null<Date>()) {
71 settlementDate = (*discountCurve_)->referenceDate();
77 Real cmpPayment =
arguments_.compensationPayment;
78 if (cmpPayment == Null<Real>()) {
81 Date cmpPaymentDate =
arguments_.compensationPaymentDate;
82 if (cmpPaymentDate == Null<Date>()) {
83 cmpPaymentDate = npvDate;
88 Date cmpPaymentDate_use = cmpPaymentDate >= npvDate ? cmpPaymentDate : maturityDate;
89 cmpPayment = cmpPaymentDate >= npvDate ? cmpPayment : 0.0;
103 results_.underlyingSpotValue, cmpPayment, npvDate, maturityDate,
arguments_.fwdSettlementDate,
104 !
arguments_.isPhysicallySettled, cmpPaymentDate_use, dirty);
110 bool hasLiveCashFlow =
false;
116 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> creditCurvePtr =
122 std::vector<Date> bondCashflowPayDates;
123 std::vector<Real> bondCashflows, bondCashflowSurvivalProbabilities, bondCashflowDiscountFactors;
126 QuantLib::ext::shared_ptr<Bond> bd =
arguments_.underlying;
128 std::vector<CashFlowResults> cashFlowResults;
129 for (Size i = 0; i < bd->cashflows().size(); i++) {
132 if (bd->cashflows()[i]->hasOccurred(
140 QuantLib::ext::shared_ptr<Coupon> coupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[i]);
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);
151 npvValue += cpnRecovery;
154 recoveryFlow.
payDate = defaultDate;
157 recoveryFlow.
amount = coupon->nominal() * recoveryVal *
arguments_.bondNotional;
161 recoveryFlow.
type =
"Bond_ExpectedRecovery";
162 cashFlowResults.push_back(recoveryFlow);
166 hasLiveCashFlow =
true;
169 Probability S = creditCurvePtr->survivalProbability(bd->cashflows()[i]->date()) /
170 creditCurvePtr->survivalProbability(computeDate);
173 bondCashflows.push_back(bd->cashflows()[i]->amount());
174 bondCashflowPayDates.push_back(bd->cashflows()[i]->date());
175 bondCashflowSurvivalProbabilities.push_back(S);
178 cfResult.
type =
"Bond_" + cfResult.
type;
181 cashFlowResults.push_back(cfResult);
186 if (!hasLiveCashFlow)
189 if (bd->cashflows().size() > 1 && numCoupons == 0) {
190 QL_FAIL(
"DiscountingForwardBondEngine does not support bonds with multiple cashflows but no coupons");
193 Real bondRecovery = 0;
194 QuantLib::ext::shared_ptr<Coupon> firstCoupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[0]);
196 Date startDate = computeDate;
197 while (startDate < bd->cashflows()[0]->date()) {
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);
204 startDate = stepDate;
208 recoveryFlow.
payDate = bd->cashflows()[0]->date();
211 recoveryFlow.
amount = firstCoupon->nominal() * recoveryVal *
arguments_.bondNotional;
215 recoveryFlow.
type =
"Bond_ExpectedRecovery";
216 cashFlowResults.push_back(recoveryFlow);
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;
228 Date startDate = computeDate;
229 while (startDate < redemption->date()) {
231 Date endDate = (stepDate > redemption->date()) ? redemption->date() : stepDate;
232 Date defaultDate = startDate + (endDate - startDate) / 2;
233 Probability
P = creditCurvePtr->defaultProbability(startDate, endDate);
235 redemptionRecovery +=
237 startDate = stepDate;
239 bondRecovery += redemptionRecovery;
242 recoveryFlow.
payDate = bd->cashflows()[0]->date();
245 recoveryFlow.
amount = redemption->amount() * recoveryVal *
arguments_.bondNotional;
249 recoveryFlow.
type =
"Bond_ExpectedRecovery";
250 cashFlowResults.push_back(recoveryFlow);
255 npvValue += bondRecovery;
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;
265 results_.additionalResults[
"cashFlowResults"] = cashFlowResults;
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;
278 Real spotValue, Real cmpPayment, Date npvDate, Date computeDate, Date settlementDate,
bool cashSettlement,
279 Date cmpPaymentDate,
bool dirty)
const {
282 Real forwardBondValue = 0.0;
283 Real forwardContractPresentValue = 0.0;
284 Real forwardContractForwardValue = 0.0;
286 std::vector<Date> fwdBondCashflowPayDates;
287 std::vector<Real> fwdBondCashflows, fwdBondCashflowSurvivalProbabilities, fwdBondCashflowDiscountFactors;
293 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> creditCurvePtr =
299 QuantLib::ext::shared_ptr<Bond> bd =
arguments_.underlying;
302 Date bondSettlementDate = bd->settlementDate(computeDate);
303 Real accruedAmount = dirty ? 0.0
304 : bd->accruedAmount(bondSettlementDate) * bd->notional(bondSettlementDate) / 100.0 *
309 if (cashSettlement) {
310 forwardBondValue = spotValue / (
incomeCurve_->discount(bondSettlementDate));
311 results_.additionalResults[
"incomeCompoundingDate"] = bondSettlementDate;
313 forwardBondValue = spotValue / (
incomeCurve_->discount(settlementDate));
314 results_.additionalResults[
"incomeCompoundingDate"] = settlementDate;
317 results_.additionalResults[
"spotForwardBondValue"] = spotValue;
318 results_.additionalResults[
"forwardForwardBondValue"] = forwardBondValue;
319 results_.additionalResults[
"incomeCompounding"] = 1.0 /
incomeCurve_->discount(bondSettlementDate);
321 results_.additionalResults[
"bondSettlementDate"] = bondSettlementDate;
322 results_.additionalResults[
"forwardSettlementDate"] = settlementDate;
324 results_.additionalResults[
"bondNotionalSettlementDate"] =
325 bd->notional(bondSettlementDate) *
arguments_.bondNotional;
326 results_.additionalResults[
"accruedAmount"] = accruedAmount;
334 QuantLib::ext::shared_ptr<Payoff> effectivePayoff;
337 forwardContractForwardValue = (*
arguments_.payoff)(forwardBondValue - accruedAmount);
339 }
else if (
arguments_.lockRate != Null<Real>()) {
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>();
349 modDur = BondFunctions::duration(*bd, yield,
arguments_.lockRateDayCounter, Compounded, Semiannual,
350 Duration::Modified, bondSettlementDate);
351 dv01 = price / 100.0 * modDur;
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);
360 effectivePayoff = QuantLib::ext::make_shared<ForwardBondTypePayoff>(
361 (*
arguments_.longInForward) ? Position::Long : Position::Short,
364 results_.additionalResults[
"dv01"] = dv01;
365 results_.additionalResults[
"modifiedDuration"] = modDur;
366 results_.additionalResults[
"yield"] = yield;
367 results_.additionalResults[
"price"] = price;
370 QL_FAIL(
"DiscountingForwardBondEngine: internal error, no payoff and no lock rate given, expected exactly one "
371 "of them to be populated.");
375 forwardContractPresentValue =
376 forwardContractForwardValue * (
discountCurve_->discount(settlementDate)) *
377 creditCurvePtr->survivalProbability(computeDate) -
382 results_.additionalResults[
"forwardContractForwardValue"] = forwardContractForwardValue;
384 results_.additionalResults[
"forwardContractSurvivalProbability"] = creditCurvePtr->survivalProbability(computeDate);
385 results_.additionalResults[
"compensationPayment"] = cmpPayment;
386 results_.additionalResults[
"compensationPaymentDate"] = cmpPaymentDate;
389 fwdBondCashflows.push_back(forwardContractForwardValue);
390 fwdBondCashflowPayDates.push_back(computeDate);
391 fwdBondCashflowSurvivalProbabilities.push_back(creditCurvePtr->survivalProbability(computeDate));
392 fwdBondCashflowDiscountFactors.push_back(
discountCurve_->discount(computeDate));
394 fwdBondCashflows.push_back(-1 * cmpPayment);
395 fwdBondCashflowPayDates.push_back(cmpPaymentDate);
396 fwdBondCashflowSurvivalProbabilities.push_back(1);
397 fwdBondCashflowDiscountFactors.push_back(
discountCurve_->discount(cmpPaymentDate));
400 std::vector<CashFlowResults> forwardCashFlowResults;
401 forwardCashFlowResults.reserve(2);
405 fwdCfResult.
payDate = settlementDate;
407 fwdCfResult.
amount = forwardContractForwardValue;
409 discountCurve_->discount(settlementDate) * creditCurvePtr->survivalProbability(computeDate);
411 fwdCfResult.
type =
"ForwardValue";
412 forwardCashFlowResults.push_back(fwdCfResult);
416 cmpCfResult.
payDate = cmpPaymentDate;
418 cmpCfResult.
amount = -1 * cmpPayment;
421 cmpCfResult.
type =
"Premium";
422 forwardCashFlowResults.push_back(cmpCfResult);
425 Real fwdBondRecovery = 0;
428 for (Size i = 0; i < bd->cashflows().size(); i++) {
429 if (bd->cashflows()[i]->hasOccurred(
432 if (bd->cashflows()[i]->date() >=
435 QuantLib::ext::shared_ptr<Coupon> coupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[i]);
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);
445 Real couponRecovery =
446 (*effectivePayoff)(coupon->nominal() *
arguments_.bondNotional * recoveryVal - accruedAmount) *
P *
448 fwdBondRecovery += couponRecovery;
451 recoveryFlow.
payDate = defaultDate;
455 (*effectivePayoff)(coupon->nominal() *
arguments_.bondNotional * recoveryVal - accruedAmount);
459 recoveryFlow.
type =
"Forward_ExpectedRecovery";
460 forwardCashFlowResults.push_back(recoveryFlow);
466 QuantLib::ext::shared_ptr<Coupon> firstCoupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(bd->cashflows()[0]);
468 Date startDate = npvDate;
469 Date stopDate = std::min(bd->cashflows()[0]->date(), computeDate);
470 Real recoveryBeforeCoupons = 0.0;
471 while (startDate < stopDate) {
473 Date endDate = (stepDate > stopDate) ? stopDate : stepDate;
474 Date defaultDate = startDate + (endDate - startDate) / 2;
475 Probability
P = creditCurvePtr->defaultProbability(startDate, endDate);
477 recoveryBeforeCoupons +=
478 (*effectivePayoff)(firstCoupon->nominal() *
arguments_.bondNotional * recoveryVal - accruedAmount) *
P *
480 startDate = stepDate;
482 fwdBondRecovery += recoveryBeforeCoupons;
485 recoveryFlow.
payDate = stopDate;
489 (*effectivePayoff)(firstCoupon->nominal() *
arguments_.bondNotional * recoveryVal - accruedAmount);
493 recoveryFlow.
type =
"Forward_ExpectedRecovery";
494 forwardCashFlowResults.push_back(recoveryFlow);
502 if (bd->cashflows().size() == 1) {
503 QuantLib::ext::shared_ptr<Redemption> redemption = QuantLib::ext::dynamic_pointer_cast<Redemption>(bd->cashflows()[0]);
505 Real redemptionRecovery = 0;
506 Date startDate = npvDate;
507 while (startDate < redemption->date()) {
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) *
515 startDate = stepDate;
517 fwdBondRecovery += redemptionRecovery;
520 recoveryFlow.
payDate = redemption->date();
524 (*effectivePayoff)(redemption->amount() *
arguments_.bondNotional * recoveryVal - accruedAmount);
528 recoveryFlow.
type =
"Forward_ExpectedRecovery";
529 forwardCashFlowResults.push_back(recoveryFlow);
534 results_.additionalResults[
"forwardBondRecovery"] = fwdBondRecovery;
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;
544 results_.additionalResults[
"cashFlowResults"] = forwardCashFlowResults;
547 forwardContractPresentValue += fwdBondRecovery;
549 return QuantLib::ext::make_tuple(forwardContractForwardValue, forwardContractPresentValue);
class holding cashflow-related results
const Instrument::results * results_
Handle< YieldTermStructure > discountCurve_
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< YieldTermStructure > incomeCurve_
Handle< DefaultProbabilityTermStructure > bondDefaultCurve_
boost::optional< bool > includeSettlementDateFlows_
Handle< Quote > bondSpread_
void calculate() const override
Real calculateBondNpv(Date, Date) const
Handle< YieldTermStructure > bondReferenceYieldCurve_
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
Handle< Quote > bondRecoveryRate_
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 ¤cy)
QuantLib::Real presentValue
QuantLib::Date accrualStartDate
QuantLib::Date accrualEndDate
QuantLib::Real discountFactor
Swap::arguments * arguments_