Coverage Report

Created: 2025-10-14 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/instruments/creditdefaultswap.cpp
Line
Count
Source
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
 <https://www.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
37
namespace 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
0
    : side_(side), notional_(notional), upfront_(ext::nullopt), runningSpread_(spread),
54
0
      settlesAccrual_(settlesAccrual), paysAtDefaultTime_(paysAtDefaultTime),
55
0
      claim_(std::move(claim)),
56
0
      protectionStart_(protectionStart == Date() ? schedule[0] : protectionStart),
57
0
      tradeDate_(tradeDate), cashSettlementDays_(cashSettlementDays) {
58
59
0
        init(schedule, convention, dayCounter, lastPeriodDayCounter, rebatesAccrual);
60
0
    }
Unexecuted instantiation: QuantLib::CreditDefaultSwap::CreditDefaultSwap(QuantLib::Protection::Side, double, double, QuantLib::Schedule const&, QuantLib::BusinessDayConvention, QuantLib::DayCounter const&, bool, bool, QuantLib::Date const&, boost::shared_ptr<QuantLib::Claim>, QuantLib::DayCounter const&, bool, QuantLib::Date const&, unsigned int)
Unexecuted instantiation: QuantLib::CreditDefaultSwap::CreditDefaultSwap(QuantLib::Protection::Side, double, double, QuantLib::Schedule const&, QuantLib::BusinessDayConvention, QuantLib::DayCounter const&, bool, bool, QuantLib::Date const&, boost::shared_ptr<QuantLib::Claim>, QuantLib::DayCounter const&, bool, QuantLib::Date const&, unsigned int)
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
0
    : side_(side), notional_(notional), upfront_(upfront), runningSpread_(runningSpread),
79
0
      settlesAccrual_(settlesAccrual), paysAtDefaultTime_(paysAtDefaultTime),
80
0
      claim_(std::move(claim)),
81
0
      protectionStart_(protectionStart == Date() ? schedule[0] : protectionStart),
82
0
      tradeDate_(tradeDate), cashSettlementDays_(cashSettlementDays) {
83
84
0
        init(schedule, convention, dayCounter, lastPeriodDayCounter, rebatesAccrual, upfrontDate);
85
0
    }
Unexecuted instantiation: QuantLib::CreditDefaultSwap::CreditDefaultSwap(QuantLib::Protection::Side, double, double, double, QuantLib::Schedule const&, QuantLib::BusinessDayConvention, QuantLib::DayCounter const&, bool, bool, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::Claim>, QuantLib::DayCounter const&, bool, QuantLib::Date const&, unsigned int)
Unexecuted instantiation: QuantLib::CreditDefaultSwap::CreditDefaultSwap(QuantLib::Protection::Side, double, double, double, QuantLib::Schedule const&, QuantLib::BusinessDayConvention, QuantLib::DayCounter const&, bool, bool, QuantLib::Date const&, QuantLib::Date const&, boost::shared_ptr<QuantLib::Claim>, QuantLib::DayCounter const&, bool, QuantLib::Date const&, unsigned int)
86
87
    void CreditDefaultSwap::init(const Schedule& schedule, BusinessDayConvention paymentConvention,
88
                                 const DayCounter& dayCounter, const DayCounter& lastPeriodDayCounter,
89
0
                                 bool rebatesAccrual, const Date& upfrontDate) {
90
91
0
        QL_REQUIRE(!schedule.empty(), "CreditDefaultSwap needs a non-empty schedule.");
92
93
0
        bool postBigBang = false;
94
0
        if (schedule.hasRule()) {
95
0
            DateGeneration::Rule rule = schedule.rule();
96
0
            postBigBang = rule == DateGeneration::CDS || rule == DateGeneration::CDS2015;
97
0
        }
98
99
0
        if (!postBigBang) {
100
0
            QL_REQUIRE(protectionStart_ <= schedule[0], "protection can not start after accrual");
101
0
        }
102
103
0
        leg_ = FixedRateLeg(schedule)
104
0
            .withNotionals(notional_)
105
0
            .withCouponRates(runningSpread_, dayCounter)
106
0
            .withPaymentAdjustment(paymentConvention)
107
0
            .withLastPeriodDayCounter(lastPeriodDayCounter);
108
109
        // Deduce the trade date if not given.
110
0
        if (tradeDate_ == Date()) {
111
0
            if (postBigBang) {
112
0
                tradeDate_ = protectionStart_;
113
0
            } else {
114
0
                tradeDate_ = protectionStart_ - 1;
115
0
            }
116
0
        }
117
118
        // Deduce the cash settlement date if not given.
119
0
        Date effectiveUpfrontDate = upfrontDate;
120
0
        if (effectiveUpfrontDate == Date()) {
121
0
            effectiveUpfrontDate = schedule.calendar().advance(tradeDate_,
122
0
                cashSettlementDays_, Days, paymentConvention);
123
0
        }
124
0
        QL_REQUIRE(effectiveUpfrontDate >= protectionStart_,
125
0
                   "The cash settlement date must not be before the protection start date.");
126
127
        // Create the upfront payment, if one is provided.
128
0
        Real upfrontAmount = 0.0;
129
0
        if (upfront_) // NOLINT(readability-implicit-bool-conversion)
130
0
            upfrontAmount = *upfront_ * notional_;
131
0
        upfrontPayment_ = ext::make_shared<SimpleCashFlow>(upfrontAmount, effectiveUpfrontDate);
132
133
        // Set the maturity date.
134
0
        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
0
        if (rebatesAccrual) {
139
140
0
            Real rebateAmount = 0.0;
141
0
            Date refDate = tradeDate_ + 1;
142
143
0
            if (tradeDate_ >= schedule.dates().front()) {
144
0
                for (Size i = 0; i < leg_.size(); ++i) {
145
0
                    const ext::shared_ptr<CashFlow>& cf = leg_[i];
146
0
                    if (refDate > cf->date()) {
147
                        // This coupon is in the past; check the next one
148
0
                        continue;
149
0
                    } 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
0
                        if (i < leg_.size() - 1)
153
0
                            rebateAmount = 0.0;
154
0
                        else {
155
                            // On last coupon
156
0
                            ext::shared_ptr<FixedRateCoupon> frc = ext::dynamic_pointer_cast<FixedRateCoupon>(cf);
157
0
                            rebateAmount = frc->amount();
158
0
                        }
159
0
                        break;
160
0
                    } 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
0
                        ext::shared_ptr<FixedRateCoupon> frc = ext::dynamic_pointer_cast<FixedRateCoupon>(cf);
164
0
                        rebateAmount = frc->accruedAmount(refDate);
165
0
                        break;
166
0
                    }
167
0
                }
168
0
            }
169
170
0
            accrualRebate_ = ext::make_shared<SimpleCashFlow>(rebateAmount, effectiveUpfrontDate);
171
0
        }
172
173
0
        if (!claim_)
174
0
            claim_ = ext::make_shared<FaceValueClaim>();
175
0
        registerWith(claim_);
176
0
    }
177
178
0
    Protection::Side CreditDefaultSwap::side() const {
179
0
        return side_;
180
0
    }
181
182
0
    Real CreditDefaultSwap::notional() const {
183
0
        return notional_;
184
0
    }
185
186
0
    Rate CreditDefaultSwap::runningSpread() const {
187
0
        return runningSpread_;
188
0
    }
189
190
0
    ext::optional<Rate> CreditDefaultSwap::upfront() const {
191
0
        return upfront_;
192
0
    }
193
194
0
    bool CreditDefaultSwap::settlesAccrual() const {
195
0
        return settlesAccrual_;
196
0
    }
197
198
0
    bool CreditDefaultSwap::paysAtDefaultTime() const {
199
0
        return paysAtDefaultTime_;
200
0
    }
201
202
0
    const Leg& CreditDefaultSwap::coupons() const {
203
0
        return leg_;
204
0
    }
205
206
207
0
    bool CreditDefaultSwap::isExpired() const {
208
0
        for (auto i = leg_.rbegin(); i != leg_.rend(); ++i) {
209
0
            if (!(*i)->hasOccurred())
210
0
                return false;
211
0
        }
212
0
        return true;
213
0
    }
214
215
0
    void CreditDefaultSwap::setupExpired() const {
216
0
        Instrument::setupExpired();
217
0
        fairSpread_ = fairUpfront_ = 0.0;
218
0
        couponLegBPS_ = upfrontBPS_ = 0.0;
219
0
        couponLegNPV_ = defaultLegNPV_ = upfrontNPV_ = 0.0;
220
0
    }
221
222
    void CreditDefaultSwap::setupArguments(
223
0
                                       PricingEngine::arguments* args) const {
224
0
        auto* arguments = dynamic_cast<CreditDefaultSwap::arguments*>(args);
225
0
        QL_REQUIRE(arguments != nullptr, "wrong argument type");
226
227
0
        arguments->side = side_;
228
0
        arguments->notional = notional_;
229
0
        arguments->leg = leg_;
230
0
        arguments->upfrontPayment = upfrontPayment_;
231
0
        arguments->accrualRebate = accrualRebate_;
232
0
        arguments->settlesAccrual = settlesAccrual_;
233
0
        arguments->paysAtDefaultTime = paysAtDefaultTime_;
234
0
        arguments->claim = claim_;
235
0
        arguments->upfront = upfront_;
236
0
        arguments->spread = runningSpread_;
237
0
        arguments->protectionStart = protectionStart_;
238
0
        arguments->maturity = maturity_;
239
0
    }
240
241
242
    void CreditDefaultSwap::fetchResults(
243
0
                                      const PricingEngine::results* r) const {
244
0
        Instrument::fetchResults(r);
245
246
0
        const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(r);
247
0
        QL_REQUIRE(results != nullptr, "wrong result type");
248
249
0
        fairSpread_ = results->fairSpread;
250
0
        fairUpfront_ = results->fairUpfront;
251
0
        couponLegBPS_ = results->couponLegBPS;
252
0
        couponLegNPV_ = results->couponLegNPV;
253
0
        defaultLegNPV_ = results->defaultLegNPV;
254
0
        upfrontNPV_ = results->upfrontNPV;
255
0
        upfrontBPS_ = results->upfrontBPS;
256
0
        accrualRebateNPV_ = results->accrualRebateNPV;
257
0
    }
258
259
0
    Rate CreditDefaultSwap::fairUpfront() const {
260
0
        calculate();
261
0
        QL_REQUIRE(fairUpfront_ != Null<Rate>(),
262
0
                   "fair upfront not available");
263
0
        return fairUpfront_;
264
0
    }
265
266
0
    Rate CreditDefaultSwap::fairSpread() const {
267
0
        calculate();
268
0
        QL_REQUIRE(fairSpread_ != Null<Rate>(),
269
0
                   "fair spread not available");
270
0
        return fairSpread_;
271
0
    }
272
273
0
    Real CreditDefaultSwap::couponLegBPS() const {
274
0
        calculate();
275
0
        QL_REQUIRE(couponLegBPS_ != Null<Rate>(),
276
0
                   "coupon-leg BPS not available");
277
0
        return couponLegBPS_;
278
0
    }
279
280
0
    Real CreditDefaultSwap::couponLegNPV() const {
281
0
        calculate();
282
0
        QL_REQUIRE(couponLegNPV_ != Null<Rate>(),
283
0
                   "coupon-leg NPV not available");
284
0
        return couponLegNPV_;
285
0
    }
286
287
0
    Real CreditDefaultSwap::defaultLegNPV() const {
288
0
        calculate();
289
0
        QL_REQUIRE(defaultLegNPV_ != Null<Rate>(),
290
0
                   "default-leg NPV not available");
291
0
        return defaultLegNPV_;
292
0
    }
293
294
0
    Real CreditDefaultSwap::upfrontNPV() const {
295
0
        calculate();
296
0
        QL_REQUIRE(upfrontNPV_ != Null<Real>(),
297
0
                   "upfront NPV not available");
298
0
        return upfrontNPV_;
299
0
    }
300
301
0
    Real CreditDefaultSwap::upfrontBPS() const {
302
0
        calculate();
303
0
        QL_REQUIRE(upfrontBPS_ != Null<Real>(),
304
0
                   "upfront BPS not available");
305
0
        return upfrontBPS_;
306
0
    }
307
308
0
    Real CreditDefaultSwap::accrualRebateNPV() const {
309
0
        calculate();
310
0
        QL_REQUIRE(accrualRebateNPV_ != Null<Real>(),
311
0
                   "accrual Rebate NPV not available");
312
0
        return accrualRebateNPV_;
313
0
    }
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
0
            : target_(target), quote_(quote),
324
0
              engine_(engine), results_(results) {}
325
326
0
            Real operator()(Real guess) const {
327
0
                quote_.setValue(guess);
328
0
                engine_.calculate();
329
0
                return results_->value - target_;
330
0
            }
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
0
                               PricingModel model) const {
347
348
0
        ext::shared_ptr<SimpleQuote> flatRate = ext::make_shared<SimpleQuote>(0.0);
349
350
0
        Handle<DefaultProbabilityTermStructure> probability =
351
0
            Handle<DefaultProbabilityTermStructure>(
352
0
                ext::make_shared<FlatHazardRate>(0, WeekendsOnly(),
353
0
                                                   Handle<Quote>(flatRate), dayCounter));
354
355
0
        ext::shared_ptr<PricingEngine> engine;
356
0
        switch (model) {
357
0
          case Midpoint:
358
0
            engine = ext::make_shared<MidPointCdsEngine>(
359
0
                probability, recoveryRate, discountCurve);
360
0
            break;
361
0
          case ISDA:
362
0
            engine = ext::make_shared<IsdaCdsEngine>(
363
0
                probability, recoveryRate, discountCurve,
364
0
                ext::nullopt,
365
0
                IsdaCdsEngine::Taylor,
366
0
                IsdaCdsEngine::HalfDayBias,
367
0
                IsdaCdsEngine::Piecewise);
368
0
            break;
369
0
          default:
370
0
            QL_FAIL("unknown CDS pricing model: " << model);
371
0
        }
372
373
0
        setupArguments(engine->getArguments());
374
0
        const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(engine->getResults());
375
376
0
        ObjectiveFunction f(targetNPV, *flatRate, *engine, results);
377
        //very close guess if targetNPV = 0.
378
0
        Rate guess = runningSpread_ / (1 - recoveryRate) * 365./360.;
379
0
        Real step = 0.1 * guess;
380
0
        return Brent().solve(f, accuracy, guess, step);
381
0
    }
382
383
    Rate CreditDefaultSwap::conventionalSpread(
384
                              Real conventionalRecovery,
385
                              const Handle<YieldTermStructure>& discountCurve,
386
                              const DayCounter& dayCounter,
387
0
                              PricingModel model) const {
388
389
0
        ext::shared_ptr<SimpleQuote> flatRate = ext::make_shared<SimpleQuote>(0.0);
390
391
0
        Handle<DefaultProbabilityTermStructure> probability =
392
0
            Handle<DefaultProbabilityTermStructure>(
393
0
                ext::make_shared<FlatHazardRate>(0, WeekendsOnly(),
394
0
                                                   Handle<Quote>(flatRate), dayCounter));
395
396
0
        ext::shared_ptr<PricingEngine> engine;
397
0
        switch (model) {
398
0
          case Midpoint:
399
0
            engine = ext::make_shared<MidPointCdsEngine>(
400
0
                probability, conventionalRecovery, discountCurve);
401
0
            break;
402
0
          case ISDA:
403
0
            engine = ext::make_shared<IsdaCdsEngine>(
404
0
                probability, conventionalRecovery, discountCurve,
405
0
                ext::nullopt,
406
0
                IsdaCdsEngine::Taylor,
407
0
                IsdaCdsEngine::HalfDayBias,
408
0
                IsdaCdsEngine::Piecewise);
409
0
            break;
410
0
          default:
411
0
            QL_FAIL("unknown CDS pricing model: " << model);
412
0
        }
413
414
0
        setupArguments(engine->getArguments());
415
0
        const auto* results = dynamic_cast<const CreditDefaultSwap::results*>(engine->getResults());
416
417
0
        ObjectiveFunction f(0., *flatRate, *engine, results);
418
0
        Rate guess = runningSpread_ / (1 - conventionalRecovery) * 365./360.;
419
0
        Real step = guess * 0.1;
420
421
0
        Brent().solve(f, 1e-9, guess, step);
422
0
        return results->fairSpread;
423
0
    }
424
425
426
0
    const Date& CreditDefaultSwap::protectionStartDate() const {
427
0
        return protectionStart_;
428
0
    }
429
430
0
    const Date& CreditDefaultSwap::protectionEndDate() const {
431
0
        return ext::dynamic_pointer_cast<Coupon>(leg_.back())
432
0
            ->accrualEndDate();
433
0
    }
434
435
0
    const ext::shared_ptr<SimpleCashFlow>& CreditDefaultSwap::upfrontPayment() const {
436
0
        return upfrontPayment_;
437
0
    }
438
439
0
    const ext::shared_ptr<SimpleCashFlow>& CreditDefaultSwap::accrualRebate() const {
440
0
        return accrualRebate_;
441
0
    }
442
443
0
    const Date& CreditDefaultSwap::tradeDate() const {
444
0
        return tradeDate_;
445
0
    }
446
447
0
    Natural CreditDefaultSwap::cashSettlementDays() const {
448
0
        return cashSettlementDays_;
449
0
    }
450
451
    CreditDefaultSwap::arguments::arguments()
452
0
    : side(Protection::Side(-1)), notional(Null<Real>()),
453
0
      spread(Null<Rate>()) {}
Unexecuted instantiation: QuantLib::CreditDefaultSwap::arguments::arguments()
Unexecuted instantiation: QuantLib::CreditDefaultSwap::arguments::arguments()
454
455
0
    void CreditDefaultSwap::arguments::validate() const {
456
0
        QL_REQUIRE(side != Protection::Side(-1), "side not set");
457
0
        QL_REQUIRE(notional != Null<Real>(), "notional not set");
458
0
        QL_REQUIRE(notional != 0.0, "null notional set");
459
0
        QL_REQUIRE(spread != Null<Rate>(), "spread not set");
460
0
        QL_REQUIRE(!leg.empty(), "coupons not set");
461
0
        QL_REQUIRE(upfrontPayment, "upfront payment not set");
462
0
        QL_REQUIRE(claim, "claim not set");
463
0
        QL_REQUIRE(protectionStart != Date(), "protection start date not set");
464
0
        QL_REQUIRE(maturity != Date(), "maturity date not set");
465
0
    }
466
467
0
    void CreditDefaultSwap::results::reset() {
468
0
        Instrument::results::reset();
469
0
        fairSpread = Null<Rate>();
470
0
        fairUpfront = Null<Rate>();
471
0
        couponLegBPS = Null<Real>();
472
0
        couponLegNPV = Null<Real>();
473
0
        defaultLegNPV = Null<Real>();
474
0
        upfrontBPS = Null<Real>();
475
0
        upfrontNPV = Null<Real>();
476
0
        accrualRebateNPV = Null<Real>();
477
0
    }
478
479
0
    Date cdsMaturity(const Date& tradeDate, const Period& tenor, DateGeneration::Rule rule) {
480
481
0
        QL_REQUIRE(rule == DateGeneration::CDS2015 || rule == DateGeneration::CDS || rule == DateGeneration::OldCDS,
482
0
            "cdsMaturity should only be used with date generation rule CDS2015, CDS or OldCDS");
483
484
0
        QL_REQUIRE(tenor.units() == Years || (tenor.units() == Months && tenor.length() % 3 == 0),
485
0
            "cdsMaturity expects a tenor that is a multiple of 3 months.");
486
487
0
        if (rule == DateGeneration::OldCDS) {
488
0
            QL_REQUIRE(tenor != 0 * Months, "A tenor of 0M is not supported for OldCDS.");
489
0
        }
490
491
0
        Date anchorDate = previousTwentieth(tradeDate, rule);
492
0
        if (rule == DateGeneration::CDS2015 && (anchorDate == Date(20, Dec, anchorDate.year()) ||
493
0
            anchorDate == Date(20, Jun, anchorDate.year()))) {
494
0
            if (tenor.length() == 0) {
495
0
                return Date();
496
0
            } else {
497
0
                anchorDate -= 3 * Months;
498
0
            }
499
0
        }
500
501
0
        Date maturity = anchorDate + tenor + 3 * Months;
502
0
        QL_REQUIRE(maturity > tradeDate, "error calculating CDS maturity. Tenor is " << tenor << ", trade date is " <<
503
0
            io::iso_date(tradeDate) << " generating a maturity of " << io::iso_date(maturity) << " <= trade date.");
504
505
0
        return maturity;
506
0
    }
507
508
}