/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 | | } |