1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2008, 2009 Jose Aparicio
5 Copyright (C) 2008 Roland Lichters
6 Copyright (C) 2008 StatPro Italia srl
7
8 This file is part of QuantLib, a free-software/open-source library
9 for financial quantitative analysts and developers - http://quantlib.org/
10
11 QuantLib is free software: you can redistribute it and/or modify it
12 under the terms of the QuantLib license. You should have received a
13 copy of the license along with this program; if not, please email
14 <quantlib-dev@lists.sf.net>. The license is also available online at
15 <http://quantlib.org/license.shtml>.
16
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 FOR A PARTICULAR PURPOSE. See the license for more details.
20*/
21
22#include <ql/cashflows/fixedratecoupon.hpp>
23#include <ql/cashflows/simplecashflow.hpp>
24#include <ql/instruments/claim.hpp>
25#include <ql/instruments/creditdefaultswap.hpp>
26#include <ql/math/solvers1d/brent.hpp>
27#include <ql/pricingengines/credit/isdacdsengine.hpp>
28#include <ql/pricingengines/credit/midpointcdsengine.hpp>
29#include <ql/quotes/simplequote.hpp>
30#include <ql/termstructures/credit/flathazardrate.hpp>
31#include <ql/termstructures/yieldtermstructure.hpp>
32#include <ql/time/calendars/weekendsonly.hpp>
33#include <ql/time/schedule.hpp>
34#include <ql/optional.hpp>
35#include <utility>
36
37namespace QuantLib {
38
39 CreditDefaultSwap::CreditDefaultSwap(Protection::Side side,
40 Real notional,
41 Rate spread,
42 const Schedule& schedule,
43 BusinessDayConvention convention,
44 const DayCounter& dayCounter,
45 bool settlesAccrual,
46 bool paysAtDefaultTime,
47 const Date& protectionStart,
48 ext::shared_ptr<Claim> claim,
49 const DayCounter& lastPeriodDayCounter,
50 const bool rebatesAccrual,
51 const Date& tradeDate,
52 Natural cashSettlementDays)
53 : side_(side), notional_(notional), upfront_(ext::nullopt), runningSpread_(spread),
54 settlesAccrual_(settlesAccrual), paysAtDefaultTime_(paysAtDefaultTime),
55 claim_(std::move(claim)),
56 protectionStart_(protectionStart == Null<Date>() ? schedule[0] : protectionStart),
57 tradeDate_(tradeDate), cashSettlementDays_(cashSettlementDays) {
58
59 init(schedule, paymentConvention: convention, dayCounter, lastPeriodDayCounter, rebatesAccrual);
60 }
61
62 CreditDefaultSwap::CreditDefaultSwap(Protection::Side side,
63 Real notional,
64 Rate upfront,
65 Rate runningSpread,
66 const Schedule& schedule,
67 BusinessDayConvention convention,
68 const DayCounter& dayCounter,
69 bool settlesAccrual,
70 bool paysAtDefaultTime,
71 const Date& protectionStart,
72 const Date& upfrontDate,
73 ext::shared_ptr<Claim> claim,
74 const DayCounter& lastPeriodDayCounter,
75 const bool rebatesAccrual,
76 const Date& tradeDate,
77 Natural cashSettlementDays)
78 : side_(side), notional_(notional), upfront_(upfront), runningSpread_(runningSpread),
79 settlesAccrual_(settlesAccrual), paysAtDefaultTime_(paysAtDefaultTime),
80 claim_(std::move(claim)),
81 protectionStart_(protectionStart == Null<Date>() ? schedule[0] : protectionStart),
82 tradeDate_(tradeDate), cashSettlementDays_(cashSettlementDays) {
83
84 init(schedule, paymentConvention: convention, dayCounter, lastPeriodDayCounter, rebatesAccrual, upfrontDate);
85 }
86
87 void CreditDefaultSwap::init(const Schedule& schedule, BusinessDayConvention paymentConvention,
88 const DayCounter& dayCounter, const DayCounter& lastPeriodDayCounter,
89 bool rebatesAccrual, const Date& upfrontDate) {
90
91 QL_REQUIRE(!schedule.empty(), "CreditDefaultSwap needs a non-empty schedule.");
92
93 bool postBigBang = false;
94 if (schedule.hasRule()) {
95 DateGeneration::Rule rule = schedule.rule();
96 postBigBang = rule == DateGeneration::CDS || rule == DateGeneration::CDS2015;
97 }
98
99 if (!postBigBang) {
100 QL_REQUIRE(protectionStart_ <= schedule[0], "protection can not start after accrual");
101 }
102
103 leg_ = FixedRateLeg(schedule)
104 .withNotionals(notional_)
105 .withCouponRates(runningSpread_, paymentDayCounter: dayCounter)
106 .withPaymentAdjustment(paymentConvention)
107 .withLastPeriodDayCounter(lastPeriodDayCounter);
108
109 // Deduce the trade date if not given.
110 if (tradeDate_ == Date()) {
111 if (postBigBang) {
112 tradeDate_ = protectionStart_;
113 } else {
114 tradeDate_ = protectionStart_ - 1;
115 }
116 }
117
118 // Deduce the cash settlement date if not given.
119 Date effectiveUpfrontDate = upfrontDate;
120 if (effectiveUpfrontDate == Date()) {
121 effectiveUpfrontDate = schedule.calendar().advance(tradeDate_,
122 n: cashSettlementDays_, unit: Days, convention: paymentConvention);
123 }
124 QL_REQUIRE(effectiveUpfrontDate >= protectionStart_,
125 "The cash settlement date must not be before the protection start date.");
126
127 // Create the upfront payment, if one is provided.
128 Real upfrontAmount = 0.0;
129 if (upfront_) // NOLINT(readability-implicit-bool-conversion)
130 upfrontAmount = *upfront_ * notional_;
131 upfrontPayment_ = ext::make_shared<SimpleCashFlow>(args&: upfrontAmount, args&: effectiveUpfrontDate);
132
133 // Set the maturity date.
134 maturity_ = schedule.dates().back();
135
136 // Deal with the accrual rebate. We use the standard conventions for accrual calculation introduced with the
137 // CDS Big Bang in 2009.
138 if (rebatesAccrual) {
139
140 Real rebateAmount = 0.0;
141 Date refDate = tradeDate_ + 1;
142
143 if (tradeDate_ >= schedule.dates().front()) {
144 for (Size i = 0; i < leg_.size(); ++i) {
145 const ext::shared_ptr<CashFlow>& cf = leg_[i];
146 if (refDate > cf->date()) {
147 // This coupon is in the past; check the next one
148 continue;
149 } else if (refDate == cf->date()) {
150 // This coupon pays at the reference date.
151 // If it's not the last coupon, the accrual is 0 so do nothing.
152 if (i < leg_.size() - 1)
153 rebateAmount = 0.0;
154 else {
155 // On last coupon
156 ext::shared_ptr<FixedRateCoupon> frc = ext::dynamic_pointer_cast<FixedRateCoupon>(r: cf);
157 rebateAmount = frc->amount();
158 }
159 break;
160 } else {
161 // This coupon pays in the future, and is the first coupon to do so (since they're sorted).
162 // Calculate the accrual and skip further coupons
163 ext::shared_ptr<FixedRateCoupon> frc = ext::dynamic_pointer_cast<FixedRateCoupon>(r: cf);
164 rebateAmount = frc->accruedAmount(refDate);
165 break;
166 }
167 }
168 }
169
170 accrualRebate_ = ext::make_shared<SimpleCashFlow>(args&: rebateAmount, args&: effectiveUpfrontDate);
171 }
172
173 if (!claim_)
174 claim_ = ext::make_shared<FaceValueClaim>();
175 registerWith(h: claim_);
176 }
177
178 Protection::Side CreditDefaultSwap::side() const {
179 return side_;
180 }
181
182 Real CreditDefaultSwap::notional() const {
183 return notional_;
184 }
185
186 Rate CreditDefaultSwap::runningSpread() const {
187 return runningSpread_;
188 }
189
190 ext::optional<Rate> CreditDefaultSwap::upfront() const {
191 return upfront_;
192 }
193
194 bool CreditDefaultSwap::settlesAccrual() const {
195 return settlesAccrual_;
196 }
197
198 bool CreditDefaultSwap::paysAtDefaultTime() const {
199 return paysAtDefaultTime_;
200 }
201
202 const Leg& CreditDefaultSwap::coupons() const {
203 return leg_;
204 }
205
206
207 bool CreditDefaultSwap::isExpired() const {
208 for (auto i = leg_.rbegin(); i != leg_.rend(); ++i) {
209 if (!(*i)->hasOccurred())
210 return false;
211 }
212 return true;
213 }
214
215 void CreditDefaultSwap::setupExpired() const {
216 Instrument::setupExpired();
217 fairSpread_ = fairUpfront_ = 0.0;
218 couponLegBPS_ = upfrontBPS_ = 0.0;
219 couponLegNPV_ = defaultLegNPV_ = upfrontNPV_ = 0.0;
220 }
221
222 void CreditDefaultSwap::setupArguments(
223 PricingEngine::arguments* args) const {
224 auto* arguments = dynamic_cast<CreditDefaultSwap::arguments*>(args);
225 QL_REQUIRE(arguments != nullptr, "wrong argument type");
226
227 arguments->side = side_;
228 arguments->notional = notional_;
229 arguments->leg = leg_;
230 arguments->upfrontPayment = upfrontPayment_;
231 arguments->accrualRebate = accrualRebate_;
232 arguments->settlesAccrual = settlesAccrual_;
233 arguments->paysAtDefaultTime = paysAtDefaultTime_;
234 arguments->claim = claim_;
235 arguments->upfront = upfront_;
236 arguments->spread = runningSpread_;
237 arguments->protectionStart = protectionStart_;
238 arguments->maturity = maturity_;
239 }
240
241
242 void CreditDefaultSwap::fetchResults(
243 const PricingEngine::results* r) const {
244 Instrument::fetchResults(r);
245
246 const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(r);
247 QL_REQUIRE(results != nullptr, "wrong result type");
248
249 fairSpread_ = results->fairSpread;
250 fairUpfront_ = results->fairUpfront;
251 couponLegBPS_ = results->couponLegBPS;
252 couponLegNPV_ = results->couponLegNPV;
253 defaultLegNPV_ = results->defaultLegNPV;
254 upfrontNPV_ = results->upfrontNPV;
255 upfrontBPS_ = results->upfrontBPS;
256 accrualRebateNPV_ = results->accrualRebateNPV;
257 }
258
259 Rate CreditDefaultSwap::fairUpfront() const {
260 calculate();
261 QL_REQUIRE(fairUpfront_ != Null<Rate>(),
262 "fair upfront not available");
263 return fairUpfront_;
264 }
265
266 Rate CreditDefaultSwap::fairSpread() const {
267 calculate();
268 QL_REQUIRE(fairSpread_ != Null<Rate>(),
269 "fair spread not available");
270 return fairSpread_;
271 }
272
273 Real CreditDefaultSwap::couponLegBPS() const {
274 calculate();
275 QL_REQUIRE(couponLegBPS_ != Null<Rate>(),
276 "coupon-leg BPS not available");
277 return couponLegBPS_;
278 }
279
280 Real CreditDefaultSwap::couponLegNPV() const {
281 calculate();
282 QL_REQUIRE(couponLegNPV_ != Null<Rate>(),
283 "coupon-leg NPV not available");
284 return couponLegNPV_;
285 }
286
287 Real CreditDefaultSwap::defaultLegNPV() const {
288 calculate();
289 QL_REQUIRE(defaultLegNPV_ != Null<Rate>(),
290 "default-leg NPV not available");
291 return defaultLegNPV_;
292 }
293
294 Real CreditDefaultSwap::upfrontNPV() const {
295 calculate();
296 QL_REQUIRE(upfrontNPV_ != Null<Real>(),
297 "upfront NPV not available");
298 return upfrontNPV_;
299 }
300
301 Real CreditDefaultSwap::upfrontBPS() const {
302 calculate();
303 QL_REQUIRE(upfrontBPS_ != Null<Real>(),
304 "upfront BPS not available");
305 return upfrontBPS_;
306 }
307
308 Real CreditDefaultSwap::accrualRebateNPV() const {
309 calculate();
310 QL_REQUIRE(accrualRebateNPV_ != Null<Real>(),
311 "accrual Rebate NPV not available");
312 return accrualRebateNPV_;
313 }
314
315 namespace {
316
317 class ObjectiveFunction {
318 public:
319 ObjectiveFunction(Real target,
320 SimpleQuote& quote,
321 PricingEngine& engine,
322 const CreditDefaultSwap::results* results)
323 : target_(target), quote_(quote),
324 engine_(engine), results_(results) {}
325
326 Real operator()(Real guess) const {
327 quote_.setValue(guess);
328 engine_.calculate();
329 return results_->value - target_;
330 }
331 private:
332 Real target_;
333 SimpleQuote& quote_;
334 PricingEngine& engine_;
335 const CreditDefaultSwap::results* results_;
336 };
337
338 }
339
340 Rate CreditDefaultSwap::impliedHazardRate(
341 Real targetNPV,
342 const Handle<YieldTermStructure>& discountCurve,
343 const DayCounter& dayCounter,
344 Real recoveryRate,
345 Real accuracy,
346 PricingModel model) const {
347
348 ext::shared_ptr<SimpleQuote> flatRate = ext::make_shared<SimpleQuote>(args: 0.0);
349
350 Handle<DefaultProbabilityTermStructure> probability =
351 Handle<DefaultProbabilityTermStructure>(
352 ext::make_shared<FlatHazardRate>(args: 0, args: WeekendsOnly(),
353 args: Handle<Quote>(flatRate), args: dayCounter));
354
355 ext::shared_ptr<PricingEngine> engine;
356 switch (model) {
357 case Midpoint:
358 engine = ext::make_shared<MidPointCdsEngine>(
359 args&: probability, args&: recoveryRate, args: discountCurve);
360 break;
361 case ISDA:
362 engine = ext::make_shared<IsdaCdsEngine>(
363 args&: probability, args&: recoveryRate, args: discountCurve,
364 args: ext::nullopt,
365 args: IsdaCdsEngine::Taylor,
366 args: IsdaCdsEngine::HalfDayBias,
367 args: IsdaCdsEngine::Piecewise);
368 break;
369 default:
370 QL_FAIL("unknown CDS pricing model: " << model);
371 }
372
373 setupArguments(engine->getArguments());
374 const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(engine->getResults());
375
376 ObjectiveFunction f(targetNPV, *flatRate, *engine, results);
377 //very close guess if targetNPV = 0.
378 Rate guess = runningSpread_ / (1 - recoveryRate) * 365./360.;
379 Real step = 0.1 * guess;
380 return Brent().solve(f, accuracy, guess, step);
381 }
382
383 Rate CreditDefaultSwap::conventionalSpread(
384 Real conventionalRecovery,
385 const Handle<YieldTermStructure>& discountCurve,
386 const DayCounter& dayCounter,
387 PricingModel model) const {
388
389 ext::shared_ptr<SimpleQuote> flatRate = ext::make_shared<SimpleQuote>(args: 0.0);
390
391 Handle<DefaultProbabilityTermStructure> probability =
392 Handle<DefaultProbabilityTermStructure>(
393 ext::make_shared<FlatHazardRate>(args: 0, args: WeekendsOnly(),
394 args: Handle<Quote>(flatRate), args: dayCounter));
395
396 ext::shared_ptr<PricingEngine> engine;
397 switch (model) {
398 case Midpoint:
399 engine = ext::make_shared<MidPointCdsEngine>(
400 args&: probability, args&: conventionalRecovery, args: discountCurve);
401 break;
402 case ISDA:
403 engine = ext::make_shared<IsdaCdsEngine>(
404 args&: probability, args&: conventionalRecovery, args: discountCurve,
405 args: ext::nullopt,
406 args: IsdaCdsEngine::Taylor,
407 args: IsdaCdsEngine::HalfDayBias,
408 args: IsdaCdsEngine::Piecewise);
409 break;
410 default:
411 QL_FAIL("unknown CDS pricing model: " << model);
412 }
413
414 setupArguments(engine->getArguments());
415 const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(engine->getResults());
416
417 ObjectiveFunction f(0., *flatRate, *engine, results);
418 Rate guess = runningSpread_ / (1 - conventionalRecovery) * 365./360.;
419 Real step = guess * 0.1;
420
421 Brent().solve(f, accuracy: 1e-9, guess, step);
422 return results->fairSpread;
423 }
424
425
426 const Date& CreditDefaultSwap::protectionStartDate() const {
427 return protectionStart_;
428 }
429
430 const Date& CreditDefaultSwap::protectionEndDate() const {
431 return ext::dynamic_pointer_cast<Coupon>(r: leg_.back())
432 ->accrualEndDate();
433 }
434
435 const ext::shared_ptr<SimpleCashFlow>& CreditDefaultSwap::upfrontPayment() const {
436 return upfrontPayment_;
437 }
438
439 const ext::shared_ptr<SimpleCashFlow>& CreditDefaultSwap::accrualRebate() const {
440 return accrualRebate_;
441 }
442
443 const Date& CreditDefaultSwap::tradeDate() const {
444 return tradeDate_;
445 }
446
447 Natural CreditDefaultSwap::cashSettlementDays() const {
448 return cashSettlementDays_;
449 }
450
451 CreditDefaultSwap::arguments::arguments()
452 : side(Protection::Side(-1)), notional(Null<Real>()),
453 spread(Null<Rate>()) {}
454
455 void CreditDefaultSwap::arguments::validate() const {
456 QL_REQUIRE(side != Protection::Side(-1), "side not set");
457 QL_REQUIRE(notional != Null<Real>(), "notional not set");
458 QL_REQUIRE(notional != 0.0, "null notional set");
459 QL_REQUIRE(spread != Null<Rate>(), "spread not set");
460 QL_REQUIRE(!leg.empty(), "coupons not set");
461 QL_REQUIRE(upfrontPayment, "upfront payment not set");
462 QL_REQUIRE(claim, "claim not set");
463 QL_REQUIRE(protectionStart != Null<Date>(),
464 "protection start date not set");
465 QL_REQUIRE(maturity != Null<Date>(),
466 "maturity date not set");
467 }
468
469 void CreditDefaultSwap::results::reset() {
470 Instrument::results::reset();
471 fairSpread = Null<Rate>();
472 fairUpfront = Null<Rate>();
473 couponLegBPS = Null<Real>();
474 couponLegNPV = Null<Real>();
475 defaultLegNPV = Null<Real>();
476 upfrontBPS = Null<Real>();
477 upfrontNPV = Null<Real>();
478 accrualRebateNPV = Null<Real>();
479 }
480
481 Date cdsMaturity(const Date& tradeDate, const Period& tenor, DateGeneration::Rule rule) {
482
483 QL_REQUIRE(rule == DateGeneration::CDS2015 || rule == DateGeneration::CDS || rule == DateGeneration::OldCDS,
484 "cdsMaturity should only be used with date generation rule CDS2015, CDS or OldCDS");
485
486 QL_REQUIRE(tenor.units() == Years || (tenor.units() == Months && tenor.length() % 3 == 0),
487 "cdsMaturity expects a tenor that is a multiple of 3 months.");
488
489 if (rule == DateGeneration::OldCDS) {
490 QL_REQUIRE(tenor != 0 * Months, "A tenor of 0M is not supported for OldCDS.");
491 }
492
493 Date anchorDate = previousTwentieth(d: tradeDate, rule);
494 if (rule == DateGeneration::CDS2015 && (anchorDate == Date(20, Dec, anchorDate.year()) ||
495 anchorDate == Date(20, Jun, anchorDate.year()))) {
496 if (tenor.length() == 0) {
497 return Null<Date>();
498 } else {
499 anchorDate -= 3 * Months;
500 }
501 }
502
503 Date maturity = anchorDate + tenor + 3 * Months;
504 QL_REQUIRE(maturity > tradeDate, "error calculating CDS maturity. Tenor is " << tenor << ", trade date is " <<
505 io::iso_date(tradeDate) << " generating a maturity of " << io::iso_date(maturity) << " <= trade date.");
506
507 return maturity;
508 }
509
510}
511

source code of quantlib/ql/instruments/creditdefaultswap.cpp

Morty Proxy This is a proxified and sanitized view of the page, visit original site.