1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2008, 2009 StatPro Italia srl
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include "creditdefaultswap.hpp"
21#include "utilities.hpp"
22#include <ql/cashflows/iborcoupon.hpp>
23#include <ql/instruments/creditdefaultswap.hpp>
24#include <ql/instruments/makecds.hpp>
25#include <ql/pricingengines/credit/midpointcdsengine.hpp>
26#include <ql/pricingengines/credit/integralcdsengine.hpp>
27#include <ql/pricingengines/credit/isdacdsengine.hpp>
28#include <ql/termstructures/credit/flathazardrate.hpp>
29#include <ql/termstructures/credit/interpolatedhazardratecurve.hpp>
30#include <ql/termstructures/yield/flatforward.hpp>
31#include <ql/termstructures/yield/discountcurve.hpp>
32#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
33#include <ql/termstructures/yield/ratehelpers.hpp>
34#include <ql/quotes/simplequote.hpp>
35#include <ql/math/interpolations/backwardflatinterpolation.hpp>
36#include <ql/time/calendars/target.hpp>
37#include <ql/time/calendars/unitedstates.hpp>
38#include <ql/time/calendars/weekendsonly.hpp>
39#include <ql/currencies/america.hpp>
40#include <ql/currencies/europe.hpp>
41#include <ql/time/daycounters/actual360.hpp>
42#include <ql/time/daycounters/thirty360.hpp>
43#include <ql/optional.hpp>
44#include <map>
45#include <iomanip>
46#include <iostream>
47
48using namespace QuantLib;
49using namespace boost::unit_test_framework;
50using std::map;
51
52void CreditDefaultSwapTest::testCachedValue() {
53
54 BOOST_TEST_MESSAGE("Testing credit-default swap against cached values...");
55
56 // Initialize curves
57 Settings::instance().evaluationDate() = Date(9,June,2006);
58 Date today = Settings::instance().evaluationDate();
59 Calendar calendar = TARGET();
60
61 Handle<Quote> hazardRate = Handle<Quote>(
62 ext::shared_ptr<Quote>(new SimpleQuote(0.01234)));
63 RelinkableHandle<DefaultProbabilityTermStructure> probabilityCurve;
64 probabilityCurve.linkTo(
65 h: ext::shared_ptr<DefaultProbabilityTermStructure>(
66 new FlatHazardRate(0, calendar, hazardRate, Actual360())));
67
68 RelinkableHandle<YieldTermStructure> discountCurve;
69
70 discountCurve.linkTo(h: ext::shared_ptr<YieldTermStructure>(
71 new FlatForward(today,0.06,Actual360())));
72
73 // Build the schedule
74 Date issueDate = calendar.advance(today, n: -1, unit: Years);
75 Date maturity = calendar.advance(issueDate, n: 10, unit: Years);
76 Frequency frequency = Semiannual;
77 BusinessDayConvention convention = ModifiedFollowing;
78
79 Schedule schedule(issueDate, maturity, Period(frequency), calendar,
80 convention, convention, DateGeneration::Forward, false);
81
82 // Build the CDS
83 Rate fixedRate = 0.0120;
84 DayCounter dayCount = Actual360();
85 Real notional = 10000.0;
86 Real recoveryRate = 0.4;
87
88 CreditDefaultSwap cds(Protection::Seller, notional, fixedRate,
89 schedule, convention, dayCount, true, true);
90 cds.setPricingEngine(ext::shared_ptr<PricingEngine>(
91 new MidPointCdsEngine(probabilityCurve,recoveryRate,discountCurve)));
92
93 Real npv = 295.0153398;
94 Rate fairRate = 0.007517539081;
95
96 Real calculatedNpv = cds.NPV();
97 Rate calculatedFairRate = cds.fairSpread();
98 Real tolerance = 1.0e-7;
99
100 if (std::fabs(x: calculatedNpv - npv) > tolerance)
101 BOOST_ERROR(
102 "Failed to reproduce NPV with mid-point engine\n"
103 << std::setprecision(10)
104 << " calculated NPV: " << calculatedNpv << "\n"
105 << " expected NPV: " << npv);
106
107 if (std::fabs(x: calculatedFairRate - fairRate) > tolerance)
108 BOOST_ERROR(
109 "Failed to reproduce fair rate with mid-point engine\n"
110 << std::setprecision(10)
111 << " calculated fair rate: " << calculatedFairRate << "\n"
112 << " expected fair rate: " << fairRate);
113
114 cds.setPricingEngine(ext::shared_ptr<PricingEngine>(
115 new IntegralCdsEngine(1*Days,probabilityCurve,
116 recoveryRate,discountCurve)));
117
118 calculatedNpv = cds.NPV();
119 calculatedFairRate = cds.fairSpread();
120 tolerance = 1.0e-5;
121
122 if (std::fabs(x: calculatedNpv - npv) > notional*tolerance*10)
123 BOOST_ERROR(
124 "Failed to reproduce NPV with integral engine "
125 "(step = 1 day)\n"
126 << std::setprecision(10)
127 << " calculated NPV: " << calculatedNpv << "\n"
128 << " expected NPV: " << npv);
129
130 if (std::fabs(x: calculatedFairRate - fairRate) > tolerance)
131 BOOST_ERROR(
132 "Failed to reproduce fair rate with integral engine "
133 "(step = 1 day)\n"
134 << std::setprecision(10)
135 << " calculated fair rate: " << calculatedFairRate << "\n"
136 << " expected fair rate: " << fairRate);
137
138 cds.setPricingEngine(ext::shared_ptr<PricingEngine>(
139 new IntegralCdsEngine(1*Weeks,probabilityCurve,
140 recoveryRate,discountCurve)));
141
142 calculatedNpv = cds.NPV();
143 calculatedFairRate = cds.fairSpread();
144 tolerance = 1.0e-5;
145
146 if (std::fabs(x: calculatedNpv - npv) > notional*tolerance*10)
147 BOOST_ERROR(
148 "Failed to reproduce NPV with integral engine "
149 "(step = 1 week)\n"
150 << std::setprecision(10)
151 << " calculated NPV: " << calculatedNpv << "\n"
152 << " expected NPV: " << npv);
153
154 if (std::fabs(x: calculatedFairRate - fairRate) > tolerance)
155 BOOST_ERROR(
156 "Failed to reproduce fair rate with integral engine "
157 "(step = 1 week)\n"
158 << std::setprecision(10)
159 << " calculated fair rate: " << calculatedFairRate << "\n"
160 << " expected fair rate: " << fairRate);
161}
162
163
164void CreditDefaultSwapTest::testCachedMarketValue() {
165
166 BOOST_TEST_MESSAGE(
167 "Testing credit-default swap against cached market values...");
168
169 Settings::instance().evaluationDate() = Date(9,June,2006);
170 Date evalDate = Settings::instance().evaluationDate();
171 Calendar calendar = UnitedStates(UnitedStates::GovernmentBond);
172
173 std::vector<Date> discountDates = {
174 evalDate,
175 calendar.advance(evalDate, n: 1, unit: Weeks, convention: ModifiedFollowing),
176 calendar.advance(evalDate, n: 1, unit: Months, convention: ModifiedFollowing),
177 calendar.advance(evalDate, n: 2, unit: Months, convention: ModifiedFollowing),
178 calendar.advance(evalDate, n: 3, unit: Months, convention: ModifiedFollowing),
179 calendar.advance(evalDate, n: 6, unit: Months, convention: ModifiedFollowing),
180 calendar.advance(evalDate,n: 12, unit: Months, convention: ModifiedFollowing),
181 calendar.advance(evalDate, n: 2, unit: Years, convention: ModifiedFollowing),
182 calendar.advance(evalDate, n: 3, unit: Years, convention: ModifiedFollowing),
183 calendar.advance(evalDate, n: 4, unit: Years, convention: ModifiedFollowing),
184 calendar.advance(evalDate, n: 5, unit: Years, convention: ModifiedFollowing),
185 calendar.advance(evalDate, n: 6, unit: Years, convention: ModifiedFollowing),
186 calendar.advance(evalDate, n: 7, unit: Years, convention: ModifiedFollowing),
187 calendar.advance(evalDate, n: 8, unit: Years, convention: ModifiedFollowing),
188 calendar.advance(evalDate, n: 9, unit: Years, convention: ModifiedFollowing),
189 calendar.advance(evalDate,n: 10, unit: Years, convention: ModifiedFollowing),
190 calendar.advance(evalDate,n: 15, unit: Years, convention: ModifiedFollowing)
191 };
192
193 std::vector<DiscountFactor> dfs = {
194 1.0,
195 0.9990151375768731,
196 0.99570502636871183,
197 0.99118260474528685,
198 0.98661167950906203,
199 0.9732592953359388,
200 0.94724424481038083,
201 0.89844996737120875,
202 0.85216647839921411,
203 0.80775477692556874,
204 0.76517289234200347,
205 0.72401019553182933,
206 0.68503909569219212,
207 0.64797499814013748,
208 0.61263171936255534,
209 0.5791942350748791,
210 0.43518868769953606
211 };
212
213 const DayCounter& curveDayCounter=Actual360();
214
215 RelinkableHandle<YieldTermStructure> discountCurve;
216 discountCurve.linkTo(
217 h: ext::shared_ptr<YieldTermStructure>(
218 new DiscountCurve(discountDates, dfs, curveDayCounter)));
219
220 DayCounter dayCounter = Thirty360(Thirty360::BondBasis);
221 std::vector<Date> dates = {
222 evalDate,
223 calendar.advance(evalDate, n: 6, unit: Months, convention: ModifiedFollowing),
224 calendar.advance(evalDate, n: 1, unit: Years, convention: ModifiedFollowing),
225 calendar.advance(evalDate, n: 2, unit: Years, convention: ModifiedFollowing),
226 calendar.advance(evalDate, n: 3, unit: Years, convention: ModifiedFollowing),
227 calendar.advance(evalDate, n: 4, unit: Years, convention: ModifiedFollowing),
228 calendar.advance(evalDate, n: 5, unit: Years, convention: ModifiedFollowing),
229 calendar.advance(evalDate, n: 7, unit: Years, convention: ModifiedFollowing),
230 calendar.advance(evalDate,n: 10, unit: Years, convention: ModifiedFollowing)
231 };
232
233 std::vector<Probability> defaultProbabilities = {
234 0.0000,
235 0.0047,
236 0.0093,
237 0.0286,
238 0.0619,
239 0.0953,
240 0.1508,
241 0.2288,
242 0.3666
243 };
244
245 std::vector<Real> hazardRates;
246 hazardRates.push_back(x: 0.0);
247 for (Size i=1; i<dates.size(); ++i) {
248 Time t1 = dayCounter.yearFraction(d1: dates[0], d2: dates[i-1]);
249 Time t2 = dayCounter.yearFraction(d1: dates[0], d2: dates[i]);
250 Probability S1 = 1.0 - defaultProbabilities[i-1];
251 Probability S2 = 1.0 - defaultProbabilities[i];
252 hazardRates.push_back(x: std::log(x: S1/S2)/(t2-t1));
253 }
254
255 RelinkableHandle<DefaultProbabilityTermStructure> piecewiseFlatHazardRate;
256 piecewiseFlatHazardRate.linkTo(
257 h: ext::shared_ptr<DefaultProbabilityTermStructure>(
258 new InterpolatedHazardRateCurve<BackwardFlat>(dates,
259 hazardRates,
260 Thirty360(Thirty360::BondBasis))));
261
262 // Testing credit default swap
263
264 // Build the schedule
265 Date issueDate(20, March, 2006);
266 Date maturity(20, June, 2013);
267 Frequency cdsFrequency = Semiannual;
268 BusinessDayConvention cdsConvention = ModifiedFollowing;
269
270 Schedule schedule(issueDate, maturity, Period(cdsFrequency), calendar,
271 cdsConvention, cdsConvention,
272 DateGeneration::Forward, false);
273
274 // Build the CDS
275 Real recoveryRate = 0.25;
276 Rate fixedRate=0.0224;
277 DayCounter dayCount=Actual360();
278 Real cdsNotional=100.0;
279
280 CreditDefaultSwap cds(Protection::Seller, cdsNotional, fixedRate,
281 schedule, cdsConvention, dayCount, true, true);
282 cds.setPricingEngine(ext::shared_ptr<PricingEngine>(
283 new MidPointCdsEngine(piecewiseFlatHazardRate,
284 recoveryRate,discountCurve)));
285
286 Real calculatedNpv = cds.NPV();
287 Real calculatedFairRate = cds.fairSpread();
288
289 double npv = -1.364048777; // from Bloomberg we have 98.15598868 - 100.00;
290 double fairRate = 0.0248429452; // from Bloomberg we have 0.0258378;
291
292 Real tolerance = 1e-9;
293
294 if (std::fabs(x: npv - calculatedNpv) > tolerance)
295 BOOST_ERROR(
296 "Failed to reproduce the npv for the given credit-default swap\n"
297 << std::setprecision(10)
298 << " computed NPV: " << calculatedNpv << "\n"
299 << " Given NPV: " << npv);
300
301 if (std::fabs(x: fairRate - calculatedFairRate) > tolerance)
302 BOOST_ERROR(
303 "Failed to reproduce the fair rate for the given credit-default swap\n"
304 << std::setprecision(10)
305 << " computed fair rate: " << calculatedFairRate << "\n"
306 << " Given fair rate: " << fairRate);
307}
308
309
310void CreditDefaultSwapTest::testImpliedHazardRate() {
311
312 BOOST_TEST_MESSAGE("Testing implied hazard-rate for credit-default swaps...");
313
314 // Initialize curves
315 Calendar calendar = TARGET();
316 Date today = calendar.adjust(Date::todaysDate());
317 Settings::instance().evaluationDate() = today;
318
319 Rate h1 = 0.30, h2 = 0.40;
320 DayCounter dayCounter = Actual365Fixed();
321
322 std::vector<Date> dates(3);
323 std::vector<Real> hazardRates(3);
324 dates[0] = today;
325 hazardRates[0] = h1;
326
327 dates[1] = today + 5*Years;
328 hazardRates[1] = h1;
329
330 dates[2] = today + 10*Years;
331 hazardRates[2] = h2;
332
333 RelinkableHandle<DefaultProbabilityTermStructure> probabilityCurve;
334 probabilityCurve.linkTo(h: ext::shared_ptr<DefaultProbabilityTermStructure>(
335 new InterpolatedHazardRateCurve<BackwardFlat>(dates,
336 hazardRates,
337 dayCounter)));
338
339 RelinkableHandle<YieldTermStructure> discountCurve;
340 discountCurve.linkTo(h: ext::shared_ptr<YieldTermStructure>(
341 new FlatForward(today,0.03,Actual360())));
342
343
344 Frequency frequency = Semiannual;
345 BusinessDayConvention convention = ModifiedFollowing;
346
347 Date issueDate = calendar.advance(today, n: -6, unit: Months);
348 Rate fixedRate = 0.0120;
349 DayCounter cdsDayCount = Actual360();
350 Real notional = 10000.0;
351 Real recoveryRate = 0.4;
352
353 Rate latestRate = Null<Rate>();
354 for (Integer n=6; n<=10; ++n) {
355
356 Date maturity = calendar.advance(issueDate, n, unit: Years);
357 Schedule schedule(issueDate, maturity, Period(frequency), calendar,
358 convention, convention,
359 DateGeneration::Forward, false);
360
361 CreditDefaultSwap cds(Protection::Seller, notional, fixedRate,
362 schedule, convention, cdsDayCount,
363 true, true);
364 cds.setPricingEngine(ext::shared_ptr<PricingEngine>(
365 new MidPointCdsEngine(probabilityCurve,
366 recoveryRate, discountCurve)));
367
368 Real NPV = cds.NPV();
369 Rate flatRate = cds.impliedHazardRate(targetNPV: NPV, discountCurve,
370 dayCounter,
371 recoveryRate);
372
373 if (flatRate < h1 || flatRate > h2) {
374 BOOST_ERROR("implied hazard rate outside expected range\n"
375 << " maturity: " << n << " years\n"
376 << " expected minimum: " << h1 << "\n"
377 << " expected maximum: " << h2 << "\n"
378 << " implied rate: " << flatRate);
379 }
380
381 if (n > 6 && flatRate < latestRate) {
382 BOOST_ERROR("implied hazard rate decreasing with swap maturity\n"
383 << " maturity: " << n << " years\n"
384 << " previous rate: " << latestRate << "\n"
385 << " implied rate: " << flatRate);
386 }
387
388 latestRate = flatRate;
389
390 RelinkableHandle<DefaultProbabilityTermStructure> probability;
391 probability.linkTo(h: ext::shared_ptr<DefaultProbabilityTermStructure>(
392 new FlatHazardRate(
393 today,
394 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(flatRate))),
395 dayCounter)));
396
397 CreditDefaultSwap cds2(Protection::Seller, notional, fixedRate,
398 schedule, convention, cdsDayCount,
399 true, true);
400 cds2.setPricingEngine(ext::shared_ptr<PricingEngine>(
401 new MidPointCdsEngine(probability,recoveryRate,
402 discountCurve)));
403
404 Real NPV2 = cds2.NPV();
405 Real tolerance = 1.0;
406 if (std::fabs(x: NPV-NPV2) > tolerance) {
407 BOOST_ERROR("failed to reproduce NPV with implied rate\n"
408 << " expected: " << NPV << "\n"
409 << " calculated: " << NPV2);
410 }
411 }
412}
413
414
415void CreditDefaultSwapTest::testFairSpread() {
416
417 BOOST_TEST_MESSAGE(
418 "Testing fair-spread calculation for credit-default swaps...");
419
420 // Initialize curves
421 Calendar calendar = TARGET();
422 Date today = calendar.adjust(Date::todaysDate());
423 Settings::instance().evaluationDate() = today;
424
425 Handle<Quote> hazardRate = Handle<Quote>(
426 ext::shared_ptr<Quote>(new SimpleQuote(0.01234)));
427 RelinkableHandle<DefaultProbabilityTermStructure> probabilityCurve;
428 probabilityCurve.linkTo(
429 h: ext::shared_ptr<DefaultProbabilityTermStructure>(
430 new FlatHazardRate(0, calendar, hazardRate, Actual360())));
431
432 RelinkableHandle<YieldTermStructure> discountCurve;
433 discountCurve.linkTo(h: ext::shared_ptr<YieldTermStructure>(
434 new FlatForward(today,0.06,Actual360())));
435
436 // Build the schedule
437 Date issueDate = calendar.advance(today, n: -1, unit: Years);
438 Date maturity = calendar.advance(issueDate, n: 10, unit: Years);
439 BusinessDayConvention convention = Following;
440
441 Schedule schedule =
442 MakeSchedule().from(effectiveDate: issueDate)
443 .to(terminationDate: maturity)
444 .withFrequency(Quarterly)
445 .withCalendar(calendar)
446 .withTerminationDateConvention(convention)
447 .withRule(DateGeneration::TwentiethIMM);
448
449 // Build the CDS
450 Rate fixedRate = 0.001;
451 DayCounter dayCount = Actual360();
452 Real notional = 10000.0;
453 Real recoveryRate = 0.4;
454
455 ext::shared_ptr<PricingEngine> engine(
456 new MidPointCdsEngine(probabilityCurve,recoveryRate,discountCurve));
457
458 CreditDefaultSwap cds(Protection::Seller, notional, fixedRate,
459 schedule, convention, dayCount, true, true);
460 cds.setPricingEngine(engine);
461
462 Rate fairRate = cds.fairSpread();
463
464 CreditDefaultSwap fairCds(Protection::Seller, notional, fairRate,
465 schedule, convention, dayCount, true, true);
466 fairCds.setPricingEngine(engine);
467
468 Real fairNPV = fairCds.NPV();
469 Real tolerance = 1e-9;
470
471 if (std::fabs(x: fairNPV) > tolerance)
472 BOOST_ERROR(
473 "Failed to reproduce null NPV with calculated fair spread\n"
474 << " calculated spread: " << io::rate(fairRate) << "\n"
475 << " calculated NPV: " << fairNPV);
476}
477
478void CreditDefaultSwapTest::testFairUpfront() {
479
480 BOOST_TEST_MESSAGE(
481 "Testing fair-upfront calculation for credit-default swaps...");
482
483 // Initialize curves
484 Calendar calendar = TARGET();
485 Date today = calendar.adjust(Date::todaysDate());
486 Settings::instance().evaluationDate() = today;
487
488 Handle<Quote> hazardRate = Handle<Quote>(
489 ext::shared_ptr<Quote>(new SimpleQuote(0.01234)));
490 RelinkableHandle<DefaultProbabilityTermStructure> probabilityCurve;
491 probabilityCurve.linkTo(
492 h: ext::shared_ptr<DefaultProbabilityTermStructure>(
493 new FlatHazardRate(0, calendar, hazardRate, Actual360())));
494
495 RelinkableHandle<YieldTermStructure> discountCurve;
496 discountCurve.linkTo(h: ext::shared_ptr<YieldTermStructure>(
497 new FlatForward(today,0.06,Actual360())));
498
499 // Build the schedule
500 Date issueDate = today;
501 Date maturity = calendar.advance(issueDate, n: 10, unit: Years);
502 BusinessDayConvention convention = Following;
503
504 Schedule schedule =
505 MakeSchedule().from(effectiveDate: issueDate)
506 .to(terminationDate: maturity)
507 .withFrequency(Quarterly)
508 .withCalendar(calendar)
509 .withTerminationDateConvention(convention)
510 .withRule(DateGeneration::TwentiethIMM);
511
512 // Build the CDS
513 Rate fixedRate = 0.05;
514 Rate upfront = 0.001;
515 DayCounter dayCount = Actual360();
516 Real notional = 10000.0;
517 Real recoveryRate = 0.4;
518
519 ext::shared_ptr<PricingEngine> engine(
520 new MidPointCdsEngine(probabilityCurve, recoveryRate,
521 discountCurve, true));
522
523 CreditDefaultSwap cds(Protection::Seller, notional, upfront, fixedRate,
524 schedule, convention, dayCount, true, true);
525 cds.setPricingEngine(engine);
526
527 Rate fairUpfront = cds.fairUpfront();
528
529 CreditDefaultSwap fairCds(Protection::Seller, notional,
530 fairUpfront, fixedRate,
531 schedule, convention, dayCount, true, true);
532 fairCds.setPricingEngine(engine);
533
534 Real fairNPV = fairCds.NPV();
535 Real tolerance = 1e-9;
536
537 if (std::fabs(x: fairNPV) > tolerance)
538 BOOST_ERROR(
539 "Failed to reproduce null NPV with calculated fair upfront\n"
540 << " calculated upfront: " << io::rate(fairUpfront) << "\n"
541 << " calculated NPV: " << fairNPV);
542
543 // same with null upfront to begin with
544 upfront = 0.0;
545 CreditDefaultSwap cds2(Protection::Seller, notional, upfront, fixedRate,
546 schedule, convention, dayCount, true, true);
547 cds2.setPricingEngine(engine);
548
549 fairUpfront = cds2.fairUpfront();
550
551 CreditDefaultSwap fairCds2(Protection::Seller, notional,
552 fairUpfront, fixedRate,
553 schedule, convention, dayCount, true, true);
554 fairCds2.setPricingEngine(engine);
555
556 fairNPV = fairCds2.NPV();
557
558 if (std::fabs(x: fairNPV) > tolerance)
559 BOOST_ERROR(
560 "Failed to reproduce null NPV with calculated fair upfront\n"
561 << " calculated upfront: " << io::rate(fairUpfront) << "\n"
562 << " calculated NPV: " << fairNPV);
563}
564
565void CreditDefaultSwapTest::testIsdaEngine() {
566
567 BOOST_TEST_MESSAGE(
568 "Testing ISDA engine calculations for credit-default swaps...");
569
570 bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons();
571
572 Date tradeDate(21, May, 2009);
573 Settings::instance().evaluationDate() = tradeDate;
574
575
576 //build an ISDA compliant yield curve
577 //data comes from Markit published rates
578 std::vector<ext::shared_ptr<RateHelper> > isdaRateHelpers;
579 int dep_tenors[] = {1, 2, 3, 6, 9, 12};
580 double dep_quotes[] = {0.003081,
581 0.005525,
582 0.007163,
583 0.012413,
584 0.014,
585 0.015488};
586
587 for(size_t i = 0; i < sizeof(dep_tenors) / sizeof(int); i++) {
588 isdaRateHelpers.push_back(x: ext::make_shared<DepositRateHelper>(
589 args&: dep_quotes[i], args: dep_tenors[i] * Months, args: 2,
590 args: WeekendsOnly(), args: ModifiedFollowing,
591 args: false, args: Actual360()
592 )
593 );
594 }
595 int swap_tenors[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 25, 30};
596 double swap_quotes[] = {0.011907,
597 0.01699,
598 0.021198,
599 0.02444,
600 0.026937,
601 0.028967,
602 0.030504,
603 0.031719,
604 0.03279,
605 0.034535,
606 0.036217,
607 0.036981,
608 0.037246,
609 0.037605};
610
611 ext::shared_ptr<IborIndex> isda_ibor = ext::make_shared<IborIndex>(
612 args: "IsdaIbor", args: 3 * Months, args: 2, args: USDCurrency(), args: WeekendsOnly(),
613 args: ModifiedFollowing, args: false, args: Actual360());
614 for(size_t i = 0; i < sizeof(swap_tenors) / sizeof(int); i++) {
615 isdaRateHelpers.push_back(x: ext::make_shared<SwapRateHelper>(
616 args&: swap_quotes[i], args: swap_tenors[i] * Years,
617 args: WeekendsOnly(),
618 args: Semiannual,
619 args: ModifiedFollowing, args: Thirty360(Thirty360::BondBasis), args&: isda_ibor
620 )
621 );
622 }
623
624 RelinkableHandle<YieldTermStructure> discountCurve;
625 discountCurve.linkTo(
626 h: ext::make_shared<PiecewiseYieldCurve<Discount, LogLinear> >(
627 args: 0, args: WeekendsOnly(), args&: isdaRateHelpers, args: Actual365Fixed())
628 );
629
630
631 RelinkableHandle<DefaultProbabilityTermStructure> probabilityCurve;
632 Date termDates[] = {Date(20, June, 2010),
633 Date(20, June, 2011),
634 Date(20, June, 2012),
635 Date(20, June, 2016),
636 Date(20, June, 2019)};
637 Rate spreads[] = {0.001, 0.1};
638 Rate recoveries[] = {0.2, 0.4};
639
640 double markitValues[] = {
641 -97798.29358, //0.001
642 -97776.11889, //0.001
643 914971.5977, //0.1
644 894985.6298, //0.1
645 -186921.3594, //0.001
646 -186839.8148, //0.001
647 1646623.672, //0.1
648 1579803.626, //0.1
649 -274298.9203,
650 -274122.4725,
651 2279730.93,
652 2147972.527,
653 -592420.2297,
654 -591571.2294,
655 3993550.206,
656 3545843.418,
657 -797501.1422,
658 -795915.9787,
659 4702034.688,
660 4042340.999
661 };
662
663 /* When using indexes coupons, the risk-free curve is a bit off.
664 We might skip the tests altogether and rely on running them
665 with indexed coupons disabled, but leaving them can be useful anyway. */
666 Real tolerance = usingAtParCoupons ? 1.0e-6 : 1.0e-3;
667
668 size_t l = 0;
669
670 for (auto termDate : termDates) {
671 for (Real spread : spreads) {
672 for (Real& recovery : recoveries) {
673
674 ext::shared_ptr<CreditDefaultSwap> quotedTrade =
675 MakeCreditDefaultSwap(termDate, spread).withNominal(10000000.);
676
677 Rate h = quotedTrade->impliedHazardRate(targetNPV: 0., discountCurve, dayCounter: Actual365Fixed(),
678 recoveryRate: recovery, accuracy: 1e-10, model: CreditDefaultSwap::ISDA);
679
680 probabilityCurve.linkTo(
681 h: ext::make_shared<FlatHazardRate>(args: 0, args: WeekendsOnly(), args&: h, args: Actual365Fixed()));
682
683 ext::shared_ptr<IsdaCdsEngine> engine = ext::make_shared<IsdaCdsEngine>(
684 args&: probabilityCurve, args&: recovery, args&: discountCurve, args: ext::nullopt, args: IsdaCdsEngine::Taylor,
685 args: IsdaCdsEngine::HalfDayBias, args: IsdaCdsEngine::Piecewise);
686
687 ext::shared_ptr<CreditDefaultSwap> conventionalTrade =
688 MakeCreditDefaultSwap(termDate, 0.01)
689 .withNominal(10000000.)
690 .withPricingEngine(engine);
691
692 QL_CHECK_CLOSE(conventionalTrade->notional() * conventionalTrade->fairUpfront(),
693 markitValues[l], tolerance);
694
695 // Now testing that with the calculated fair-upfront, both Buyer and Seller sides
696 // price close to zero
697 ext::shared_ptr<CreditDefaultSwap> conventionalTradeBuy =
698 MakeCreditDefaultSwap(termDate, 0.01)
699 .withNominal(10000000.)
700 .withUpfrontRate(conventionalTrade->fairUpfront())
701 .withSide(Protection::Buyer)
702 .withPricingEngine(engine);
703
704 QL_CHECK_SMALL(conventionalTradeBuy->NPV(), tolerance);
705
706 ext::shared_ptr<CreditDefaultSwap> conventionalTradeSell =
707 MakeCreditDefaultSwap(termDate, 0.01)
708 .withNominal(10000000.)
709 .withUpfrontRate(conventionalTrade->fairUpfront())
710 .withSide(Protection::Seller)
711 .withPricingEngine(engine);
712
713 QL_CHECK_SMALL(conventionalTradeSell->NPV(), tolerance);
714
715 l++;
716 }
717 }
718 }
719}
720
721void CreditDefaultSwapTest::testAccrualRebateAmounts() {
722
723 BOOST_TEST_MESSAGE("Testing accrual rebate amounts on credit default swaps...");
724
725 // The accrual values are taken from various test results on the ISDA CDS model website
726 // https://www.cdsmodel.com/cdsmodel/documentation.html.
727
728 // Inputs
729 Real notional = 10000000;
730 Real spread = 0.0100;
731 Date maturity(20, Jun, 2014);
732
733 // key is trade date and value is expected accrual
734 typedef map<Date, Real> InputData;
735 InputData inputs = {
736 {Date(18, Mar, 2009), 24166.67},
737 {Date(19, Mar, 2009), 0.00},
738 {Date(20, Mar, 2009), 277.78},
739 {Date(23, Mar, 2009), 1111.11},
740 {Date(19, Jun, 2009), 25555.56},
741 {Date(20, Jun, 2009), 25833.33},
742 {Date(21, Jun, 2009), 0.00},
743 {Date(22, Jun, 2009), 277.78},
744 {Date(18, Jun, 2014), 25277.78},
745 {Date(19, Jun, 2014), 25555.56}
746 };
747
748 for (auto& input: inputs) {
749 Settings::instance().evaluationDate() = input.first;
750 CreditDefaultSwap cds = MakeCreditDefaultSwap(maturity, spread)
751 .withNominal(notional);
752 QL_CHECK_SMALL(input.second - cds.accrualRebate()->amount(), 0.01);
753 }
754}
755
756void CreditDefaultSwapTest::testIsdaCalculatorReconcileSingleQuote ()
757{
758 BOOST_TEST_MESSAGE(
759 "Testing ISDA engine calculations for a single credit-default swap record (reconciliation)...");
760
761 Date tradeDate(26, July, 2021);
762 Settings::instance().evaluationDate() = tradeDate;
763
764 //build an ISDA compliant yield curve
765 //data comes from Markit published rates
766 std::vector<ext::shared_ptr<RateHelper> > isdaRateHelpers;
767 int dep_tenors[] = {1, 3, 6, 12};
768 double dep_quotes[] = {-0.0056,-0.005440,-0.005190,-0.004930};
769
770 for(size_t i = 0; i < sizeof(dep_tenors) / sizeof(int); i++) {
771 isdaRateHelpers.push_back(x: ext::make_shared<DepositRateHelper>(
772 args&: dep_quotes[i], args: dep_tenors[i] * Months, args: 2,
773 args: WeekendsOnly(), args: ModifiedFollowing,
774 args: false, args: Actual360()
775 )
776 );
777 }
778 int swap_tenors[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 30};
779 double swap_quotes[] = {-0.004820,
780 -0.004420,
781 -0.003990,
782 -0.003520,
783 -0.002970,
784 -0.002370,
785 -0.001760,
786 -0.001140,
787 -0.000540,
788 0.000570,
789 0.001880,
790 0.002940,
791 0.002820};
792
793 ext::shared_ptr<IborIndex> isda_ibor = ext::make_shared<IborIndex>(
794 args: "IsdaIbor", args: 6 * Months, args: 2, args: EURCurrency(), args: WeekendsOnly(),
795 args: ModifiedFollowing, args: false, args: Actual360());
796 for(size_t i = 0; i < sizeof(swap_tenors) / sizeof(int); i++) {
797 isdaRateHelpers.push_back(x: ext::make_shared<SwapRateHelper>(
798 args&: swap_quotes[i], args: swap_tenors[i] * Years,
799 args: WeekendsOnly(),
800 args: Annual,
801 args: ModifiedFollowing, args: Thirty360(Thirty360::BondBasis), args&: isda_ibor
802 )
803 );
804 }
805
806 RelinkableHandle<YieldTermStructure> discountCurve;
807 discountCurve.linkTo(
808 h: ext::make_shared<PiecewiseYieldCurve<Discount, LogLinear> >(
809 args: 0, args: WeekendsOnly(), args&: isdaRateHelpers, args: Actual365Fixed())
810 );
811
812 RelinkableHandle<DefaultProbabilityTermStructure> probabilityCurve;
813 Date instrumentMaturity = Date(20, June, 2026);
814 Rate coupon = 0.01, conventionalSpread = 0.006713, recovery = 0.4;
815 double nominal = 1e6, markitValue = -16070.7, expected_accrual = 1000, tolerance = 1.0e-3;
816
817 ext::shared_ptr<CreditDefaultSwap> quotedTrade =
818 MakeCreditDefaultSwap(instrumentMaturity, conventionalSpread).withNominal(nominal);
819
820 Rate h = quotedTrade->impliedHazardRate(targetNPV: 0., discountCurve, dayCounter: Actual365Fixed(),
821 recoveryRate: recovery, accuracy: 1e-10, model: CreditDefaultSwap::ISDA);
822
823 probabilityCurve.linkTo(
824 h: ext::make_shared<FlatHazardRate>(args: 0, args: WeekendsOnly(), args&: h, args: Actual365Fixed()));
825
826 ext::shared_ptr<IsdaCdsEngine> engine = ext::make_shared<IsdaCdsEngine>(
827 args&: probabilityCurve, args&: recovery, args&: discountCurve, args: ext::nullopt, args: IsdaCdsEngine::Taylor,
828 args: IsdaCdsEngine::HalfDayBias, args: IsdaCdsEngine::Piecewise);
829
830 ext::shared_ptr<CreditDefaultSwap> conventionalTrade =
831 MakeCreditDefaultSwap(instrumentMaturity, coupon)
832 .withNominal(nominal)
833 .withPricingEngine(engine);
834
835
836 Real npv = conventionalTrade->NPV();
837 Real calculated_upfront = conventionalTrade->notional() * conventionalTrade->fairUpfront();
838 Real df = calculated_upfront / npv; // to take into account of the discount to cash settlement
839 Real derived_accrual =
840 df * (npv -
841 conventionalTrade->defaultLegNPV() -
842 conventionalTrade->couponLegNPV());
843
844 Real calculated_accrual = conventionalTrade->accrualRebate()->amount();
845
846 auto settlement_date = conventionalTrade->accrualRebate()->date();
847
848 QL_CHECK_CLOSE(npv, markitValue, tolerance);
849
850 QL_CHECK_CLOSE(calculated_upfront, df * markitValue, tolerance);
851
852 QL_CHECK_CLOSE(derived_accrual, expected_accrual, tolerance);
853
854 QL_CHECK_CLOSE(calculated_accrual, expected_accrual, tolerance);
855
856 BOOST_CHECK_EQUAL(settlement_date, WeekendsOnly().advance(tradeDate,3, TimeUnit::Days));
857
858}
859
860void CreditDefaultSwapTest::testIsdaCalculatorReconcileSingleWithIssueDateInThePast ()
861{
862 BOOST_TEST_MESSAGE(
863 "Testing ISDA engine calculations for a single credit-default swap with issue date in the past...");
864
865 Date valueDate(26, July, 2021);
866 Settings::instance().evaluationDate() = valueDate;
867
868 //this is not IMM date but the settlement date is in the past so the accrual rebate
869 //should not be part of the NPV
870 Date tradeDate(20, July, 2019);
871
872 //build an ISDA compliant yield curve
873 //data comes from Markit published rates
874 std::vector<ext::shared_ptr<RateHelper> > isdaRateHelpers;
875 int dep_tenors[] = {1, 3, 6, 12};
876 double dep_quotes[] = {-0.0056,-0.005440,-0.005190,-0.004930};
877
878 for(size_t i = 0; i < sizeof(dep_tenors) / sizeof(int); i++) {
879 isdaRateHelpers.push_back(x: ext::make_shared<DepositRateHelper>(
880 args&: dep_quotes[i], args: dep_tenors[i] * Months, args: 2,
881 args: WeekendsOnly(), args: ModifiedFollowing,
882 args: false, args: Actual360()
883 )
884 );
885 }
886 int swap_tenors[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 30};
887 double swap_quotes[] = {-0.004820,
888 -0.004420,
889 -0.003990,
890 -0.003520,
891 -0.002970,
892 -0.002370,
893 -0.001760,
894 -0.001140,
895 -0.000540,
896 0.000570,
897 0.001880,
898 0.002940,
899 0.002820};
900
901 ext::shared_ptr<IborIndex> isda_ibor = ext::make_shared<IborIndex>(
902 args: "IsdaIbor", args: 6 * Months, args: 2, args: EURCurrency(), args: WeekendsOnly(),
903 args: ModifiedFollowing, args: false, args: Actual360());
904 for(size_t i = 0; i < sizeof(swap_tenors) / sizeof(int); i++) {
905 isdaRateHelpers.push_back(x: ext::make_shared<SwapRateHelper>(
906 args&: swap_quotes[i], args: swap_tenors[i] * Years,
907 args: WeekendsOnly(),
908 args: Annual,
909 args: ModifiedFollowing, args: Thirty360(Thirty360::BondBasis), args&: isda_ibor
910 )
911 );
912 }
913
914 RelinkableHandle<YieldTermStructure> discountCurve;
915 discountCurve.linkTo(
916 h: ext::make_shared<PiecewiseYieldCurve<Discount, LogLinear> >(
917 args: 0, args: WeekendsOnly(), args&: isdaRateHelpers, args: Actual365Fixed())
918 );
919
920 RelinkableHandle<DefaultProbabilityTermStructure> probabilityCurve;
921 Date instrumentMaturity = Date(20, June, 2026);
922 Rate coupon = 0.01, conventionalSpread = 0.006713, recovery = 0.4;
923
924 //because there is no accrual involved, the markit value is decreased as compared to the
925 //previous test (old_markit_value - old_accrual or -16070.7 - 1000)
926 double nominal = 1e6, markitValue = -17070.77, expected_accrual = 0, tolerance = 1.0e-3;
927
928 ext::shared_ptr<CreditDefaultSwap> quotedTrade =
929 MakeCreditDefaultSwap(instrumentMaturity, conventionalSpread)
930 .withNominal(nominal);
931
932 Rate h = quotedTrade->impliedHazardRate(targetNPV: 0., discountCurve, dayCounter: Actual365Fixed(),
933 recoveryRate: recovery, accuracy: 1e-10, model: CreditDefaultSwap::ISDA);
934
935 probabilityCurve.linkTo(
936 h: ext::make_shared<FlatHazardRate>(args: 0, args: WeekendsOnly(), args&: h, args: Actual365Fixed()));
937
938 ext::shared_ptr<IsdaCdsEngine> engine = ext::make_shared<IsdaCdsEngine>(
939 args&: probabilityCurve, args&: recovery, args&: discountCurve, args: ext::nullopt, args: IsdaCdsEngine::Taylor,
940 args: IsdaCdsEngine::HalfDayBias, args: IsdaCdsEngine::Piecewise);
941
942 ext::shared_ptr<CreditDefaultSwap> conventionalTrade =
943 MakeCreditDefaultSwap(instrumentMaturity, coupon)
944 .withNominal(nominal)
945 .withPricingEngine(engine)
946 .withTradeDate(tradeDate);
947
948
949 Real npv = conventionalTrade->NPV();
950 Real calculated_accrual = npv -
951 conventionalTrade->defaultLegNPV() -
952 conventionalTrade->couponLegNPV();
953
954 QL_CHECK_CLOSE(npv, markitValue, tolerance);
955
956 QL_CHECK_CLOSE(calculated_accrual, expected_accrual, tolerance);
957}
958
959test_suite* CreditDefaultSwapTest::suite() {
960 auto* suite = BOOST_TEST_SUITE("Credit-default swap tests");
961 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testCachedValue));
962 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testCachedMarketValue));
963 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testImpliedHazardRate));
964 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testFairSpread));
965 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testFairUpfront));
966 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testIsdaEngine));
967 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testAccrualRebateAmounts));
968 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testIsdaCalculatorReconcileSingleQuote));
969 suite->add(QUANTLIB_TEST_CASE(&CreditDefaultSwapTest::testIsdaCalculatorReconcileSingleWithIssueDateInThePast));
970 return suite;
971}
972

source code of quantlib/test-suite/creditdefaultswap.cpp

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