Coverage Report

Created: 2025-08-11 06:28

/src/quantlib/ql/time/schedule.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2006, 2007, 2008, 2010, 2011, 2015 Ferdinando Ametrano
5
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
6
 Copyright (C) 2009, 2012 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/optional.hpp>
23
#include <ql/settings.hpp>
24
#include <ql/time/imm.hpp>
25
#include <ql/time/schedule.hpp>
26
#include <algorithm>
27
#include <utility>
28
29
namespace QuantLib {
30
31
    namespace {
32
33
0
        Date nextTwentieth(const Date& d, DateGeneration::Rule rule) {
34
0
            Date result = Date(20, d.month(), d.year());
35
0
            if (result < d)
36
0
                result += 1*Months;
37
0
            if (rule == DateGeneration::TwentiethIMM ||
38
0
                rule == DateGeneration::OldCDS ||
39
0
                rule == DateGeneration::CDS ||
40
0
                rule == DateGeneration::CDS2015) {
41
0
                Month m = result.month();
42
0
                if (m % 3 != 0) { // not a main IMM nmonth
43
0
                    Integer skip = 3 - m%3;
44
0
                    result += skip*Months;
45
0
                }
46
0
            }
47
0
            return result;
48
0
        }
49
50
80.0k
        bool allowsEndOfMonth(const Period& tenor) {
51
80.0k
            return (tenor.units() == Months || tenor.units() == Years)
52
80.0k
                && tenor >= 1*Months;
53
80.0k
        }
54
55
    }
56
57
58
    Schedule::Schedule(const std::vector<Date>& dates,
59
                       Calendar calendar,
60
                       BusinessDayConvention convention,
61
                       const ext::optional<BusinessDayConvention>& terminationDateConvention,
62
                       const ext::optional<Period>& tenor,
63
                       const ext::optional<DateGeneration::Rule>& rule,
64
                       const ext::optional<bool>& endOfMonth,
65
                       std::vector<bool> isRegular)
66
0
    : tenor_(tenor), calendar_(std::move(calendar)), convention_(convention),
67
0
      terminationDateConvention_(terminationDateConvention), rule_(rule), dates_(dates),
68
0
      isRegular_(std::move(isRegular)) {
69
70
0
        if (tenor && !allowsEndOfMonth(*tenor))
71
0
            endOfMonth_ = false;
72
0
        else
73
0
            endOfMonth_ = endOfMonth;
74
75
0
        QL_REQUIRE(isRegular_.empty() || isRegular_.size() == dates.size() - 1,
76
0
                   "isRegular size (" << isRegular_.size()
77
0
                                      << ") must be zero or equal to the number of dates minus 1 ("
78
0
                                      << dates.size() - 1 << ")");
79
0
    }
80
81
    Schedule::Schedule(Date effectiveDate,
82
                       const Date& terminationDate,
83
                       const Period& tenor,
84
                       Calendar cal,
85
                       BusinessDayConvention convention,
86
                       BusinessDayConvention terminationDateConvention,
87
                       DateGeneration::Rule rule,
88
                       bool endOfMonth,
89
                       const Date& first,
90
                       const Date& nextToLast)
91
80.0k
    : tenor_(tenor), calendar_(std::move(cal)), convention_(convention),
92
80.0k
      terminationDateConvention_(terminationDateConvention), rule_(rule),
93
80.0k
      endOfMonth_(allowsEndOfMonth(tenor) ? endOfMonth : false),
94
80.0k
      firstDate_(first == effectiveDate ? Date() : first),
95
80.0k
      nextToLastDate_(nextToLast == terminationDate ? Date() : nextToLast) {
96
        // sanity checks
97
80.0k
        QL_REQUIRE(terminationDate != Date(), "null termination date");
98
99
        // in many cases (e.g. non-expired bonds) the effective date is not
100
        // really necessary. In these cases a decent placeholder is enough
101
80.0k
        if (effectiveDate==Date() && first==Date()
102
80.0k
                                  && rule==DateGeneration::Backward) {
103
0
            Date evalDate = Settings::instance().evaluationDate();
104
0
            QL_REQUIRE(evalDate < terminationDate, "null effective date");
105
0
            Natural y;
106
0
            if (nextToLast != Date()) {
107
0
                y = (nextToLast - evalDate)/366 + 1;
108
0
                effectiveDate = nextToLast - y*Years;
109
0
            } else {
110
0
                y = (terminationDate - evalDate)/366 + 1;
111
0
                effectiveDate = terminationDate - y*Years;
112
0
            }
113
0
        } else
114
80.0k
            QL_REQUIRE(effectiveDate != Date(), "null effective date");
115
116
80.0k
        QL_REQUIRE(effectiveDate < terminationDate,
117
80.0k
                   "effective date (" << effectiveDate
118
80.0k
                   << ") later than or equal to termination date ("
119
80.0k
                   << terminationDate << ")");
120
121
80.0k
        if (tenor.length()==0)
122
0
            rule_ = DateGeneration::Zero;
123
80.0k
        else
124
80.0k
            QL_REQUIRE(tenor.length()>0,
125
80.0k
                       "non positive tenor (" << tenor << ") not allowed");
126
127
80.0k
        if (firstDate_ != Date()) {
128
0
            switch (*rule_) {
129
0
              case DateGeneration::Backward:
130
0
              case DateGeneration::Forward:
131
0
                QL_REQUIRE(firstDate_ > effectiveDate &&
132
0
                           firstDate_ <= terminationDate,
133
0
                           "first date (" << firstDate_ <<
134
0
                           ") out of effective-termination date range (" <<
135
0
                           effectiveDate << ", " << terminationDate << "]");
136
                // we should ensure that the above condition is still
137
                // verified after adjustment
138
0
                break;
139
0
              case DateGeneration::ThirdWednesday:
140
0
                  QL_REQUIRE(IMM::isIMMdate(firstDate_, false),
141
0
                             "first date (" << firstDate_ <<
142
0
                             ") is not an IMM date");
143
0
                break;
144
0
              case DateGeneration::Zero:
145
0
              case DateGeneration::Twentieth:
146
0
              case DateGeneration::TwentiethIMM:
147
0
              case DateGeneration::OldCDS:
148
0
              case DateGeneration::CDS:
149
0
              case DateGeneration::CDS2015:
150
0
                QL_FAIL("first date incompatible with " << *rule_ <<
151
0
                        " date generation rule");
152
0
              default:
153
0
                QL_FAIL("unknown rule (" << Integer(*rule_) << ")");
154
0
            }
155
0
        }
156
80.0k
        if (nextToLastDate_ != Date()) {
157
0
            switch (*rule_) {
158
0
              case DateGeneration::Backward:
159
0
              case DateGeneration::Forward:
160
0
                QL_REQUIRE(nextToLastDate_ >= effectiveDate &&
161
0
                           nextToLastDate_ < terminationDate,
162
0
                           "next to last date (" << nextToLastDate_ <<
163
0
                           ") out of effective-termination date range [" <<
164
0
                           effectiveDate << ", " << terminationDate << ")");
165
                // we should ensure that the above condition is still
166
                // verified after adjustment
167
0
                break;
168
0
              case DateGeneration::ThirdWednesday:
169
0
                QL_REQUIRE(IMM::isIMMdate(nextToLastDate_, false),
170
0
                           "next-to-last date (" << nextToLastDate_ <<
171
0
                           ") is not an IMM date");
172
0
                break;
173
0
              case DateGeneration::Zero:
174
0
              case DateGeneration::Twentieth:
175
0
              case DateGeneration::TwentiethIMM:
176
0
              case DateGeneration::OldCDS:
177
0
              case DateGeneration::CDS:
178
0
              case DateGeneration::CDS2015:
179
0
                QL_FAIL("next to last date incompatible with " << *rule_ <<
180
0
                        " date generation rule");
181
0
              default:
182
0
                QL_FAIL("unknown rule (" << Integer(*rule_) << ")");
183
0
            }
184
0
        }
185
186
187
        // calendar needed for endOfMonth adjustment
188
80.0k
        Calendar nullCalendar = NullCalendar();
189
80.0k
        Integer periods = 1;
190
80.0k
        Date seed, exitDate;
191
80.0k
        switch (*rule_) {
192
193
0
          case DateGeneration::Zero:
194
0
            tenor_ = 0*Years;
195
0
            dates_.push_back(effectiveDate);
196
0
            dates_.push_back(terminationDate);
197
0
            isRegular_.push_back(true);
198
0
            break;
199
200
80.0k
          case DateGeneration::Backward:
201
202
80.0k
            dates_.push_back(terminationDate);
203
204
80.0k
            seed = terminationDate;
205
80.0k
            if (nextToLastDate_ != Date()) {
206
0
                dates_.push_back(nextToLastDate_);
207
0
                Date temp = nullCalendar.advance(seed,
208
0
                    -periods*(*tenor_), convention, *endOfMonth_);
209
0
                isRegular_.push_back(temp == nextToLastDate_);
210
0
                seed = nextToLastDate_;
211
0
            }
212
213
80.0k
            exitDate = effectiveDate;
214
80.0k
            if (firstDate_ != Date())
215
0
                exitDate = firstDate_;
216
217
28.8M
            for (;;) {
218
28.8M
                Date temp = nullCalendar.advance(seed,
219
28.8M
                    -periods*(*tenor_), convention, *endOfMonth_);
220
28.8M
                if (temp < exitDate) {
221
80.0k
                    if (firstDate_ != Date() &&
222
80.0k
                        (calendar_.adjust(dates_.back(),convention)!=
223
0
                         calendar_.adjust(firstDate_,convention))) {
224
0
                        dates_.push_back(firstDate_);
225
0
                        isRegular_.push_back(false);
226
0
                    }
227
80.0k
                    break;
228
28.8M
                } else {
229
                    // skip dates that would result in duplicates
230
                    // after adjustment
231
28.8M
                    if (calendar_.adjust(dates_.back(),convention)!=
232
28.8M
                        calendar_.adjust(temp,convention)) {
233
28.8M
                        dates_.push_back(temp);
234
28.8M
                        isRegular_.push_back(true);
235
28.8M
                    }
236
28.8M
                    ++periods;
237
28.8M
                }
238
28.8M
            }
239
240
80.0k
            if (calendar_.adjust(dates_.back(),convention)!=
241
80.0k
                calendar_.adjust(effectiveDate,convention)) {
242
0
                dates_.push_back(effectiveDate);
243
0
                isRegular_.push_back(false);
244
0
            }
245
80.0k
      std::reverse(dates_.begin(), dates_.end());
246
80.0k
      std::reverse(isRegular_.begin(), isRegular_.end());
247
80.0k
            break;
248
249
0
          case DateGeneration::Twentieth:
250
0
          case DateGeneration::TwentiethIMM:
251
0
          case DateGeneration::ThirdWednesday:
252
0
          case DateGeneration::ThirdWednesdayInclusive:
253
0
          case DateGeneration::OldCDS:
254
0
          case DateGeneration::CDS:
255
0
          case DateGeneration::CDS2015:
256
0
            QL_REQUIRE(!*endOfMonth_,
257
0
                       "endOfMonth convention incompatible with " << *rule_ <<
258
0
                       " date generation rule");
259
0
            [[fallthrough]];
260
0
          case DateGeneration::Forward:
261
262
0
            if (*rule_ == DateGeneration::CDS || *rule_ == DateGeneration::CDS2015) {
263
0
                Date prev20th = previousTwentieth(effectiveDate, *rule_);
264
0
                if (calendar_.adjust(prev20th, convention) > effectiveDate) {
265
0
                    dates_.push_back(prev20th - 3 * Months);
266
0
                    isRegular_.push_back(true);
267
0
                }
268
0
                dates_.push_back(prev20th);
269
0
            } else {
270
0
                dates_.push_back(effectiveDate);
271
0
            }
272
273
0
            seed = dates_.back();
274
275
0
            if (firstDate_!=Date()) {
276
0
                dates_.push_back(firstDate_);
277
0
                Date temp = nullCalendar.advance(seed, periods*(*tenor_),
278
0
                                                 convention, *endOfMonth_);
279
0
                if (temp!=firstDate_)
280
0
                    isRegular_.push_back(false);
281
0
                else
282
0
                    isRegular_.push_back(true);
283
0
                seed = firstDate_;
284
0
            } else if (*rule_ == DateGeneration::Twentieth ||
285
0
                       *rule_ == DateGeneration::TwentiethIMM ||
286
0
                       *rule_ == DateGeneration::OldCDS ||
287
0
                       *rule_ == DateGeneration::CDS ||
288
0
                       *rule_ == DateGeneration::CDS2015) {
289
0
                Date next20th = nextTwentieth(effectiveDate, *rule_);
290
0
                if (*rule_ == DateGeneration::OldCDS) {
291
                    // distance rule inforced in natural days
292
0
                    static const Date::serial_type stubDays = 30;
293
0
                    if (next20th - effectiveDate < stubDays) {
294
                        // +1 will skip this one and get the next
295
0
                        next20th = nextTwentieth(next20th + 1, *rule_);
296
0
                    }
297
0
                }
298
0
                if (next20th != effectiveDate) {
299
0
                    dates_.push_back(next20th);
300
0
                    isRegular_.push_back(*rule_ == DateGeneration::CDS || *rule_ == DateGeneration::CDS2015);
301
0
                    seed = next20th;
302
0
                }
303
0
            }
304
305
0
            exitDate = terminationDate;
306
0
            if (nextToLastDate_ != Date())
307
0
                exitDate = nextToLastDate_;
308
0
            for (;;) {
309
0
                Date temp = nullCalendar.advance(seed, periods*(*tenor_),
310
0
                                                 convention, *endOfMonth_);
311
0
                if (temp > exitDate) {
312
0
                    if (nextToLastDate_ != Date() &&
313
0
                        (calendar_.adjust(dates_.back(),convention)!=
314
0
                         calendar_.adjust(nextToLastDate_,convention))) {
315
0
                        dates_.push_back(nextToLastDate_);
316
0
                        isRegular_.push_back(false);
317
0
                    }
318
0
                    break;
319
0
                } else {
320
                    // skip dates that would result in duplicates
321
                    // after adjustment
322
0
                    if (calendar_.adjust(dates_.back(),convention)!=
323
0
                        calendar_.adjust(temp,convention)) {
324
0
                        dates_.push_back(temp);
325
0
                        isRegular_.push_back(true);
326
0
                    }
327
0
                    ++periods;
328
0
                }
329
0
            }
330
331
0
            if (calendar_.adjust(dates_.back(),terminationDateConvention)!=
332
0
                calendar_.adjust(terminationDate,terminationDateConvention)) {
333
0
                if (*rule_ == DateGeneration::Twentieth ||
334
0
                    *rule_ == DateGeneration::TwentiethIMM ||
335
0
                    *rule_ == DateGeneration::OldCDS ||
336
0
                    *rule_ == DateGeneration::CDS ||
337
0
                    *rule_ == DateGeneration::CDS2015) {
338
0
                    dates_.push_back(nextTwentieth(terminationDate, *rule_));
339
0
                    isRegular_.push_back(true);
340
0
                } else {
341
0
                    dates_.push_back(terminationDate);
342
0
                    isRegular_.push_back(false);
343
0
                }
344
0
            }
345
346
0
            break;
347
348
0
          default:
349
0
            QL_FAIL("unknown rule (" << Integer(*rule_) << ")");
350
80.0k
        }
351
352
        // adjustments
353
80.0k
        if (*rule_==DateGeneration::ThirdWednesday)
354
0
            for (Size i=1; i<dates_.size()-1; ++i)
355
0
                dates_[i] = Date::nthWeekday(3, Wednesday,
356
0
                                             dates_[i].month(),
357
0
                                             dates_[i].year());
358
80.0k
        else if (*rule_ == DateGeneration::ThirdWednesdayInclusive)
359
0
            for (auto& date : dates_)
360
0
                date = Date::nthWeekday(3, Wednesday, date.month(), date.year());
361
362
        // first date not adjusted for old CDS schedules
363
80.0k
        if (convention != Unadjusted && *rule_ != DateGeneration::OldCDS)
364
0
            dates_.front() = calendar_.adjust(dates_.front(), convention);
365
366
        // termination date is NOT adjusted as per ISDA
367
        // specifications, unless otherwise specified in the
368
        // confirmation of the deal or unless we're creating a CDS
369
        // schedule
370
80.0k
        if (terminationDateConvention != Unadjusted 
371
80.0k
            && *rule_ != DateGeneration::CDS 
372
80.0k
            && *rule_ != DateGeneration::CDS2015) {
373
0
            dates_.back() = calendar_.adjust(dates_.back(), 
374
0
                                             terminationDateConvention);
375
0
        }
376
377
80.0k
        if (*endOfMonth_ && calendar_.isEndOfMonth(seed)) {
378
            // adjust to end of month
379
0
            for (Size i=1; i<dates_.size()-1; ++i)
380
0
                dates_[i] = calendar_.adjust(Date::endOfMonth(dates_[i]), convention);
381
80.0k
        } else {
382
28.8M
            for (Size i=1; i<dates_.size()-1; ++i)
383
28.7M
                dates_[i] = calendar_.adjust(dates_[i], convention);
384
80.0k
        }
385
386
        // Final safety checks to remove extra next-to-last date, if
387
        // necessary.  It can happen to be equal or later than the end
388
        // date due to EOM adjustments (see the Schedule test suite
389
        // for an example).
390
80.0k
        if (dates_.size() >= 2 && dates_[dates_.size()-2] >= dates_.back()) {
391
            // there might be two dates only, then isRegular_ has size one
392
0
            if (isRegular_.size() >= 2) {
393
0
                isRegular_[isRegular_.size() - 2] =
394
0
                    (dates_[dates_.size() - 2] == dates_.back());
395
0
            }
396
0
            dates_[dates_.size() - 2] = dates_.back();
397
0
            dates_.pop_back();
398
0
            isRegular_.pop_back();
399
0
        }
400
80.0k
        if (dates_.size() >= 2 && dates_[1] <= dates_.front()) {
401
0
            isRegular_[1] =
402
0
                (dates_[1] == dates_.front());
403
0
            dates_[1] = dates_.front();
404
0
            dates_.erase(dates_.begin());
405
0
            isRegular_.erase(isRegular_.begin());
406
0
        }
407
408
80.0k
        QL_ENSURE(dates_.size()>1,
409
80.0k
            "degenerate single date (" << dates_[0] << ") schedule" <<
410
80.0k
            "\n seed date: " << seed <<
411
80.0k
            "\n exit date: " << exitDate <<
412
80.0k
            "\n effective date: " << effectiveDate <<
413
80.0k
            "\n first date: " << first <<
414
80.0k
            "\n next to last date: " << nextToLast <<
415
80.0k
            "\n termination date: " << terminationDate <<
416
80.0k
            "\n generation rule: " << *rule_ <<
417
80.0k
            "\n end of month: " << *endOfMonth_);
418
80.0k
    }
419
420
0
    Schedule Schedule::after(const Date& truncationDate) const {
421
0
        Schedule result = *this;
422
423
0
        QL_REQUIRE(truncationDate < result.dates_.back(),
424
0
            "truncation date " << truncationDate <<
425
0
            " must be before the last schedule date " <<
426
0
            result.dates_.back());
427
0
        if (truncationDate > result.dates_[0]) {
428
            // remove earlier dates
429
0
            while (result.dates_[0] < truncationDate) {
430
0
                result.dates_.erase(result.dates_.begin());
431
0
                if (!result.isRegular_.empty())
432
0
                    result.isRegular_.erase(result.isRegular_.begin());
433
0
            }
434
435
            // add truncationDate if missing
436
0
            if (truncationDate != result.dates_.front()) {
437
0
                result.dates_.insert(result.dates_.begin(), truncationDate);
438
0
                result.isRegular_.insert(result.isRegular_.begin(), false);
439
0
                result.terminationDateConvention_ = Unadjusted;
440
0
            }
441
0
            else {
442
0
                result.terminationDateConvention_ = convention_;
443
0
            }
444
445
0
            if (result.nextToLastDate_ <= truncationDate)
446
0
                result.nextToLastDate_ = Date();
447
0
            if (result.firstDate_ <= truncationDate)
448
0
                result.firstDate_ = Date();
449
0
        }
450
451
0
        return result;
452
0
    }
453
454
0
    Schedule Schedule::until(const Date& truncationDate) const {
455
0
        Schedule result = *this;
456
457
0
        QL_REQUIRE(truncationDate>result.dates_[0],
458
0
                   "truncation date " << truncationDate <<
459
0
                   " must be later than schedule first date " <<
460
0
                   result.dates_[0]);
461
0
        if (truncationDate<result.dates_.back()) {
462
            // remove later dates
463
0
            while (result.dates_.back()>truncationDate) {
464
0
                result.dates_.pop_back();
465
0
                if(!result.isRegular_.empty())
466
0
                    result.isRegular_.pop_back();
467
0
            }
468
469
            // add truncationDate if missing
470
0
            if (truncationDate!=result.dates_.back()) {
471
0
                result.dates_.push_back(truncationDate);
472
0
                result.isRegular_.push_back(false);
473
0
                result.terminationDateConvention_ = Unadjusted;
474
0
            } else {
475
0
                result.terminationDateConvention_ = convention_;
476
0
            }
477
478
0
            if (result.nextToLastDate_>=truncationDate)
479
0
                result.nextToLastDate_ = Date();
480
0
            if (result.firstDate_>=truncationDate)
481
0
                result.firstDate_ = Date();
482
0
        }
483
484
0
        return result;
485
0
    }
486
487
    std::vector<Date>::const_iterator
488
0
    Schedule::lower_bound(const Date& refDate) const {
489
0
        Date d = (refDate==Date() ?
490
0
                  Settings::instance().evaluationDate() :
491
0
                  refDate);
492
0
        return std::lower_bound(dates_.begin(), dates_.end(), d);
493
0
    }
494
495
0
    Date Schedule::nextDate(const Date& refDate) const {
496
0
        auto res = lower_bound(refDate);
497
0
        if (res!=dates_.end())
498
0
            return *res;
499
0
        else
500
0
            return {};
501
0
    }
502
503
0
    Date Schedule::previousDate(const Date& refDate) const {
504
0
        auto res = lower_bound(refDate);
505
0
        if (res!=dates_.begin())
506
0
            return *(--res);
507
0
        else
508
0
            return {};
509
0
    }
510
511
320k
    bool Schedule::hasIsRegular() const { return !isRegular_.empty(); }
512
513
160k
    bool Schedule::isRegular(Size i) const {
514
160k
        QL_REQUIRE(hasIsRegular(),
515
160k
                   "full interface (isRegular) not available");
516
160k
        QL_REQUIRE(i<=isRegular_.size() && i>0,
517
160k
                   "index (" << i << ") must be in [1, " <<
518
160k
                   isRegular_.size() <<"]");
519
160k
        return isRegular_[i-1];
520
160k
    }
521
522
0
    const std::vector<bool>& Schedule::isRegular() const {
523
0
        QL_REQUIRE(!isRegular_.empty(), "full interface (isRegular) not available");
524
0
        return isRegular_;
525
0
    }
526
527
0
    MakeSchedule& MakeSchedule::from(const Date& effectiveDate) {
528
0
        effectiveDate_ = effectiveDate;
529
0
        return *this;
530
0
    }
531
532
0
    MakeSchedule& MakeSchedule::to(const Date& terminationDate) {
533
0
        terminationDate_ = terminationDate;
534
0
        return *this;
535
0
    }
536
537
0
    MakeSchedule& MakeSchedule::withTenor(const Period& tenor) {
538
0
        tenor_ = tenor;
539
0
        return *this;
540
0
    }
541
542
0
    MakeSchedule& MakeSchedule::withFrequency(Frequency frequency) {
543
0
        tenor_ = Period(frequency);
544
0
        return *this;
545
0
    }
546
547
0
    MakeSchedule& MakeSchedule::withCalendar(const Calendar& calendar) {
548
0
        calendar_ = calendar;
549
0
        return *this;
550
0
    }
551
552
0
    MakeSchedule& MakeSchedule::withConvention(BusinessDayConvention conv) {
553
0
        convention_ = conv;
554
0
        return *this;
555
0
    }
556
557
    MakeSchedule& MakeSchedule::withTerminationDateConvention(
558
0
                                                BusinessDayConvention conv) {
559
0
        terminationDateConvention_ = conv;
560
0
        return *this;
561
0
    }
562
563
0
    MakeSchedule& MakeSchedule::withRule(DateGeneration::Rule r) {
564
0
        rule_ = r;
565
0
        return *this;
566
0
    }
567
568
0
    MakeSchedule& MakeSchedule::forwards() {
569
0
        rule_ = DateGeneration::Forward;
570
0
        return *this;
571
0
    }
572
573
0
    MakeSchedule& MakeSchedule::backwards() {
574
0
        rule_ = DateGeneration::Backward;
575
0
        return *this;
576
0
    }
577
578
0
    MakeSchedule& MakeSchedule::endOfMonth(bool flag) {
579
0
        endOfMonth_ = flag;
580
0
        return *this;
581
0
    }
582
583
0
    MakeSchedule& MakeSchedule::withFirstDate(const Date& d) {
584
0
        firstDate_ = d;
585
0
        return *this;
586
0
    }
587
588
0
    MakeSchedule& MakeSchedule::withNextToLastDate(const Date& d) {
589
0
        nextToLastDate_ = d;
590
0
        return *this;
591
0
    }
592
593
0
    MakeSchedule::operator Schedule() const {
594
        // check for mandatory arguments
595
0
        QL_REQUIRE(effectiveDate_ != Date(), "effective date not provided");
596
0
        QL_REQUIRE(terminationDate_ != Date(), "termination date not provided");
597
0
        QL_REQUIRE(tenor_, "tenor/frequency not provided");
598
599
        // set dynamic defaults:
600
0
        BusinessDayConvention convention;
601
        // if a convention was set, we use it.
602
0
        if (convention_) { // NOLINT(readability-implicit-bool-conversion)
603
0
            convention = *convention_;
604
0
        } else {
605
0
            if (!calendar_.empty()) {
606
                // ...if we set a calendar, we probably want it to be used;
607
0
                convention = Following;
608
0
            } else {
609
                // if not, we don't care.
610
0
                convention = Unadjusted;
611
0
            }
612
0
        }
613
614
0
        BusinessDayConvention terminationDateConvention;
615
        // if set explicitly, we use it;
616
0
        if (terminationDateConvention_) { // NOLINT(readability-implicit-bool-conversion)
617
0
            terminationDateConvention = *terminationDateConvention_;
618
0
        } else {
619
            // Unadjusted as per ISDA specification
620
0
            terminationDateConvention = convention;
621
0
        }
622
623
0
        Calendar calendar = calendar_;
624
        // if no calendar was set...
625
0
        if (calendar.empty()) {
626
            // ...we use a null one.
627
0
            calendar = NullCalendar();
628
0
        }
629
630
0
        return Schedule(effectiveDate_, terminationDate_, *tenor_, calendar,
631
0
                        convention, terminationDateConvention,
632
0
                        rule_, endOfMonth_, firstDate_, nextToLastDate_);
633
0
    }
634
635
0
    Date previousTwentieth(const Date& d, DateGeneration::Rule rule) {
636
0
        Date result = Date(20, d.month(), d.year());
637
0
        if (result > d)
638
0
            result -= 1 * Months;
639
0
        if (rule == DateGeneration::TwentiethIMM ||
640
0
            rule == DateGeneration::OldCDS ||
641
0
            rule == DateGeneration::CDS ||
642
0
            rule == DateGeneration::CDS2015) {
643
0
            Month m = result.month();
644
0
            if (m % 3 != 0) { // not a main IMM nmonth
645
0
                Integer skip = m % 3;
646
0
                result -= skip * Months;
647
0
            }
648
0
        }
649
0
        return result;
650
0
    }
651
652
}