• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lballabio / QuantLib / 23014668432

12 Mar 2026 05:17PM UTC coverage: 74.223% (-0.04%) from 74.259%
23014668432

Pull #2368

github

web-flow
Merge 859f97bcc into 0a02cced0
Pull Request #2368: Fx options utils - `BlackVolatilitySurfaceDelta` class

113 of 195 new or added lines in 5 files covered. (57.95%)

220 existing lines in 21 files now uncovered.

57858 of 77952 relevant lines covered (74.22%)

8737358.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.39
/ql/time/schedule.cpp
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
 <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/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
        Date nextTwentieth(const Date& d, DateGeneration::Rule rule) {
506✔
34
            Date result = Date(20, d.month(), d.year());
506✔
35
            if (result < d)
506✔
36
                result += 1*Months;
208✔
37
            if (rule == DateGeneration::TwentiethIMM ||
506✔
38
                rule == DateGeneration::OldCDS ||
39
                rule == DateGeneration::CDS ||
506✔
40
                rule == DateGeneration::CDS2015) {
41
                Month m = result.month();
506✔
42
                if (m % 3 != 0) { // not a main IMM nmonth
506✔
43
                    Integer skip = 3 - m%3;
152✔
44
                    result += skip*Months;
152✔
45
                }
46
            }
47
            return result;
506✔
48
        }
49

50
    }
51

52

53
    Schedule::Schedule(const std::vector<Date>& dates,
766✔
54
                       Calendar calendar,
55
                       BusinessDayConvention convention,
56
                       const ext::optional<BusinessDayConvention>& terminationDateConvention,
57
                       const ext::optional<Period>& tenor,
58
                       const ext::optional<DateGeneration::Rule>& rule,
59
                       const ext::optional<bool>& endOfMonth,
60
                       std::vector<bool> isRegular)
766✔
61
    : tenor_(tenor), calendar_(std::move(calendar)), convention_(convention),
766✔
62
      terminationDateConvention_(terminationDateConvention), rule_(rule), dates_(dates),
1,532✔
63
      isRegular_(std::move(isRegular)) {
766✔
64

65
        if (tenor && !allowsEndOfMonth(*tenor))
766✔
UNCOV
66
            endOfMonth_ = false;
×
67
        else
68
            endOfMonth_ = endOfMonth;
766✔
69

70
        QL_REQUIRE(isRegular_.empty() || isRegular_.size() == dates.size() - 1,
5✔
71
                   "isRegular size (" << isRegular_.size()
72
                                      << ") must be zero or equal to the number of dates minus 1 ("
73
                                      << dates.size() - 1 << ")");
74
    }
766✔
75

76
    Schedule::Schedule(Date effectiveDate,
113,962✔
77
                       const Date& terminationDate,
78
                       const Period& tenor,
79
                       Calendar cal,
80
                       BusinessDayConvention convention,
81
                       BusinessDayConvention terminationDateConvention,
82
                       DateGeneration::Rule rule,
83
                       bool endOfMonth,
84
                       const Date& first,
85
                       const Date& nextToLast)
113,962✔
86
    : tenor_(tenor), calendar_(std::move(cal)), convention_(convention),
113,962✔
87
      terminationDateConvention_(terminationDateConvention), rule_(rule),
88
      endOfMonth_(allowsEndOfMonth(tenor) ? endOfMonth : false),
113,962✔
89
      firstDate_(first == effectiveDate ? Date() : first),
113,962✔
90
      nextToLastDate_(nextToLast == terminationDate ? Date() : nextToLast) {
113,962✔
91
        // sanity checks
92
        QL_REQUIRE(terminationDate != Date(), "null termination date");
113,962✔
93

94
        // in many cases (e.g. non-expired bonds) the effective date is not
95
        // really necessary. In these cases a decent placeholder is enough
96
        if (effectiveDate==Date() && first==Date()
113,962✔
97
                                  && rule==DateGeneration::Backward) {
113,962✔
UNCOV
98
            Date evalDate = Settings::instance().evaluationDate();
×
UNCOV
99
            QL_REQUIRE(evalDate < terminationDate, "null effective date");
×
100
            Natural y;
UNCOV
101
            if (nextToLast != Date()) {
×
UNCOV
102
                y = (nextToLast - evalDate)/366 + 1;
×
103
                effectiveDate = nextToLast - y*Years;
×
104
            } else {
UNCOV
105
                y = (terminationDate - evalDate)/366 + 1;
×
106
                effectiveDate = terminationDate - y*Years;
×
107
            }
108
        } else
109
            QL_REQUIRE(effectiveDate != Date(), "null effective date");
113,962✔
110

111
        QL_REQUIRE(effectiveDate < terminationDate,
113,962✔
112
                   "effective date (" << effectiveDate
113
                   << ") later than or equal to termination date ("
114
                   << terminationDate << ")");
115

116
        if (tenor.length()==0)
113,962✔
117
            rule_ = DateGeneration::Zero;
6✔
118
        else
119
            QL_REQUIRE(tenor.length()>0,
113,956✔
120
                       "non positive tenor (" << tenor << ") not allowed");
121

122
        if (firstDate_ != Date()) {
113,962✔
123
            switch (*rule_) {
19✔
124
              case DateGeneration::Backward:
19✔
125
              case DateGeneration::Forward:
126
                QL_REQUIRE(firstDate_ > effectiveDate &&
19✔
127
                           firstDate_ <= terminationDate,
128
                           "first date (" << firstDate_ <<
129
                           ") out of effective-termination date range (" <<
130
                           effectiveDate << ", " << terminationDate << "]");
131
                // we should ensure that the above condition is still
132
                // verified after adjustment
133
                break;
UNCOV
134
              case DateGeneration::ThirdWednesday:
×
UNCOV
135
                  QL_REQUIRE(IMM::isIMMdate(firstDate_, false),
×
136
                             "first date (" << firstDate_ <<
137
                             ") is not an IMM date");
138
                break;
139
              case DateGeneration::Zero:
×
140
              case DateGeneration::Twentieth:
141
              case DateGeneration::TwentiethIMM:
142
              case DateGeneration::OldCDS:
143
              case DateGeneration::CDS:
144
              case DateGeneration::CDS2015:
UNCOV
145
                QL_FAIL("first date incompatible with " << *rule_ <<
×
146
                        " date generation rule");
UNCOV
147
              default:
×
UNCOV
148
                QL_FAIL("unknown rule (" << Integer(*rule_) << ")");
×
149
            }
150
        }
151
        if (nextToLastDate_ != Date()) {
113,962✔
152
            switch (*rule_) {
11✔
153
              case DateGeneration::Backward:
11✔
154
              case DateGeneration::Forward:
155
                QL_REQUIRE(nextToLastDate_ >= effectiveDate &&
11✔
156
                           nextToLastDate_ < terminationDate,
157
                           "next to last date (" << nextToLastDate_ <<
158
                           ") out of effective-termination date range [" <<
159
                           effectiveDate << ", " << terminationDate << ")");
160
                // we should ensure that the above condition is still
161
                // verified after adjustment
162
                break;
UNCOV
163
              case DateGeneration::ThirdWednesday:
×
UNCOV
164
                QL_REQUIRE(IMM::isIMMdate(nextToLastDate_, false),
×
165
                           "next-to-last date (" << nextToLastDate_ <<
166
                           ") is not an IMM date");
167
                break;
168
              case DateGeneration::Zero:
×
169
              case DateGeneration::Twentieth:
170
              case DateGeneration::TwentiethIMM:
171
              case DateGeneration::OldCDS:
172
              case DateGeneration::CDS:
173
              case DateGeneration::CDS2015:
UNCOV
174
                QL_FAIL("next to last date incompatible with " << *rule_ <<
×
175
                        " date generation rule");
UNCOV
176
              default:
×
UNCOV
177
                QL_FAIL("unknown rule (" << Integer(*rule_) << ")");
×
178
            }
179
        }
180

181

182
        // calendar needed for endOfMonth adjustment
183
        Calendar nullCalendar = NullCalendar();
113,962✔
184
        Integer periods = 1;
185
        Date seed, exitDate;
113,962✔
186
        switch (*rule_) {
113,962✔
187

188
          case DateGeneration::Zero:
189
            tenor_ = 0*Years;
190
            dates_.push_back(effectiveDate);
6✔
191
            dates_.push_back(terminationDate);
6✔
192
            isRegular_.push_back(true);
6✔
193
            break;
194

195
          case DateGeneration::Backward:
93,615✔
196

197
            dates_.push_back(terminationDate);
93,615✔
198

199
            seed = terminationDate;
93,615✔
200
            if (nextToLastDate_ != Date()) {
93,615✔
201
                dates_.push_back(nextToLastDate_);
11✔
202
                Date temp = nullCalendar.advance(seed,
11✔
203
                    -periods*(*tenor_), convention, *endOfMonth_);
11✔
204
                isRegular_.push_back(temp == nextToLastDate_);
11✔
205
                seed = nextToLastDate_;
11✔
206
            }
207

208
            exitDate = effectiveDate;
93,615✔
209
            if (firstDate_ != Date())
93,615✔
210
                exitDate = firstDate_;
10✔
211

212
            for (;;) {
213
                Date temp = nullCalendar.advance(seed,
5,096,352✔
214
                    -periods*(*tenor_), convention, *endOfMonth_);
5,096,352✔
215
                if (temp < exitDate) {
5,096,352✔
216
                    if (firstDate_ != Date() &&
93,625✔
217
                        (calendar_.adjust(dates_.back(),convention)!=
10✔
218
                         calendar_.adjust(firstDate_,convention))) {
93,621✔
219
                        dates_.push_back(firstDate_);
4✔
220
                        isRegular_.push_back(false);
4✔
221
                    }
222
                    break;
93,615✔
223
                } else {
224
                    // skip dates that would result in duplicates
225
                    // after adjustment
226
                    if (calendar_.adjust(dates_.back(),convention)!=
5,002,737✔
227
                        calendar_.adjust(temp,convention)) {
10,005,474✔
228
                        dates_.push_back(temp);
3,883,579✔
229
                        isRegular_.push_back(true);
3,883,579✔
230
                    }
231
                    ++periods;
5,002,737✔
232
                }
233
            }
5,002,737✔
234

235
            if (calendar_.adjust(dates_.back(),convention)!=
93,615✔
236
                calendar_.adjust(effectiveDate,convention)) {
187,230✔
237
                dates_.push_back(effectiveDate);
11,075✔
238
                isRegular_.push_back(false);
11,075✔
239
            }
240
            std::reverse(dates_.begin(), dates_.end());
93,615✔
241
            std::reverse(isRegular_.begin(), isRegular_.end());
93,615✔
242
            break;
243

244
          case DateGeneration::Twentieth:
418✔
245
          case DateGeneration::TwentiethIMM:
246
          case DateGeneration::ThirdWednesday:
247
          case DateGeneration::ThirdWednesdayInclusive:
248
          case DateGeneration::OldCDS:
249
          case DateGeneration::CDS:
250
          case DateGeneration::CDS2015:
251
            QL_REQUIRE(!*endOfMonth_,
418✔
252
                       "endOfMonth convention incompatible with " << *rule_ <<
253
                       " date generation rule");
254
            [[fallthrough]];
255
          case DateGeneration::Forward:
256

257
            if (*rule_ == DateGeneration::CDS || *rule_ == DateGeneration::CDS2015) {
20,341✔
258
                Date prev20th = previousTwentieth(effectiveDate, *rule_);
306✔
259
                if (calendar_.adjust(prev20th, convention) > effectiveDate) {
306✔
260
                    dates_.push_back(prev20th - 3 * Months);
20✔
261
                    isRegular_.push_back(true);
20✔
262
                }
263
                dates_.push_back(prev20th);
306✔
264
            } else {
265
                dates_.push_back(effectiveDate);
20,035✔
266
            }
267

268
            seed = dates_.back();
20,341✔
269

270
            if (firstDate_!=Date()) {
20,341✔
271
                dates_.push_back(firstDate_);
9✔
272
                Date temp = nullCalendar.advance(seed, periods*(*tenor_),
9✔
273
                                                 convention, *endOfMonth_);
9✔
274
                if (temp!=firstDate_)
9✔
275
                    isRegular_.push_back(false);
4✔
276
                else
277
                    isRegular_.push_back(true);
5✔
278
                seed = firstDate_;
9✔
279
            } else if (*rule_ == DateGeneration::Twentieth ||
20,332✔
280
                       *rule_ == DateGeneration::TwentiethIMM ||
20,289✔
281
                       *rule_ == DateGeneration::OldCDS ||
20,222✔
282
                       *rule_ == DateGeneration::CDS ||
40,332✔
283
                       *rule_ == DateGeneration::CDS2015) {
284
                Date next20th = nextTwentieth(effectiveDate, *rule_);
416✔
285
                if (*rule_ == DateGeneration::OldCDS) {
416✔
286
                    // distance rule inforced in natural days
287
                    static const Date::serial_type stubDays = 30;
288
                    if (next20th - effectiveDate < stubDays) {
67✔
289
                        // +1 will skip this one and get the next
290
                        next20th = nextTwentieth(next20th + 1, *rule_);
44✔
291
                    }
292
                }
293
                if (next20th != effectiveDate) {
416✔
294
                    dates_.push_back(next20th);
363✔
295
                    isRegular_.push_back(*rule_ == DateGeneration::CDS || *rule_ == DateGeneration::CDS2015);
422✔
296
                    seed = next20th;
363✔
297
                }
298
            }
299

300
            exitDate = terminationDate;
20,341✔
301
            if (nextToLastDate_ != Date())
20,341✔
UNCOV
302
                exitDate = nextToLastDate_;
×
303
            for (;;) {
304
                Date temp = nullCalendar.advance(seed, periods*(*tenor_),
262,265✔
305
                                                 convention, *endOfMonth_);
262,265✔
306
                if (temp > exitDate) {
262,265✔
307
                    if (nextToLastDate_ != Date() &&
20,341✔
UNCOV
308
                        (calendar_.adjust(dates_.back(),convention)!=
×
309
                         calendar_.adjust(nextToLastDate_,convention))) {
20,341✔
UNCOV
310
                        dates_.push_back(nextToLastDate_);
×
UNCOV
311
                        isRegular_.push_back(false);
×
312
                    }
313
                    break;
20,341✔
314
                } else {
315
                    // skip dates that would result in duplicates
316
                    // after adjustment
317
                    if (calendar_.adjust(dates_.back(),convention)!=
241,924✔
318
                        calendar_.adjust(temp,convention)) {
483,848✔
319
                        dates_.push_back(temp);
241,759✔
320
                        isRegular_.push_back(true);
241,759✔
321
                    }
322
                    ++periods;
241,924✔
323
                }
324
            }
241,924✔
325

326
            if (calendar_.adjust(dates_.back(),terminationDateConvention)!=
20,341✔
327
                calendar_.adjust(terminationDate,terminationDateConvention)) {
40,682✔
328
                if (*rule_ == DateGeneration::Twentieth ||
181✔
329
                    *rule_ == DateGeneration::TwentiethIMM ||
138✔
330
                    *rule_ == DateGeneration::OldCDS ||
138✔
331
                    *rule_ == DateGeneration::CDS ||
318✔
332
                    *rule_ == DateGeneration::CDS2015) {
333
                    dates_.push_back(nextTwentieth(terminationDate, *rule_));
46✔
334
                    isRegular_.push_back(true);
46✔
335
                } else {
336
                    dates_.push_back(terminationDate);
135✔
337
                    isRegular_.push_back(false);
135✔
338
                }
339
            }
340

341
            break;
342

UNCOV
343
          default:
×
UNCOV
344
            QL_FAIL("unknown rule (" << Integer(*rule_) << ")");
×
345
        }
346

347
        // adjustments
348
        if (*rule_==DateGeneration::ThirdWednesday)
113,962✔
349
            for (Size i=1; i<dates_.size()-1; ++i)
×
UNCOV
350
                dates_[i] = Date::nthWeekday(3, Wednesday,
×
351
                                             dates_[i].month(),
352
                                             dates_[i].year());
353
        else if (*rule_ == DateGeneration::ThirdWednesdayInclusive)
113,962✔
354
            for (auto& date : dates_)
8✔
355
                date = Date::nthWeekday(3, Wednesday, date.month(), date.year());
6✔
356

357
        // first date not adjusted for old CDS schedules
358
        if (convention != Unadjusted && *rule_ != DateGeneration::OldCDS)
113,962✔
359
            dates_.front() = calendar_.adjust(dates_.front(), convention);
98,604✔
360

361
        // termination date is NOT adjusted as per ISDA
362
        // specifications, unless otherwise specified in the
363
        // confirmation of the deal or unless we're creating a CDS
364
        // schedule
365
        if (terminationDateConvention != Unadjusted 
366
            && *rule_ != DateGeneration::CDS 
97,544✔
367
            && *rule_ != DateGeneration::CDS2015) {
211,505✔
368
            dates_.back() = calendar_.adjust(dates_.back(), 
97,543✔
369
                                             terminationDateConvention);
370
        }
371

372
        if (*endOfMonth_ && calendar_.isEndOfMonth(seed)) {
114,339✔
373
            // adjust to end of month
374
            for (Size i=1; i<dates_.size()-1; ++i)
244✔
375
                dates_[i] = calendar_.adjust(Date::endOfMonth(dates_[i]), convention);
221✔
376
        } else {
377
            for (Size i=1; i<dates_.size()-1; ++i)
4,136,763✔
378
                dates_[i] = calendar_.adjust(dates_[i], convention);
4,022,824✔
379
        }
380

381
        // Final safety checks to remove extra next-to-last date, if
382
        // necessary.  It can happen to be equal or later than the end
383
        // date due to EOM adjustments (see the Schedule test suite
384
        // for an example).
385
        if (dates_.size() >= 2 && dates_[dates_.size()-2] >= dates_.back()) {
113,962✔
386
            // there might be two dates only, then isRegular_ has size one
387
            if (isRegular_.size() >= 2) {
3✔
388
                isRegular_[isRegular_.size() - 2] =
3✔
389
                    (dates_[dates_.size() - 2] == dates_.back());
3✔
390
            }
391
            dates_[dates_.size() - 2] = dates_.back();
3✔
392
            dates_.pop_back();
393
            isRegular_.pop_back();
394
        }
395
        if (dates_.size() >= 2 && dates_[1] <= dates_.front()) {
113,962✔
UNCOV
396
            isRegular_[1] =
×
UNCOV
397
                (dates_[1] == dates_.front());
×
UNCOV
398
            dates_[1] = dates_.front();
×
399
            dates_.erase(dates_.begin());
400
            isRegular_.erase(isRegular_.begin());
401
        }
402

403
        QL_ENSURE(dates_.size()>1,
113,962✔
404
            "degenerate single date (" << dates_[0] << ") schedule" <<
405
            "\n seed date: " << seed <<
406
            "\n exit date: " << exitDate <<
407
            "\n effective date: " << effectiveDate <<
408
            "\n first date: " << first <<
409
            "\n next to last date: " << nextToLast <<
410
            "\n termination date: " << terminationDate <<
411
            "\n generation rule: " << *rule_ <<
412
            "\n end of month: " << *endOfMonth_);
413
    }
113,962✔
414

415
    Schedule Schedule::after(const Date& truncationDate) const {
28✔
416
        Schedule result = *this;
28✔
417

418
        QL_REQUIRE(truncationDate < result.dates_.back(),
28✔
419
            "truncation date " << truncationDate <<
420
            " must be before the last schedule date " <<
421
            result.dates_.back());
422
        if (truncationDate > result.dates_[0]) {
28✔
423
            // remove earlier dates
424
            while (result.dates_[0] < truncationDate) {
1,226✔
425
                result.dates_.erase(result.dates_.begin());
1,201✔
426
                if (!result.isRegular_.empty())
427
                    result.isRegular_.erase(result.isRegular_.begin());
1,201✔
428
            }
429

430
            // add truncationDate if missing
431
            if (truncationDate != result.dates_.front()) {
25✔
432
                result.dates_.insert(result.dates_.begin(), truncationDate);
1✔
433
                result.isRegular_.insert(result.isRegular_.begin(), false);
1✔
434
                result.terminationDateConvention_ = Unadjusted;
1✔
435
            }
436
            else {
437
                result.terminationDateConvention_ = convention_;
24✔
438
            }
439

440
            if (result.nextToLastDate_ <= truncationDate)
25✔
441
                result.nextToLastDate_ = Date();
25✔
442
            if (result.firstDate_ <= truncationDate)
25✔
443
                result.firstDate_ = Date();
25✔
444
        }
445

446
        return result;
28✔
UNCOV
447
    }
×
448

449
    Schedule Schedule::until(const Date& truncationDate) const {
138✔
450
        Schedule result = *this;
138✔
451

452
        QL_REQUIRE(truncationDate>result.dates_[0],
138✔
453
                   "truncation date " << truncationDate <<
454
                   " must be later than schedule first date " <<
455
                   result.dates_[0]);
456
        if (truncationDate<result.dates_.back()) {
138✔
457
            // remove later dates
458
            while (result.dates_.back()>truncationDate) {
1,459✔
459
                result.dates_.pop_back();
460
                if(!result.isRegular_.empty())
461
                    result.isRegular_.pop_back();
462
            }
463

464
            // add truncationDate if missing
465
            if (truncationDate!=result.dates_.back()) {
40✔
466
                result.dates_.push_back(truncationDate);
14✔
467
                result.isRegular_.push_back(false);
14✔
468
                result.terminationDateConvention_ = Unadjusted;
14✔
469
            } else {
470
                result.terminationDateConvention_ = convention_;
26✔
471
            }
472

473
            if (result.nextToLastDate_>=truncationDate)
40✔
UNCOV
474
                result.nextToLastDate_ = Date();
×
475
            if (result.firstDate_>=truncationDate)
40✔
UNCOV
476
                result.firstDate_ = Date();
×
477
        }
478

479
        return result;
138✔
UNCOV
480
    }
×
481

482
    std::vector<Date>::const_iterator
UNCOV
483
    Schedule::lower_bound(const Date& refDate) const {
×
UNCOV
484
        Date d = (refDate==Date() ?
×
485
                  Settings::instance().evaluationDate() :
×
UNCOV
486
                  refDate);
×
UNCOV
487
        return std::lower_bound(dates_.begin(), dates_.end(), d);
×
488
    }
489

490
    Date Schedule::nextDate(const Date& refDate) const {
×
491
        auto res = lower_bound(refDate);
×
492
        if (res!=dates_.end())
×
UNCOV
493
            return *res;
×
494
        else
495
            return {};
×
496
    }
497

498
    Date Schedule::previousDate(const Date& refDate) const {
×
UNCOV
499
        auto res = lower_bound(refDate);
×
500
        if (res!=dates_.begin())
×
UNCOV
501
            return *(--res);
×
502
        else
503
            return {};
×
504
    }
505

506
    bool Schedule::hasIsRegular() const { return !isRegular_.empty(); }
428,022✔
507

508
    bool Schedule::isRegular(Size i) const {
213,444✔
509
        QL_REQUIRE(hasIsRegular(),
213,444✔
510
                   "full interface (isRegular) not available");
511
        QL_REQUIRE(i<=isRegular_.size() && i>0,
213,444✔
512
                   "index (" << i << ") must be in [1, " <<
513
                   isRegular_.size() <<"]");
514
        return isRegular_[i-1];
213,444✔
515
    }
516

517
    const std::vector<bool>& Schedule::isRegular() const {
6✔
UNCOV
518
        QL_REQUIRE(!isRegular_.empty(), "full interface (isRegular) not available");
×
519
        return isRegular_;
6✔
520
    }
521

522
    MakeSchedule& MakeSchedule::from(const Date& effectiveDate) {
22,086✔
523
        effectiveDate_ = effectiveDate;
22,086✔
524
        return *this;
22,086✔
525
    }
526

527
    MakeSchedule& MakeSchedule::to(const Date& terminationDate) {
22,086✔
528
        terminationDate_ = terminationDate;
22,086✔
529
        return *this;
22,086✔
530
    }
531

532
    MakeSchedule& MakeSchedule::withTenor(const Period& tenor) {
15,670✔
533
        tenor_ = tenor;
534
        return *this;
15,670✔
535
    }
536

537
    MakeSchedule& MakeSchedule::withFrequency(Frequency frequency) {
6,416✔
538
        tenor_ = Period(frequency);
6,416✔
539
        return *this;
6,416✔
540
    }
541

542
    MakeSchedule& MakeSchedule::withCalendar(const Calendar& calendar) {
22,067✔
543
        calendar_ = calendar;
544
        return *this;
22,067✔
545
    }
546

547
    MakeSchedule& MakeSchedule::withConvention(BusinessDayConvention conv) {
22,044✔
548
        convention_ = conv;
22,044✔
549
        return *this;
22,044✔
550
    }
551

552
    MakeSchedule& MakeSchedule::withTerminationDateConvention(
312✔
553
                                                BusinessDayConvention conv) {
554
        terminationDateConvention_ = conv;
312✔
555
        return *this;
312✔
556
    }
557

558
    MakeSchedule& MakeSchedule::withRule(DateGeneration::Rule r) {
340✔
559
        rule_ = r;
340✔
560
        return *this;
340✔
561
    }
562

563
    MakeSchedule& MakeSchedule::forwards() {
6,314✔
564
        rule_ = DateGeneration::Forward;
6,314✔
565
        return *this;
6,314✔
566
    }
567

568
    MakeSchedule& MakeSchedule::backwards() {
15,374✔
569
        rule_ = DateGeneration::Backward;
15,374✔
570
        return *this;
15,374✔
571
    }
572

573
    MakeSchedule& MakeSchedule::endOfMonth(bool flag) {
817✔
574
        endOfMonth_ = flag;
817✔
575
        return *this;
817✔
576
    }
577

578
    MakeSchedule& MakeSchedule::withFirstDate(const Date& d) {
13✔
579
        firstDate_ = d;
13✔
580
        return *this;
13✔
581
    }
582

583
    MakeSchedule& MakeSchedule::withNextToLastDate(const Date& d) {
9✔
584
        nextToLastDate_ = d;
9✔
585
        return *this;
9✔
586
    }
587

588
    MakeSchedule::operator Schedule() const {
22,086✔
589
        // check for mandatory arguments
590
        QL_REQUIRE(effectiveDate_ != Date(), "effective date not provided");
22,086✔
591
        QL_REQUIRE(terminationDate_ != Date(), "termination date not provided");
22,086✔
592
        QL_REQUIRE(tenor_, "tenor/frequency not provided");
22,086✔
593

594
        // set dynamic defaults:
595
        BusinessDayConvention convention;
596
        // if a convention was set, we use it.
597
        if (convention_) { // NOLINT(readability-implicit-bool-conversion)
22,086✔
598
            convention = *convention_;
22,044✔
599
        } else {
600
            if (!calendar_.empty()) {
42✔
601
                // ...if we set a calendar, we probably want it to be used;
602
                convention = Following;
603
            } else {
604
                // if not, we don't care.
605
                convention = Unadjusted;
606
            }
607
        }
608

609
        BusinessDayConvention terminationDateConvention;
610
        // if set explicitly, we use it;
611
        if (terminationDateConvention_) { // NOLINT(readability-implicit-bool-conversion)
22,086✔
612
            terminationDateConvention = *terminationDateConvention_;
312✔
613
        } else {
614
            // Unadjusted as per ISDA specification
615
            terminationDateConvention = convention;
616
        }
617

618
        Calendar calendar = calendar_;
619
        // if no calendar was set...
620
        if (calendar.empty()) {
22,086✔
621
            // ...we use a null one.
622
            calendar = NullCalendar();
38✔
623
        }
624

625
        return Schedule(effectiveDate_, terminationDate_, *tenor_, calendar,
22,086✔
626
                        convention, terminationDateConvention,
627
                        rule_, endOfMonth_, firstDate_, nextToLastDate_);
66,258✔
628
    }
629

630
    Date previousTwentieth(const Date& d, DateGeneration::Rule rule) {
579✔
631
        Date result = Date(20, d.month(), d.year());
579✔
632
        if (result > d)
579✔
633
            result -= 1 * Months;
241✔
634
        if (rule == DateGeneration::TwentiethIMM ||
579✔
635
            rule == DateGeneration::OldCDS ||
636
            rule == DateGeneration::CDS ||
579✔
637
            rule == DateGeneration::CDS2015) {
638
            Month m = result.month();
579✔
639
            if (m % 3 != 0) { // not a main IMM nmonth
579✔
640
                Integer skip = m % 3;
641
                result -= skip * Months;
315✔
642
            }
643
        }
644
        return result;
579✔
645
    }
646

647
    bool allowsEndOfMonth(const Period& tenor) {
113,968✔
648
        return (tenor.units() == Months || tenor.units() == Years) && tenor >= 1 * Months;
113,968✔
649
    }
650

651
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc