QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
isdacdsengine.cpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2014 Jose Aparicio
5 Copyright (C) 2014 Peter Caspers
6
7 This file is part of QuantLib, a free-software/open-source library
8 for financial quantitative analysts and developers - http://quantlib.org/
9
10 QuantLib is free software: you can redistribute it and/or modify it
11 under the terms of the QuantLib license. You should have received a
12 copy of the license along with this program; if not, please email
13 <quantlib-dev@lists.sf.net>. The license is also available online at
14 <http://quantlib.org/license.shtml>.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21#include <ql/cashflows/fixedratecoupon.hpp>
22#include <ql/instruments/claim.hpp>
23#include <ql/math/interpolations/forwardflatinterpolation.hpp>
24#include <ql/pricingengines/credit/isdacdsengine.hpp>
25#include <ql/termstructures/credit/flathazardrate.hpp>
26#include <ql/termstructures/credit/piecewisedefaultcurve.hpp>
27#include <ql/termstructures/yield/flatforward.hpp>
28#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
29#include <ql/time/calendars/weekendsonly.hpp>
30#include <ql/time/daycounters/actual360.hpp>
31#include <ql/optional.hpp>
32#include <utility>
33
34namespace QuantLib {
35
37 Real recoveryRate,
38 Handle<YieldTermStructure> discountCurve,
39 const ext::optional<bool>& includeSettlementDateFlows,
40 const NumericalFix numericalFix,
41 const AccrualBias accrualBias,
42 const ForwardsInCouponPeriod forwardsInCouponPeriod)
43 : probability_(std::move(probability)), recoveryRate_(recoveryRate),
44 discountCurve_(std::move(discountCurve)),
45 includeSettlementDateFlows_(includeSettlementDateFlows), numericalFix_(numericalFix),
46 accrualBias_(accrualBias), forwardsInCouponPeriod_(forwardsInCouponPeriod) {
47
50 }
51
53
54 QL_REQUIRE(numericalFix_ == None || numericalFix_ == Taylor,
55 "numerical fix must be None or Taylor");
56 QL_REQUIRE(accrualBias_ == HalfDayBias || accrualBias_ == NoBias,
57 "accrual bias must be HalfDayBias or NoBias");
58 QL_REQUIRE(forwardsInCouponPeriod_ == Flat ||
60 "forwards in coupon period must be Flat or Piecewise");
61
62 // it would be possible to handle the cases which are excluded below,
63 // but the ISDA engine is not explicitly specified to handle them,
64 // so we just forbid them too
65
67 Actual360 dc1;
68 Actual360 dc2(true);
69
71
72 // check if given curves are ISDA compatible
73 // (the interpolation is checked below)
74
75 QL_REQUIRE(!discountCurve_.empty(), "no discount term structure set");
76 QL_REQUIRE(!probability_.empty(), "no probability term structure set");
77 QL_REQUIRE(discountCurve_->dayCounter() == dc,
78 "yield term structure day counter ("
79 << discountCurve_->dayCounter()
80 << ") should be Act/365(Fixed)");
81 QL_REQUIRE(probability_->dayCounter() == dc,
82 "probability term structure day counter ("
83 << probability_->dayCounter() << ") should be "
84 << "Act/365(Fixed)");
85 QL_REQUIRE(discountCurve_->referenceDate() == evalDate,
86 "yield term structure reference date ("
87 << discountCurve_->referenceDate()
88 << " should be evaluation date (" << evalDate << ")");
89 QL_REQUIRE(probability_->referenceDate() == evalDate,
90 "probability term structure reference date ("
91 << probability_->referenceDate()
92 << " should be evaluation date (" << evalDate << ")");
93 QL_REQUIRE(arguments_.settlesAccrual,
94 "ISDA engine not compatible with non accrual paying CDS");
96 "ISDA engine not compatible with end period payment");
97 QL_REQUIRE(ext::dynamic_pointer_cast<FaceValueClaim>(arguments_.claim) != nullptr,
98 "ISDA engine not compatible with non face value claim");
99
100 Date maturity = arguments_.maturity;
101 Date effectiveProtectionStart =
102 std::max<Date>(arguments_.protectionStart, evalDate + 1);
103
104 // collect nodes from both curves and sort them
105 std::vector<Date> yDates, cDates;
106
107 // the calls to dates() below might not trigger bootstrap (because
108 // they will call the InterpolatedCurve methods, not the ones from
109 // PiecewiseYieldCurve or PiecewiseDefaultCurve) so we force it here
110 discountCurve_->discount(0.0);
111 probability_->defaultProbability(0.0);
112
113 if(ext::shared_ptr<InterpolatedDiscountCurve<LogLinear> > castY1 =
114 ext::dynamic_pointer_cast<
116 yDates = castY1->dates();
117 } else if(ext::shared_ptr<InterpolatedForwardCurve<BackwardFlat> >
118 castY2 = ext::dynamic_pointer_cast<
120 yDates = castY2->dates();
121 } else if(ext::shared_ptr<InterpolatedForwardCurve<ForwardFlat> >
122 castY3 = ext::dynamic_pointer_cast<
124 yDates = castY3->dates();
125 } else if(ext::shared_ptr<FlatForward> castY4 =
126 ext::dynamic_pointer_cast<FlatForward>(*discountCurve_)) {
127 // no dates to extract
128 } else {
129 QL_FAIL("Yield curve must be flat forward interpolated");
130 }
131
133 castC1 = ext::dynamic_pointer_cast<
135 *probability_)) {
136 cDates = castC1->dates();
137 } else if(
138 ext::shared_ptr<InterpolatedHazardRateCurve<BackwardFlat> > castC2 =
139 ext::dynamic_pointer_cast<
141 cDates = castC2->dates();
142 } else if(
143 ext::shared_ptr<FlatHazardRate> castC3 =
144 ext::dynamic_pointer_cast<FlatHazardRate>(*probability_)) {
145 // no dates to extract
146 } else{
147 QL_FAIL("Credit curve must be flat forward interpolated");
148 }
149
150 std::vector<Date> nodes;
151 std::set_union(yDates.begin(), yDates.end(), cDates.begin(), cDates.end(), std::back_inserter(nodes));
152
153
154 if(nodes.empty()){
155 nodes.push_back(maturity);
156 }
157 const Real nFix = (numericalFix_ == None ? 1E-50 : 0.0);
158
159 // protection leg pricing (npv is always negative at this stage)
160 Real protectionNpv = 0.0;
161
162 Date d0 = effectiveProtectionStart-1;
163 Real P0 = discountCurve_->discount(d0);
164 Real Q0 = probability_->survivalProbability(d0);
165 Date d1;
166 std::vector<Date>::const_iterator it =
167 std::upper_bound(nodes.begin(), nodes.end(), effectiveProtectionStart);
168
169 for(;it != nodes.end(); ++it) {
170 if(*it > maturity) {
171 d1 = maturity;
172 it = nodes.end() - 1; //early exit
173 } else {
174 d1 = *it;
175 }
176 Real P1 = discountCurve_->discount(d1);
177 Real Q1 = probability_->survivalProbability(d1);
178
179 Real fhat = std::log(P0) - std::log(P1);
180 Real hhat = std::log(Q0) - std::log(Q1);
181 Real fhphh = fhat + hhat;
182
183 if (fhphh < 1E-4 && numericalFix_ == Taylor) {
184 Real fhphhq = fhphh * fhphh;
185 protectionNpv +=
186 P0 * Q0 * hhat * (1.0 - 0.5 * fhphh + 1.0 / 6.0 * fhphhq -
187 1.0 / 24.0 * fhphhq * fhphh +
188 1.0 / 120 * fhphhq * fhphhq);
189 } else {
190 protectionNpv += hhat / (fhphh + nFix) * (P0 * Q0 - P1 * Q1);
191 }
192 d0 = d1;
193 P0 = P1;
194 Q0 = Q1;
195 }
196 protectionNpv *= arguments_.claim->amount(
198
199 results_.defaultLegNPV = protectionNpv;
200
201 // premium leg pricing (npv is always positive at this stage)
202
203 Real premiumNpv = 0.0, defaultAccrualNpv = 0.0;
204 for (auto& i : arguments_.leg) {
205 ext::shared_ptr<FixedRateCoupon> coupon = ext::dynamic_pointer_cast<FixedRateCoupon>(i);
206
207 QL_REQUIRE(coupon->dayCounter() == dc ||
208 coupon->dayCounter() == dc1 ||
209 coupon->dayCounter() == dc2,
210 "ISDA engine requires a coupon day counter Act/365Fixed "
211 << "or Act/360 (" << coupon->dayCounter() << ")");
212
213 // premium coupons
214 if (!i->hasOccurred(effectiveProtectionStart, includeSettlementDateFlows_)) {
215 premiumNpv +=
216 coupon->amount() *
217 discountCurve_->discount(coupon->date()) *
218 probability_->survivalProbability(coupon->date()-1);
219 }
220
221 // default accruals
222
223 if (!detail::simple_event(coupon->accrualEndDate())
224 .hasOccurred(effectiveProtectionStart, false)) {
225 Date start = std::max<Date>(coupon->accrualStartDate(),
226 effectiveProtectionStart)-1;
227 Date end = coupon->date()-1;
228 Real tstart =
229 discountCurve_->timeFromReference(coupon->accrualStartDate()-1) -
230 (accrualBias_ == HalfDayBias ? 1.0 / 730.0 : 0.0);
231 std::vector<Date> localNodes;
232 localNodes.push_back(start);
233 //add intermediary nodes, if any
235 std::vector<Date>::const_iterator it0 =
236 std::upper_bound(nodes.begin(), nodes.end(), start);
237 std::vector<Date>::const_iterator it1 =
238 std::lower_bound(nodes.begin(), nodes.end(), end);
239 localNodes.insert(localNodes.end(), it0, it1);
240 }
241 localNodes.push_back(end);
242
243 Real defaultAccrThisNode = 0.;
244 std::vector<Date>::const_iterator node = localNodes.begin();
245 Real t0 = discountCurve_->timeFromReference(*node);
246 Real P0 = discountCurve_->discount(*node);
247 Real Q0 = probability_->survivalProbability(*node);
248
249 for (++node; node != localNodes.end(); ++node) {
250 Real t1 = discountCurve_->timeFromReference(*node);
251 Real P1 = discountCurve_->discount(*node);
252 Real Q1 = probability_->survivalProbability(*node);
253 Real fhat = std::log(P0) - std::log(P1);
254 Real hhat = std::log(Q0) - std::log(Q1);
255 Real fhphh = fhat + hhat;
256 if (fhphh < 1E-4 && numericalFix_ == Taylor) {
257 // see above, terms up to (f+h)^3 seem more than enough,
258 // what exactly is implemented in the standard isda C
259 // code ?
260 Real fhphhq = fhphh * fhphh;
261 defaultAccrThisNode +=
262 hhat * P0 * Q0 *
263 ((t0 - tstart) *
264 (1.0 - 0.5 * fhphh + 1.0 / 6.0 * fhphhq -
265 1.0 / 24.0 * fhphhq * fhphh) +
266 (t1 - t0) *
267 (0.5 - 1.0 / 3.0 * fhphh + 1.0 / 8.0 * fhphhq -
268 1.0 / 30.0 * fhphhq * fhphh));
269 } else {
270 defaultAccrThisNode +=
271 (hhat / (fhphh + nFix)) *
272 ((t1 - t0) * ((P0 * Q0 - P1 * Q1) / (fhphh + nFix) -
273 P1 * Q1) +
274 (t0 - tstart) * (P0 * Q0 - P1 * Q1));
275 }
276
277 t0 = t1;
278 P0 = P1;
279 Q0 = Q1;
280 }
281 defaultAccrualNpv += defaultAccrThisNode * arguments_.notional *
282 coupon->rate() * 365. / 360.;
283 }
284 }
285
286
287 results_.couponLegNPV = premiumNpv + defaultAccrualNpv;
288
289 // upfront flow npv
290
291 Real upfPVO1 = 0.0;
292 results_.upfrontNPV = 0.0;
293 if (!arguments_.upfrontPayment->hasOccurred(
294 evalDate, includeSettlementDateFlows_)) {
295 upfPVO1 =
296 discountCurve_->discount(arguments_.upfrontPayment->date());
297 if(arguments_.upfrontPayment->amount() != 0.) {
298 results_.upfrontNPV = upfPVO1 * arguments_.upfrontPayment->amount();
299 }
300 }
301
303 // NOLINTNEXTLINE(readability-implicit-bool-conversion)
304 if (arguments_.accrualRebate && arguments_.accrualRebate->amount() != 0. &&
305 !arguments_.accrualRebate->hasOccurred(evalDate, includeSettlementDateFlows_)) {
307 discountCurve_->discount(arguments_.accrualRebate->date()) *
308 arguments_.accrualRebate->amount();
309 }
310
311 Real upfrontSign = 1.0;
312 switch (arguments_.side) {
314 results_.defaultLegNPV *= -1.0;
316 break;
318 results_.couponLegNPV *= -1.0;
319 results_.upfrontNPV *= -1.0;
320 upfrontSign = -1.0;
321 break;
322 default:
323 QL_FAIL("unknown protection side");
324 }
325
328
330
331 if (results_.couponLegNPV != 0.0) {
335 } else {
337 }
338
339 Real upfrontSensitivity = upfPVO1 * arguments_.notional;
340 if (upfrontSensitivity != 0.0) {
342 -upfrontSign * (results_.defaultLegNPV + results_.couponLegNPV +
344 upfrontSensitivity;
345 } else {
347 }
348
349 static const Rate basisPoint = 1.0e-4;
350
351 if (arguments_.spread != 0.0) {
353 results_.couponLegNPV * basisPoint / arguments_.spread;
354 } else {
356 }
357
358 // NOLINTNEXTLINE(readability-implicit-bool-conversion)
359 if (arguments_.upfront && *arguments_.upfront != 0.0) {
361 results_.upfrontNPV * basisPoint / (*arguments_.upfront);
362 } else {
364 }
365 }
366}
Actual/360 day count convention.
Definition: actual360.hpp:37
Actual/365 (Fixed) day count convention.
ext::shared_ptr< SimpleCashFlow > accrualRebate
ext::shared_ptr< SimpleCashFlow > upfrontPayment
Concrete date class.
Definition: date.hpp:125
virtual bool hasOccurred(const Date &refDate=Date(), ext::optional< bool > includeRefDate=ext::nullopt) const
returns true if an event has already occurred before a date
Definition: event.cpp:28
Shared handle to an observable.
Definition: handle.hpp:41
YieldTermStructure based on interpolation of discount factors.
YieldTermStructure based on interpolation of forward rates.
DefaultProbabilityTermStructure based on interpolation of hazard rates.
DefaultProbabilityTermStructure based on interpolation of survival probabilities.
const ext::optional< bool > includeSettlementDateFlows_
Handle< YieldTermStructure > discountCurve_
IsdaCdsEngine(Handle< DefaultProbabilityTermStructure > probability, Real recoveryRate, Handle< YieldTermStructure > discountCurve, const ext::optional< bool > &includeSettlementDateFlows=ext::nullopt, NumericalFix numericalFix=Taylor, AccrualBias accrualBias=HalfDayBias, ForwardsInCouponPeriod forwardsInCouponPeriod=Piecewise)
Handle< DefaultProbabilityTermStructure > probability_
void calculate() const override
const ForwardsInCouponPeriod forwardsInCouponPeriod_
const NumericalFix numericalFix_
const AccrualBias accrualBias_
template class providing a null value for a given type.
Definition: null.hpp:76
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:228
DateProxy & evaluationDate()
the date at which pricing is to be performed.
Definition: settings.hpp:147
static Settings & instance()
access to the unique instance
Definition: singleton.hpp:104
QL_REAL Real
real number
Definition: types.hpp:50
Real Rate
interest rates
Definition: types.hpp:70
Definition: any.hpp:35
STL namespace.