Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
discountingriskybondengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 Quaternion Risk Management Ltd
3 Copyright (C) 2017 Aareal Bank AG
4 All rights reserved.
5
6 This file is part of ORE, a free-software/open-source library
7 for transparent pricing and risk analysis - http://opensourcerisk.org
8
9 ORE is free software: you can redistribute it and/or modify it
10 under the terms of the Modified BSD License. You should have received a
11 copy of the license along with this program.
12 The license is also available online at <http://opensourcerisk.org>
13
14 This program is distributed on the basis that it will form a useful
15 contribution to risk analytics and model standardisation, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include <boost/date_time.hpp>
21#include <boost/make_shared.hpp>
22#include <ql/cashflows/cashflows.hpp>
23#include <ql/cashflows/coupon.hpp>
24#include <ql/cashflows/simplecashflow.hpp>
25#include <ql/termstructures/credit/flathazardrate.hpp>
26#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
29
30using namespace std;
31using namespace QuantLib;
32
33namespace QuantExt {
34
35DiscountingRiskyBondEngine::DiscountingRiskyBondEngine(const Handle<YieldTermStructure>& discountCurve,
36 const Handle<DefaultProbabilityTermStructure>& defaultCurve,
37 const Handle<Quote>& recoveryRate,
38 const Handle<Quote>& securitySpread, Period timestepPeriod,
39 boost::optional<bool> includeSettlementDateFlows)
40 : defaultCurve_(defaultCurve), recoveryRate_(recoveryRate), securitySpread_(securitySpread),
41 timestepPeriod_(timestepPeriod), includeSettlementDateFlows_(includeSettlementDateFlows) {
43 securitySpread_.empty()
45 : Handle<YieldTermStructure>(QuantLib::ext::make_shared<ZeroSpreadedTermStructure>(discountCurve, securitySpread));
46 registerWith(discountCurve_);
47 registerWith(defaultCurve_);
48 registerWith(recoveryRate_);
49 registerWith(securitySpread_);
50}
51
52DiscountingRiskyBondEngine::DiscountingRiskyBondEngine(const Handle<YieldTermStructure>& discountCurve,
53 const Handle<Quote>& securitySpread, Period timestepPeriod,
54 boost::optional<bool> includeSettlementDateFlows)
55 : securitySpread_(securitySpread), timestepPeriod_(timestepPeriod),
56 includeSettlementDateFlows_(includeSettlementDateFlows) {
58 securitySpread_.empty()
60 : Handle<YieldTermStructure>(QuantLib::ext::make_shared<ZeroSpreadedTermStructure>(discountCurve, securitySpread));
61 registerWith(discountCurve_);
62 registerWith(securitySpread_);
63}
64
66 QL_REQUIRE(!discountCurve_.empty(), "discounting term structure handle is empty");
67 results_.valuationDate = (*discountCurve_)->referenceDate();
68
69 // the npv as of today, excluding cashflows before the settlement date
70
72 results_.valuationDate, arguments_.settlementDate, arguments_.cashflows, includeSettlementDateFlows_);
73
74 // the results value is set to the npv as of today including the cashflows before settlement
75
76 results_.value = npvResults.npv + npvResults.cashflowsBeforeSettlementValue;
77
78 // the settlement value is excluding cashflows before the settlement date and compounded to the settlement date
79
80 results_.settlementValue = npvResults.npv * npvResults.compoundFactorSettlement;
81
82 // set a few more additional results
83 results_.additionalResults["cashFlowResults"] = npvResults.cashflowResults;
84 results_.additionalResults["securitySpread"] = securitySpread_.empty() ? 0.0 : securitySpread_->value();
85 Date maturity = CashFlows::maturityDate(arguments_.cashflows);
86 if (maturity > results_.valuationDate) {
87 Real t = discountCurve_->timeFromReference(maturity);
88 results_.additionalResults["maturityTime"] = t;
89 results_.additionalResults["maturityDiscountFactor"] = discountCurve_->discount(t);
90 results_.additionalResults["maturitySurvivalProb"] =
91 defaultCurve_.empty() ? 1.0 : defaultCurve_->survivalProbability(t);
92 results_.additionalResults["recoveryRate"] = recoveryRate_.empty() ? 0.0 : recoveryRate_->value();
93 }
94}
95
97DiscountingRiskyBondEngine::calculateNpv(const Date& npvDate, const Date& settlementDate, const Leg& cashflows,
98 boost::optional<bool> includeSettlementDateFlows,
99 const Handle<YieldTermStructure>& incomeCurve,
100 const bool conditionalOnSurvival, const bool additionalResults) const {
101
102 bool includeRefDateFlows =
103 includeSettlementDateFlows ? *includeSettlementDateFlows_ : Settings::instance().includeReferenceDateEvents();
104
105 Real npvValue = 0.0;
107 calculationResults.cashflowsBeforeSettlementValue = 0.0;
108
109 // handle case where we wish to price simply with benchmark curve and scalar security spread
110 // i.e. credit curve term structure (and recovery) have not been specified
111 // we set the default probability and recovery rate to zero in this instance (issuer credit worthiness already
112 // captured within security spread)
113 QuantLib::ext::shared_ptr<DefaultProbabilityTermStructure> creditCurvePtr =
114 defaultCurve_.empty() ? QuantLib::ext::make_shared<QuantLib::FlatHazardRate>(npvDate, 0.0, discountCurve_->dayCounter())
115 : defaultCurve_.currentLink();
116 Rate recoveryVal = recoveryRate_.empty() ? 0.0 : recoveryRate_->value();
117
118 // compounding factors for npv date
119 Real dfNpv = incomeCurve.empty() ? discountCurve_->discount(npvDate) : incomeCurve->discount(npvDate);
120 Real spNpv = conditionalOnSurvival ? creditCurvePtr->survivalProbability(npvDate) : 1.0;
121
122 // compound factors for settlement date
123 Real dfSettl =
124 incomeCurve.empty() ? discountCurve_->discount(settlementDate) : incomeCurve->discount(settlementDate);
125 Real spSettl = creditCurvePtr->survivalProbability(settlementDate);
126 if (!conditionalOnSurvival)
127 spSettl /= creditCurvePtr->survivalProbability(npvDate);
128
129 // effective compound factor to get settlement npv from npv date npv
130 calculationResults.compoundFactorSettlement = (dfNpv * spNpv) / (dfSettl * spSettl);
131
132 Size numCoupons = 0;
133 bool hasLiveCashFlow = false;
134 for (Size i = 0; i < cashflows.size(); i++) {
135 QuantLib::ext::shared_ptr<CashFlow> cf = cashflows[i];
136 if (cf->hasOccurred(npvDate, includeRefDateFlows))
137 continue;
138 hasLiveCashFlow = true;
139
140 DiscountFactor df = discountCurve_->discount(cf->date()) / dfNpv;
141 // Coupon value is discounted future payment times the survival probability
142 Probability S = creditCurvePtr->survivalProbability(cf->date()) / spNpv;
143 Real tmp = cf->amount() * S * df;
144 if (!cf->hasOccurred(settlementDate, includeRefDateFlows))
145 npvValue += tmp;
146 else
147 calculationResults.cashflowsBeforeSettlementValue += tmp;
148
149 /* The amount recovered in the case of default is the recoveryrate*Notional*Probability of
150 Default; this is added to the NPV value. For coupon bonds the coupon periods are taken
151 as the timesteps for integrating over the probability of default.
152 */
153 if (additionalResults) {
155 cfRes.discountFactor = S * df;
156 cfRes.presentValue = cfRes.amount * cfRes.discountFactor;
157 calculationResults.cashflowResults.push_back(cfRes);
158 }
159
160 QuantLib::ext::shared_ptr<Coupon> coupon = QuantLib::ext::dynamic_pointer_cast<Coupon>(cf);
161 if (coupon) {
162 numCoupons++;
163 Date startDate = coupon->accrualStartDate();
164 Date endDate = coupon->accrualEndDate();
165 Date effectiveStartDate = (startDate <= npvDate && npvDate <= endDate) ? npvDate : startDate;
166 Date defaultDate = effectiveStartDate + (endDate - effectiveStartDate) / 2;
167 Probability P = creditCurvePtr->defaultProbability(effectiveStartDate, endDate) / spNpv;
168 Real expectedRecoveryAmount = coupon->nominal() * recoveryVal;
169 DiscountFactor recoveryDiscountFactor = discountCurve_->discount(defaultDate) / dfNpv;
170 if (additionalResults && !close_enough(expectedRecoveryAmount * P * recoveryDiscountFactor, 0.0)) {
171 // Add a new flow for the expected recovery conditional on the default during
172 CashFlowResults recoveryResult;
173 recoveryResult.amount = expectedRecoveryAmount;
174 recoveryResult.payDate = defaultDate;
175 recoveryResult.currency = "";
176 recoveryResult.discountFactor = P * recoveryDiscountFactor;
177 recoveryResult.presentValue = recoveryResult.discountFactor * recoveryResult.amount;
178 recoveryResult.type = "ExpectedRecovery";
179 calculationResults.cashflowResults.push_back(recoveryResult);
180 }
181 npvValue += expectedRecoveryAmount * P * recoveryDiscountFactor;
182 }
183 }
184
185 // the ql instrument might not yet be expired and still have not anything to value if
186 // the npvDate > evaluation date
187 if (!hasLiveCashFlow) {
188 calculationResults.npv = 0.0;
189 return calculationResults;
190 }
191
192 if (cashflows.size() > 1 && numCoupons == 0) {
193 QL_FAIL("DiscountingRiskyBondEngine does not support bonds with multiple cashflows but no coupons");
194 }
195
196 /* If there are no coupon, as in a Zero Bond, we must integrate over the entire period from npv date to
197 maturity. The timestepPeriod specified is used as provide the steps for the integration. This only applies
198 to bonds with 1 cashflow, identified as a final redemption payment.
199 */
200 if (cashflows.size() == 1) {
201 QuantLib::ext::shared_ptr<Redemption> redemption = QuantLib::ext::dynamic_pointer_cast<Redemption>(cashflows[0]);
202 if (redemption) {
203 Date startDate = npvDate;
204 while (startDate < redemption->date()) {
205 Date stepDate = startDate + timestepPeriod_;
206 Date endDate = (stepDate > redemption->date()) ? redemption->date() : stepDate;
207 Date defaultDate = startDate + (endDate - startDate) / 2;
208 Probability P = creditCurvePtr->defaultProbability(startDate, endDate) / spNpv;
209 if (additionalResults) {
210 CashFlowResults recoveryResult;
211 recoveryResult.amount = redemption->amount() * recoveryVal;
212 recoveryResult.payDate = defaultDate;
213 recoveryResult.currency = "";
214 recoveryResult.discountFactor = P * discountCurve_->discount(defaultDate) / dfNpv;
215 recoveryResult.presentValue = recoveryResult.discountFactor * recoveryResult.amount;
216 recoveryResult.type = "ExpectedRecovery";
217 calculationResults.cashflowResults.push_back(recoveryResult);
218 }
219 npvValue += redemption->amount() * recoveryVal * P * discountCurve_->discount(defaultDate) / dfNpv;
220 startDate = stepDate;
221 }
222 }
223 }
224 calculationResults.npv = npvValue;
225 return calculationResults;
226} // namespace QuantExt
227} // namespace QuantExt
class holding cashflow-related results
const Instrument::results * results_
Definition: cdsoption.cpp:81
Handle< YieldTermStructure > discountCurve() const
DiscountingRiskyBondEngine(const Handle< YieldTermStructure > &discountCurve, const Handle< DefaultProbabilityTermStructure > &defaultCurve, const Handle< Quote > &recoveryRate, const Handle< Quote > &securitySpread, Period timestepPeriod, boost::optional< bool > includeSettlementDateFlows=boost::none)
BondNPVCalculationResults calculateNpv(const Date &npvDate, const Date &settlementDate, const Leg &cashflows, boost::optional< bool > includeSettlementDateFlows=boost::none, const Handle< YieldTermStructure > &incomeCurve=Handle< YieldTermStructure >(), const bool conditionalOnSurvival=true, const bool additionalResults=true) const
Handle< DefaultProbabilityTermStructure > defaultCurve_
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_