[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2003 RiskMap srl
5 Copyright (C) 2006 Piter Dias
6 Copyright (C) 2012 Simon Shakeshaft
7 Copyright (c) 2015 Klaus Spanderen
8
9 This file is part of QuantLib, a free-software/open-source library
10 for financial quantitative analysts and developers - http://quantlib.org/
11
12 QuantLib is free software: you can redistribute it and/or modify it
13 under the terms of the QuantLib license. You should have received a
14 copy of the license along with this program; if not, please email
15 <quantlib-dev@lists.sf.net>. The license is also available online at
16 <http://quantlib.org/license.shtml>.
17
18 This program is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 FOR A PARTICULAR PURPOSE. See the license for more details.
21*/
22
23
24#include "daycounters.hpp"
25#include "utilities.hpp"
26#include <ql/time/daycounters/actual360.hpp>
27#include <ql/time/daycounters/actualactual.hpp>
28#include <ql/time/daycounters/actual365fixed.hpp>
29#include <ql/time/daycounters/actual36525.hpp>
30#include <ql/time/daycounters/actual366.hpp>
31#include <ql/time/daycounters/actual364.hpp>
32#include <ql/time/daycounters/one.hpp>
33#include <ql/time/daycounters/simpledaycounter.hpp>
34#include <ql/time/daycounters/business252.hpp>
35#include <ql/time/daycounters/thirty360.hpp>
36#include <ql/time/daycounters/thirty365.hpp>
37#include <ql/time/calendars/brazil.hpp>
38#include <ql/time/calendars/canada.hpp>
39#include <ql/time/calendars/unitedstates.hpp>
40#include <ql/time/schedule.hpp>
41#include <ql/math/comparison.hpp>
42#include <ql/time/calendars/china.hpp>
43#include <ql/time/daycounters/yearfractiontodate.hpp>
44
45#include <cmath>
46#include <iomanip>
47
48using namespace QuantLib;
49using namespace boost::unit_test_framework;
50
51namespace day_counters_test {
52
53 struct SingleCase {
54 SingleCase(ActualActual::Convention convention,
55 const Date& start,
56 const Date& end,
57 const Date& refStart,
58 const Date& refEnd,
59 Time result)
60 : convention(convention), start(start), end(end),
61 refStart(refStart), refEnd(refEnd), result(result) {}
62 SingleCase(ActualActual::Convention convention,
63 const Date& start,
64 const Date& end,
65 Time result)
66 : convention(convention), start(start), end(end), result(result) {}
67 ActualActual::Convention convention;
68 Date start;
69 Date end;
70 Date refStart;
71 Date refEnd;
72 Time result;
73 };
74
75 struct Thirty360Case {
76 Date start;
77 Date end;
78 Date::serial_type expected;
79 };
80
81 Time ISMAYearFractionWithReferenceDates(
82 const DayCounter& dayCounter, Date start, Date end, Date refStart, Date refEnd) {
83 Real referenceDayCount = Real(dayCounter.dayCount(d1: refStart, d2: refEnd));
84 // guess how many coupon periods per year:
85 auto couponsPerYear = (Integer)std::lround(x: 365.0 / referenceDayCount);
86 // the above is good enough for annual or semi annual payments.
87 return Real(dayCounter.dayCount(d1: start, d2: end))
88 / (referenceDayCount*couponsPerYear);
89 }
90
91 Time actualActualDaycountComputation(const Schedule& schedule, Date start, Date end) {
92
93 DayCounter daycounter = ActualActual(ActualActual::ISMA, schedule);
94 Time yearFraction = 0.0;
95
96 for (Size i = 1; i < schedule.size() - 1; i++) {
97 Date referenceStart = schedule.date(i);
98 Date referenceEnd = schedule.date(i: i+1);
99 if (start < referenceEnd && end > referenceStart) {
100 yearFraction += ISMAYearFractionWithReferenceDates(
101 dayCounter: daycounter,
102 start: (start > referenceStart) ? start : referenceStart,
103 end: (end < referenceEnd) ? end : referenceEnd,
104 refStart: referenceStart,
105 refEnd: referenceEnd
106 );
107 };
108 }
109 return yearFraction;
110 }
111
112}
113
114
115void DayCounterTest::testActualActual() {
116
117 BOOST_TEST_MESSAGE("Testing actual/actual day counters...");
118
119 using namespace day_counters_test;
120
121 SingleCase testCases[] = {
122 // first example
123 SingleCase(ActualActual::ISDA,
124 Date(1,November,2003), Date(1,May,2004),
125 0.497724380567),
126 SingleCase(ActualActual::ISMA,
127 Date(1,November,2003), Date(1,May,2004),
128 Date(1,November,2003), Date(1,May,2004),
129 0.500000000000),
130 SingleCase(ActualActual::AFB,
131 Date(1,November,2003), Date(1,May,2004),
132 0.497267759563),
133 // short first calculation period (first period)
134 SingleCase(ActualActual::ISDA,
135 Date(1,February,1999), Date(1,July,1999),
136 0.410958904110),
137 SingleCase(ActualActual::ISMA,
138 Date(1,February,1999), Date(1,July,1999),
139 Date(1,July,1998), Date(1,July,1999),
140 0.410958904110),
141 SingleCase(ActualActual::AFB,
142 Date(1,February,1999), Date(1,July,1999),
143 0.410958904110),
144 // short first calculation period (second period)
145 SingleCase(ActualActual::ISDA,
146 Date(1,July,1999), Date(1,July,2000),
147 1.001377348600),
148 SingleCase(ActualActual::ISMA,
149 Date(1,July,1999), Date(1,July,2000),
150 Date(1,July,1999), Date(1,July,2000),
151 1.000000000000),
152 SingleCase(ActualActual::AFB,
153 Date(1,July,1999), Date(1,July,2000),
154 1.000000000000),
155 // long first calculation period (first period)
156 SingleCase(ActualActual::ISDA,
157 Date(15,August,2002), Date(15,July,2003),
158 0.915068493151),
159 SingleCase(ActualActual::ISMA,
160 Date(15,August,2002), Date(15,July,2003),
161 Date(15,January,2003), Date(15,July,2003),
162 0.915760869565),
163 SingleCase(ActualActual::AFB,
164 Date(15,August,2002), Date(15,July,2003),
165 0.915068493151),
166 // long first calculation period (second period)
167 /* Warning: the ISDA case is in disagreement with mktc1198.pdf */
168 SingleCase(ActualActual::ISDA,
169 Date(15,July,2003), Date(15,January,2004),
170 0.504004790778),
171 SingleCase(ActualActual::ISMA,
172 Date(15,July,2003), Date(15,January,2004),
173 Date(15,July,2003), Date(15,January,2004),
174 0.500000000000),
175 SingleCase(ActualActual::AFB,
176 Date(15,July,2003), Date(15,January,2004),
177 0.504109589041),
178 // short final calculation period (penultimate period)
179 SingleCase(ActualActual::ISDA,
180 Date(30,July,1999), Date(30,January,2000),
181 0.503892506924),
182 SingleCase(ActualActual::ISMA,
183 Date(30,July,1999), Date(30,January,2000),
184 Date(30,July,1999), Date(30,January,2000),
185 0.500000000000),
186 SingleCase(ActualActual::AFB,
187 Date(30,July,1999), Date(30,January,2000),
188 0.504109589041),
189 // short final calculation period (final period)
190 SingleCase(ActualActual::ISDA,
191 Date(30,January,2000), Date(30,June,2000),
192 0.415300546448),
193 SingleCase(ActualActual::ISMA,
194 Date(30,January,2000), Date(30,June,2000),
195 Date(30,January,2000), Date(30,July,2000),
196 0.417582417582),
197 SingleCase(ActualActual::AFB,
198 Date(30,January,2000), Date(30,June,2000),
199 0.41530054644)
200 };
201
202 Size n = sizeof(testCases)/sizeof(SingleCase);
203 for (Size i=0; i<n; i++) {
204 ActualActual dayCounter(testCases[i].convention);
205 Date d1 = testCases[i].start,
206 d2 = testCases[i].end,
207 rd1 = testCases[i].refStart,
208 rd2 = testCases[i].refEnd;
209 Time calculated = dayCounter.yearFraction(d1,d2,refPeriodStart: rd1,refPeriodEnd: rd2);
210
211 if (std::fabs(x: calculated-testCases[i].result) > 1.0e-10) {
212 std::ostringstream period, refPeriod;
213 period << "period: " << d1 << " to " << d2;
214 if (testCases[i].convention == ActualActual::ISMA)
215 refPeriod << "referencePeriod: " << rd1 << " to " << rd2;
216 BOOST_ERROR(dayCounter.name() << ":\n"
217 << period.str() << "\n" << refPeriod.str() << "\n"
218 << std::setprecision(10)
219 << " calculated: " << calculated << "\n"
220 << " expected: " << testCases[i].result);
221 }
222 }
223}
224
225void DayCounterTest::testActualActualIsma()
226{
227 BOOST_TEST_MESSAGE("Testing actual/actual (ISMA) with odd last period...");
228
229 bool isEndOfMonth(false);
230 Frequency frequency(Semiannual);
231 Date interestAccrualDate(30, Jan, 1999);
232 Date maturityDate(30, Jun, 2000);
233 Date firstCouponDate(30, Jul, 1999);
234 Date penultimateCouponDate(30, Jan, 2000);
235 Date d1(30, Jan, 2000);
236 Date d2(30, Jun, 2000);
237
238 double expected(152. / (182. * 2));
239
240 Schedule schedule = MakeSchedule()
241 .from(effectiveDate: interestAccrualDate)
242 .to(terminationDate: maturityDate)
243 .withFrequency(frequency)
244 .withFirstDate(d: firstCouponDate)
245 .withNextToLastDate(d: penultimateCouponDate)
246 .endOfMonth(flag: isEndOfMonth);
247
248 DayCounter dayCounter = ActualActual(ActualActual::ISMA, schedule);
249
250 Real calculated(dayCounter.yearFraction(d1, d2));
251
252 if (std::fabs(x: calculated - expected) > 1.0e-10) {
253 std::ostringstream period;
254 period << "period: " << d1 << " to " << d2 << "\n"
255 << "firstCouponDate: " << firstCouponDate << "\n"
256 << "penultimateCouponDate: " << penultimateCouponDate << "\n";
257 BOOST_ERROR(dayCounter.name() << ":\n"
258 << period.str()
259 << std::setprecision(10)
260 << " calculated: " << calculated << "\n"
261 << " expected: " << expected);
262 }
263
264 //////////////////////////////////
265
266 isEndOfMonth = true;
267 frequency = Quarterly;
268 interestAccrualDate = Date(31, May, 1999);
269 maturityDate = Date(30, Apr, 2000);
270 firstCouponDate = Date(31, Aug, 1999);
271 penultimateCouponDate = Date(30, Nov, 1999);
272 d1 = Date(30, Nov, 1999);
273 d2 = Date(30, Apr, 2000);
274
275 expected = 91.0 / (91.0 * 4) + 61.0 / (92.0 * 4);
276
277 schedule = MakeSchedule()
278 .from(effectiveDate: interestAccrualDate)
279 .to(terminationDate: maturityDate)
280 .withFrequency(frequency)
281 .withFirstDate(d: firstCouponDate)
282 .withNextToLastDate(d: penultimateCouponDate)
283 .endOfMonth(flag: isEndOfMonth);
284
285 dayCounter = ActualActual(ActualActual::ISMA, schedule);
286
287 calculated = dayCounter.yearFraction(d1, d2);
288
289 if (std::fabs(x: calculated - expected) > 1.0e-10) {
290 std::ostringstream period;
291 period << "period: " << d1 << " to " << d2 << "\n"
292 << "firstCouponDate: " << firstCouponDate << "\n"
293 << "penultimateCouponDate: " << penultimateCouponDate << "\n";
294 BOOST_ERROR(dayCounter.name() << ":\n"
295 << period.str()
296 << std::setprecision(10)
297 << " calculated: " << calculated << "\n"
298 << " expected: " << expected);
299 }
300
301
302 //////////////////////////////////
303
304 isEndOfMonth = false;
305 frequency = Quarterly;
306 interestAccrualDate = Date(31, May, 1999);
307 maturityDate = Date(30, Apr, 2000);
308 firstCouponDate = Date(31, Aug, 1999);
309 penultimateCouponDate = Date(30, Nov, 1999);
310 d1 = Date(30, Nov, 1999);
311 d2 = Date(30, Apr, 2000);
312
313 expected = 91.0 / (91.0 * 4) + 61.0 / (90.0 * 4);
314
315 schedule = MakeSchedule()
316 .from(effectiveDate: interestAccrualDate)
317 .to(terminationDate: maturityDate)
318 .withFrequency(frequency)
319 .withFirstDate(d: firstCouponDate)
320 .withNextToLastDate(d: penultimateCouponDate)
321 .endOfMonth(flag: isEndOfMonth);
322
323 dayCounter = ActualActual(ActualActual::ISMA, schedule);
324
325 calculated = dayCounter.yearFraction(d1, d2);
326
327 if (std::fabs(x: calculated - expected) > 1.0e-10) {
328 std::ostringstream period;
329 period << "period: " << d1 << " to " << d2 << "\n"
330 << "firstCouponDate: " << firstCouponDate << "\n"
331 << "penultimateCouponDate: " << penultimateCouponDate << "\n";
332 BOOST_ERROR(dayCounter.name() << ":\n"
333 << period.str()
334 << std::setprecision(10)
335 << " calculated: " << calculated << "\n"
336 << " expected: " << expected);
337 }
338}
339
340void DayCounterTest::testActualActualWithSemiannualSchedule() {
341
342 BOOST_TEST_MESSAGE("Testing actual/actual with schedule "
343 "for undefined semiannual reference periods...");
344
345 using namespace day_counters_test;
346
347 Calendar calendar = UnitedStates(UnitedStates::GovernmentBond);
348 Date fromDate = Date(10, January, 2017);
349 Date firstCoupon = Date(31, August, 2017);
350 Date quasiCoupon = Date(28, February, 2017);
351 Date quasiCoupon2 = Date(31, August, 2016);
352
353 Schedule schedule = MakeSchedule()
354 .from(effectiveDate: fromDate)
355 .withFirstDate(d: firstCoupon)
356 .to(terminationDate: Date(28, February, 2026))
357 .withFrequency(Semiannual)
358 .withCalendar(calendar)
359 .withConvention(Unadjusted)
360 .backwards().endOfMonth(flag: true);
361
362 Date testDate = schedule.date(i: 1);
363 DayCounter dayCounter = ActualActual(ActualActual::ISMA, schedule);
364 DayCounter dayCounterNoSchedule = ActualActual(ActualActual::ISMA);
365
366 Date referencePeriodStart = schedule.date(i: 1);
367 Date referencePeriodEnd = schedule.date(i: 2);
368
369 //Test
370 QL_ASSERT(dayCounter.yearFraction(referencePeriodStart,
371 referencePeriodStart) == 0.0,
372 "This should be zero."
373 );
374 QL_ASSERT(dayCounterNoSchedule.yearFraction(referencePeriodStart,
375 referencePeriodStart) == 0.0,
376 "This should be zero"
377 );
378 QL_ASSERT(dayCounterNoSchedule.yearFraction(referencePeriodStart,
379 referencePeriodStart,
380 referencePeriodStart,
381 referencePeriodStart) == 0.0,
382 "This should be zero"
383 );
384 QL_ASSERT(dayCounter.yearFraction(referencePeriodStart,
385 referencePeriodEnd) == 0.5,
386 "This should be exact using schedule; "
387 << referencePeriodStart << " to " << referencePeriodEnd
388 << "Should be 0.5"
389 );
390 QL_ASSERT(dayCounterNoSchedule.yearFraction(referencePeriodStart,
391 referencePeriodEnd,
392 referencePeriodStart,
393 referencePeriodEnd) == 0.5,
394 "This should be exact for explicit reference "
395 "periods with no schedule");
396
397
398 while (testDate < referencePeriodEnd) {
399 Time difference =
400 dayCounter.yearFraction(d1: testDate, d2: referencePeriodEnd,
401 refPeriodStart: referencePeriodStart, refPeriodEnd: referencePeriodEnd) -
402 dayCounter.yearFraction(d1: testDate, d2: referencePeriodEnd);
403 if (std::fabs(x: difference) > 1.0e-10) {
404 BOOST_ERROR("Failed to correctly use the schedule "
405 "to find the reference period for Act/Act");
406 };
407 testDate = calendar.advance(testDate, n: 1, unit: Days);
408 }
409
410 //Test long first coupon
411 Real calculatedYearFraction =
412 dayCounter.yearFraction(d1: fromDate, d2: firstCoupon);
413 Real expectedYearFraction =
414 0.5 + ((Real) dayCounter.dayCount(d1: fromDate, d2: quasiCoupon))
415 /(2*dayCounter.dayCount(d1: quasiCoupon2, d2: quasiCoupon));
416
417 QL_ASSERT(std::fabs(calculatedYearFraction-expectedYearFraction) < 1.0e-10,
418 "Failed to compute the expected year fraction "
419 "\n expected: " << expectedYearFraction <<
420 "\n calculated: " << calculatedYearFraction);
421
422 // test multiple periods
423
424 schedule = MakeSchedule()
425 .from(effectiveDate: Date(10, January, 2017))
426 .withFirstDate(d: Date(31, August, 2017))
427 .to(terminationDate: Date(28, February, 2026))
428 .withFrequency(Semiannual)
429 .withCalendar(calendar)
430 .withConvention(Unadjusted)
431 .backwards().endOfMonth(flag: false);
432
433 Date periodStartDate = schedule.date(i: 1);
434 Date periodEndDate = schedule.date(i: 2);
435
436 dayCounter = ActualActual(ActualActual::ISMA, schedule);
437
438 while (periodEndDate < schedule.date(i: schedule.size()-2)) {
439 Time expected =
440 actualActualDaycountComputation(schedule,
441 start: periodStartDate,
442 end: periodEndDate);
443 Time calculated = dayCounter.yearFraction(d1: periodStartDate,
444 d2: periodEndDate);
445
446 if (std::fabs(x: expected - calculated) > 1e-8) {
447 BOOST_ERROR("Failed to compute the correct year fraction "
448 "given a schedule: " << periodStartDate <<
449 " to " << periodEndDate <<
450 "\n expected: " << expected <<
451 " calculated: " << calculated);
452 }
453 periodEndDate = calendar.advance(periodEndDate, n: 1, unit: Days);
454 }
455}
456
457
458void DayCounterTest::testActualActualWithAnnualSchedule(){
459
460 BOOST_TEST_MESSAGE("Testing actual/actual with schedule "
461 "for undefined annual reference periods...");
462
463 using namespace day_counters_test;
464
465 // Now do an annual schedule
466 Calendar calendar = UnitedStates(UnitedStates::GovernmentBond);
467 Schedule schedule = MakeSchedule()
468 .from(effectiveDate: Date(10, January, 2017))
469 .withFirstDate(d: Date(31, August, 2017))
470 .to(terminationDate: Date(28, February, 2026))
471 .withFrequency(Annual)
472 .withCalendar(calendar)
473 .withConvention(Unadjusted)
474 .backwards().endOfMonth(flag: false);
475
476 Date referencePeriodStart = schedule.date(i: 1);
477 Date referencePeriodEnd = schedule.date(i: 2);
478
479 Date testDate = schedule.date(i: 1);
480 DayCounter dayCounter = ActualActual(ActualActual::ISMA, schedule);
481
482 while (testDate < referencePeriodEnd) {
483 Time difference =
484 ISMAYearFractionWithReferenceDates(dayCounter,
485 start: testDate, end: referencePeriodEnd,
486 refStart: referencePeriodStart, refEnd: referencePeriodEnd) -
487 dayCounter.yearFraction(d1: testDate, d2: referencePeriodEnd);
488 if (std::fabs(x: difference) > 1.0e-10) {
489 BOOST_ERROR("Failed to correctly use the schedule "
490 "to find the reference period for Act/Act:\n"
491 << testDate << " to " << referencePeriodEnd
492 << "\n Ref: " << referencePeriodStart
493 << " to " << referencePeriodEnd);
494 }
495
496 testDate = calendar.advance(testDate, n: 1, unit: Days);
497 }
498}
499
500void DayCounterTest::testActualActualWithSchedule() {
501
502 BOOST_TEST_MESSAGE("Testing actual/actual day counter with schedule...");
503
504 using namespace day_counters_test;
505
506 // long first coupon
507 Date issueDateExpected = Date(17, January, 2017);
508 Date firstCouponDateExpected = Date(31, August, 2017);
509
510 Schedule schedule =
511 MakeSchedule()
512 .from(effectiveDate: issueDateExpected)
513 .withFirstDate(d: firstCouponDateExpected)
514 .to(terminationDate: Date(28, February, 2026))
515 .withFrequency(Semiannual)
516 .withCalendar(Canada())
517 .withConvention(Unadjusted)
518 .backwards()
519 .endOfMonth();
520
521 Date issueDate = schedule.date(i: 0);
522 QL_REQUIRE(issueDate == issueDateExpected,
523 "This is not the expected issue date " << issueDate
524 << " expected " << issueDateExpected);
525 Date firstCouponDate = schedule.date(i: 1);
526 QL_REQUIRE(firstCouponDate == firstCouponDateExpected,
527 "This is not the expected first coupon date " << firstCouponDate
528 << " expected: " << firstCouponDateExpected);
529
530 //Make thw quasi coupon dates:
531 Date quasiCouponDate2 = schedule.calendar().advance(date: firstCouponDate,
532 period: -schedule.tenor(),
533 convention: schedule.businessDayConvention(),
534 endOfMonth: schedule.endOfMonth());
535 Date quasiCouponDate1 = schedule.calendar().advance(date: quasiCouponDate2,
536 period: -schedule.tenor(),
537 convention: schedule.businessDayConvention(),
538 endOfMonth: schedule.endOfMonth());
539
540 Date quasiCouponDate1Expected = Date(31, August, 2016);
541 Date quasiCouponDate2Expected = Date(28, February, 2017);
542
543 QL_REQUIRE(quasiCouponDate2 == quasiCouponDate2Expected,
544 "Expected " << quasiCouponDate2Expected
545 << " as the later quasi coupon date but received "
546 << quasiCouponDate2);
547 QL_REQUIRE(quasiCouponDate1 == quasiCouponDate1Expected,
548 "Expected " << quasiCouponDate1Expected
549 << " as the earlier quasi coupon date but received "
550 << quasiCouponDate1);
551
552 DayCounter dayCounter = ActualActual(ActualActual::ISMA, schedule);
553
554 // full coupon
555 Time t_with_reference = dayCounter.yearFraction(
556 d1: issueDate, d2: firstCouponDate,
557 refPeriodStart: quasiCouponDate2, refPeriodEnd: firstCouponDate
558 );
559 Time t_no_reference = dayCounter.yearFraction(
560 d1: issueDate,
561 d2: firstCouponDate
562 );
563 Time t_total =
564 ISMAYearFractionWithReferenceDates(dayCounter,
565 start: issueDate, end: quasiCouponDate2,
566 refStart: quasiCouponDate1, refEnd: quasiCouponDate2)
567 + 0.5;
568 Time expected = 0.6160220994;
569
570
571 if (std::fabs(x: t_total - expected) > 1.0e-10) {
572 BOOST_ERROR("Failed to reproduce expected time:\n"
573 << std::setprecision(10)
574 << " calculated: " << t_total << "\n"
575 << " expected: " << expected);
576 }
577 if (std::fabs(x: t_with_reference -expected) > 1.0e-10) {
578 BOOST_ERROR("Failed to reproduce expected time:\n"
579 << std::setprecision(10)
580 << " calculated: " << t_with_reference << "\n"
581 << " expected: " << expected);
582 }
583 if (std::fabs(x: t_no_reference - t_with_reference) > 1.0e-10) {
584 BOOST_ERROR("Should produce the same time "
585 "whether or not references are present");
586 }
587
588 // settlement date in the first quasi-period
589 Date settlementDate = Date(29, January, 2017);
590
591 t_with_reference = ISMAYearFractionWithReferenceDates(
592 dayCounter,
593 start: issueDate, end: settlementDate,
594 refStart: quasiCouponDate1, refEnd: quasiCouponDate2
595 );
596 t_no_reference = dayCounter.yearFraction(d1: issueDate, d2: settlementDate);
597 Time t_expected_first_qp = 0.03314917127071823; //12.0/362
598 if (std::fabs(x: t_with_reference - t_expected_first_qp) > 1.0e-10) {
599 BOOST_ERROR("Failed to reproduce expected time:\n"
600 << std::setprecision(10)
601 << " calculated: " << t_no_reference << "\n"
602 << " expected: " << t_expected_first_qp);
603 }
604 if (std::fabs(x: t_no_reference - t_with_reference) > 1.0e-10) {
605 BOOST_ERROR("Should produce the same time "
606 "whether or not references are present");
607 }
608 Time t2 = dayCounter.yearFraction(d1: settlementDate, d2: firstCouponDate);
609 if (std::fabs(x: t_expected_first_qp + t2 - expected) > 1.0e-10) {
610 BOOST_ERROR("Sum of quasiperiod2 split is not consistent");
611 }
612
613 // settlement date in the second quasi-period
614 settlementDate = Date(29, July, 2017);
615
616 /*T = dayCounter.yearFraction(issueDate,
617 settlementDate,
618 quasiCouponDate2,
619 firstCouponDate);
620 t1 = dayCounter.yearFraction(issueDate,
621 quasiCouponDate2,
622 quasiCouponDate1,
623 quasiCouponDate2);
624 Time t2 = dayCounter.yearFraction(quasiCouponDate2,
625 settlementDate,
626 quasiCouponDate2,
627 firstCouponDate);*/
628 t_no_reference = dayCounter.yearFraction(d1: issueDate, d2: settlementDate);
629 t_with_reference = ISMAYearFractionWithReferenceDates(
630 dayCounter,
631 start: issueDate, end: quasiCouponDate2,
632 refStart: quasiCouponDate1, refEnd: quasiCouponDate2
633 ) + ISMAYearFractionWithReferenceDates(
634 dayCounter,
635 start: quasiCouponDate2, end: settlementDate,
636 refStart: quasiCouponDate2, refEnd: firstCouponDate
637 );
638 if (std::fabs(x: t_no_reference - t_with_reference) > 1.0e-10) {
639 BOOST_ERROR("These two cases should be identical");
640 };
641 t2 = dayCounter.yearFraction(d1: settlementDate, d2: firstCouponDate);
642
643
644 if (std::fabs(x: t_total-(t_no_reference+t2)) > 1.0e-10) {
645 BOOST_ERROR("Failed to reproduce expected time:\n"
646 << std::setprecision(10)
647 << " calculated: " << t_total << "\n"
648 << " expected: " << t_no_reference+t2);
649 }
650}
651
652void DayCounterTest::testSimple() {
653
654 BOOST_TEST_MESSAGE("Testing simple day counter...");
655
656 Period p[] = { Period(3,Months), Period(6,Months), Period(1,Years) };
657 Time expected[] = { 0.25, 0.5, 1.0 };
658 Size n = sizeof(p)/sizeof(Period);
659
660 // 4 years should be enough
661 Date first(1,January,2002), last(31,December,2005);
662 DayCounter dayCounter = SimpleDayCounter();
663
664 for (Date start = first; start <= last; start++) {
665 for (Size i=0; i<n; i++) {
666 Date end = start + p[i];
667 Time calculated = dayCounter.yearFraction(d1: start,d2: end);
668 if (std::fabs(x: calculated-expected[i]) > 1.0e-12) {
669 BOOST_ERROR("from " << start << " to " << end << ":\n"
670 << std::setprecision(12)
671 << " calculated: " << calculated << "\n"
672 << " expected: " << expected[i]);
673 }
674 }
675 }
676}
677
678void DayCounterTest::testOne() {
679
680 BOOST_TEST_MESSAGE("Testing 1/1 day counter...");
681
682 Period p[] = { Period(3,Months), Period(6,Months), Period(1,Years) };
683 Time expected[] = { 1.0, 1.0, 1.0 };
684 Size n = sizeof(p)/sizeof(Period);
685
686 // 1 years should be enough
687 Date first(1,January,2004), last(31,December,2004);
688 DayCounter dayCounter = OneDayCounter();
689
690 for (Date start = first; start <= last; start++) {
691 for (Size i=0; i<n; i++) {
692 Date end = start + p[i];
693 Time calculated = dayCounter.yearFraction(d1: start,d2: end);
694 if (std::fabs(x: calculated-expected[i]) > 1.0e-12) {
695 BOOST_ERROR("from " << start << " to " << end << ":\n"
696 << std::setprecision(12)
697 << " calculated: " << calculated << "\n"
698 << " expected: " << expected[i]);
699 }
700 }
701 }
702}
703
704void DayCounterTest::testBusiness252() {
705
706 BOOST_TEST_MESSAGE("Testing business/252 day counter...");
707
708 std::vector<Date> testDates = {
709 Date(1, February, 2002),
710 Date(4, February, 2002),
711 Date(16, May, 2003),
712 Date(17, December, 2003),
713 Date(17, December, 2004),
714 Date(19, December, 2005),
715 Date(2, January, 2006),
716 Date(13, March, 2006),
717 Date(15, May, 2006),
718 Date(17, March, 2006),
719 Date(15, May, 2006),
720 Date(26, July, 2006),
721 Date(28, June, 2007),
722 Date(16, September, 2009),
723 Date(26, July, 2016)
724 };
725
726 Time expected[] = {
727 0.0039682539683,
728 1.2738095238095,
729 0.6031746031746,
730 0.9960317460317,
731 1.0000000000000,
732 0.0396825396825,
733 0.1904761904762,
734 0.1666666666667,
735 -0.1507936507937,
736 0.1507936507937,
737 0.2023809523810,
738 0.912698412698,
739 2.214285714286,
740 6.84126984127
741 };
742
743 DayCounter dayCounter1 = Business252(Brazil());
744
745 Time calculated;
746
747 for (Size i=1; i<testDates.size(); i++) {
748 calculated = dayCounter1.yearFraction(d1: testDates[i-1],d2: testDates[i]);
749 if (std::fabs(x: calculated-expected[i-1]) > 1.0e-12) {
750 BOOST_ERROR("from " << testDates[i-1]
751 << " to " << testDates[i] << ":\n"
752 << std::setprecision(14)
753 << " calculated: " << calculated << "\n"
754 << " expected: " << expected[i-1]);
755 }
756 }
757
758 DayCounter dayCounter2 = Business252();
759
760 for (Size i=1; i<testDates.size(); i++) {
761 calculated = dayCounter2.yearFraction(d1: testDates[i-1],d2: testDates[i]);
762 if (std::fabs(x: calculated-expected[i-1]) > 1.0e-12) {
763 BOOST_ERROR("from " << testDates[i-1]
764 << " to " << testDates[i] << ":\n"
765 << std::setprecision(14)
766 << " calculated: " << calculated << "\n"
767 << " expected: " << expected[i-1]);
768 }
769 }
770}
771
772void DayCounterTest::testThirty365() {
773
774 BOOST_TEST_MESSAGE("Testing 30/365 day counter...");
775
776 Date d1(17,June,2011), d2(30,December,2012);
777 DayCounter dayCounter = Thirty365();
778
779 BigInteger days = dayCounter.dayCount(d1,d2);
780 if (days != 553) {
781 BOOST_FAIL("from " << d1 << " to " << d2 << ":\n"
782 << " calculated: " << days << "\n"
783 << " expected: " << 553);
784 }
785
786 Time t = dayCounter.yearFraction(d1,d2);
787 Time expected = 553/365.0;
788 if (std::fabs(x: t-expected) > 1.0e-12) {
789 BOOST_FAIL("from " << d1 << " to " << d2 << ":\n"
790 << std::setprecision(12)
791 << " calculated: " << t << "\n"
792 << " expected: " << expected);
793 }
794}
795
796void DayCounterTest::testThirty360_BondBasis() {
797
798 BOOST_TEST_MESSAGE("Testing 30/360 day counter (Bond Basis)...");
799
800 // See https://www.isda.org/2008/12/22/30-360-day-count-conventions/
801
802 DayCounter dayCounter = Thirty360(Thirty360::BondBasis);
803
804 day_counters_test::Thirty360Case data[] = {
805 // Example 1: End dates do not involve the last day of February
806 {.start: Date(20, August, 2006), .end: Date(20, February, 2007), .expected: 180},
807 {.start: Date(20, February, 2007), .end: Date(20, August, 2007), .expected: 180},
808 {.start: Date(20, August, 2007), .end: Date(20, February, 2008), .expected: 180},
809 {.start: Date(20, February, 2008), .end: Date(20, August, 2008), .expected: 180},
810 {.start: Date(20, August, 2008), .end: Date(20, February, 2009), .expected: 180},
811 {.start: Date(20, February, 2009), .end: Date(20, August, 2009), .expected: 180},
812
813 // Example 2: End dates include some end-February dates
814 {.start: Date(31, August, 2006), .end: Date(28, February, 2007), .expected: 178},
815 {.start: Date(28, February, 2007), .end: Date(31, August, 2007), .expected: 183},
816 {.start: Date(31, August, 2007), .end: Date(29, February, 2008), .expected: 179},
817 {.start: Date(29, February, 2008), .end: Date(31, August, 2008), .expected: 182},
818 {.start: Date(31, August, 2008), .end: Date(28, February, 2009), .expected: 178},
819 {.start: Date(28, February, 2009), .end: Date(31, August, 2009), .expected: 183},
820
821 // Example 3: Miscellaneous calculations
822 {.start: Date(31, January, 2006), .end: Date(28, February, 2006), .expected: 28},
823 {.start: Date(30, January, 2006), .end: Date(28, February, 2006), .expected: 28},
824 {.start: Date(28, February, 2006), .end: Date(3, March, 2006), .expected: 5},
825 {.start: Date(14, February, 2006), .end: Date(28, February, 2006), .expected: 14},
826 {.start: Date(30, September, 2006), .end: Date(31, October, 2006), .expected: 30},
827 {.start: Date(31, October, 2006), .end: Date(28, November, 2006), .expected: 28},
828 {.start: Date(31, August, 2007), .end: Date(28, February, 2008), .expected: 178},
829 {.start: Date(28, February, 2008), .end: Date(28, August, 2008), .expected: 180},
830 {.start: Date(28, February, 2008), .end: Date(30, August, 2008), .expected: 182},
831 {.start: Date(28, February, 2008), .end: Date(31, August, 2008), .expected: 183},
832 {.start: Date(26, February, 2007), .end: Date(28, February, 2008), .expected: 362},
833 {.start: Date(26, February, 2007), .end: Date(29, February, 2008), .expected: 363},
834 {.start: Date(29, February, 2008), .end: Date(28, February, 2009), .expected: 359},
835 {.start: Date(28, February, 2008), .end: Date(30, March, 2008), .expected: 32},
836 {.start: Date(28, February, 2008), .end: Date(31, March, 2008), .expected: 33}
837 };
838
839 for (auto x : data) {
840 Date::serial_type calculated = dayCounter.dayCount(d1: x.start, d2: x.end);
841 if (calculated != x.expected) {
842 BOOST_ERROR("from " << x.start
843 << " to " << x.end << ":\n"
844 << " calculated: " << calculated << "\n"
845 << " expected: " << x.expected);
846 }
847 }
848}
849
850void DayCounterTest::testThirty360_EurobondBasis() {
851
852 BOOST_TEST_MESSAGE("Testing 30/360 day counter (Eurobond Basis)...");
853
854 // See https://www.isda.org/2008/12/22/30-360-day-count-conventions/
855
856 DayCounter dayCounter = Thirty360(Thirty360::EurobondBasis);
857
858 day_counters_test::Thirty360Case data[] = {
859 // Example 1: End dates do not involve the last day of February
860 {.start: Date(20, August, 2006), .end: Date(20, February, 2007), .expected: 180},
861 {.start: Date(20, February, 2007), .end: Date(20, August, 2007), .expected: 180},
862 {.start: Date(20, August, 2007), .end: Date(20, February, 2008), .expected: 180},
863 {.start: Date(20, February, 2008), .end: Date(20, August, 2008), .expected: 180},
864 {.start: Date(20, August, 2008), .end: Date(20, February, 2009), .expected: 180},
865 {.start: Date(20, February, 2009), .end: Date(20, August, 2009), .expected: 180},
866
867 // Example 2: End dates include some end-February dates
868 {.start: Date(28, February, 2006), .end: Date(31, August, 2006), .expected: 182},
869 {.start: Date(31, August, 2006), .end: Date(28, February, 2007), .expected: 178},
870 {.start: Date(28, February, 2007), .end: Date(31, August, 2007), .expected: 182},
871 {.start: Date(31, August, 2007), .end: Date(29, February, 2008), .expected: 179},
872 {.start: Date(29, February, 2008), .end: Date(31, August, 2008), .expected: 181},
873 {.start: Date(31, August, 2008), .end: Date(28, Feb, 2009), .expected: 178},
874 {.start: Date(28, February, 2009), .end: Date(31, August, 2009), .expected: 182},
875 {.start: Date(31, August, 2009), .end: Date(28, Feb, 2010), .expected: 178},
876 {.start: Date(28, February, 2010), .end: Date(31, August, 2010), .expected: 182},
877 {.start: Date(31, August, 2010), .end: Date(28, Feb, 2011), .expected: 178},
878 {.start: Date(28, February, 2011), .end: Date(31, August, 2011), .expected: 182},
879 {.start: Date(31, August, 2011), .end: Date(29, Feb, 2012), .expected: 179},
880
881 // Example 3: Miscellaneous calculations
882 {.start: Date(31, January, 2006), .end: Date(28, February, 2006), .expected: 28},
883 {.start: Date(30, January, 2006), .end: Date(28, February, 2006), .expected: 28},
884 {.start: Date(28, February, 2006), .end: Date(3, March, 2006), .expected: 5},
885 {.start: Date(14, February, 2006), .end: Date(28, February, 2006), .expected: 14},
886 {.start: Date(30, September, 2006), .end: Date(31, October, 2006), .expected: 30},
887 {.start: Date(31, October, 2006), .end: Date(28, November, 2006), .expected: 28},
888 {.start: Date(31, August, 2007), .end: Date(28, February, 2008), .expected: 178},
889 {.start: Date(28, February, 2008), .end: Date(28, August, 2008), .expected: 180},
890 {.start: Date(28, February, 2008), .end: Date(30, August, 2008), .expected: 182},
891 {.start: Date(28, February, 2008), .end: Date(31, August, 2008), .expected: 182},
892 {.start: Date(26, February, 2007), .end: Date(28, February, 2008), .expected: 362},
893 {.start: Date(26, February, 2007), .end: Date(29, February, 2008), .expected: 363},
894 {.start: Date(29, February, 2008), .end: Date(28, February, 2009), .expected: 359},
895 {.start: Date(28, February, 2008), .end: Date(30, March, 2008), .expected: 32},
896 {.start: Date(28, February, 2008), .end: Date(31, March, 2008), .expected: 32}
897 };
898
899 for (auto x : data) {
900 Date::serial_type calculated = dayCounter.dayCount(d1: x.start, d2: x.end);
901 if (calculated != x.expected) {
902 BOOST_ERROR("from " << x.start
903 << " to " << x.end << ":\n"
904 << " calculated: " << calculated << "\n"
905 << " expected: " << x.expected);
906 }
907 }
908}
909
910
911void DayCounterTest::testThirty360_ISDA() {
912
913 BOOST_TEST_MESSAGE("Testing 30/360 day counter (ISDA)...");
914
915 // See https://www.isda.org/2008/12/22/30-360-day-count-conventions/
916
917 day_counters_test::Thirty360Case data1[] = {
918 // Example 1: End dates do not involve the last day of February
919 {.start: Date(20, August, 2006), .end: Date(20, February, 2007), .expected: 180},
920 {.start: Date(20, February, 2007), .end: Date(20, August, 2007), .expected: 180},
921 {.start: Date(20, August, 2007), .end: Date(20, February, 2008), .expected: 180},
922 {.start: Date(20, February, 2008), .end: Date(20, August, 2008), .expected: 180},
923 {.start: Date(20, August, 2008), .end: Date(20, February, 2009), .expected: 180},
924 {.start: Date(20, February, 2009), .end: Date(20, August, 2009), .expected: 180},
925 };
926
927 Date terminationDate = Date(20, August, 2009);
928 Thirty360 dayCounter(Thirty360::ISDA, terminationDate);
929
930 for (auto x : data1) {
931 Date::serial_type calculated = dayCounter.dayCount(d1: x.start, d2: x.end);
932 if (calculated != x.expected) {
933 BOOST_ERROR("from " << x.start
934 << " to " << x.end << ":\n"
935 << " calculated: " << calculated << "\n"
936 << " expected: " << x.expected);
937 }
938 }
939
940 day_counters_test::Thirty360Case data2[] = {
941 // Example 2: End dates include some end-February dates
942 {.start: Date(28, February, 2006), .end: Date(31, August, 2006), .expected: 180},
943 {.start: Date(31, August, 2006), .end: Date(28, February, 2007), .expected: 180},
944 {.start: Date(28, February, 2007), .end: Date(31, August, 2007), .expected: 180},
945 {.start: Date(31, August, 2007), .end: Date(29, February, 2008), .expected: 180},
946 {.start: Date(29, February, 2008), .end: Date(31, August, 2008), .expected: 180},
947 {.start: Date(31, August, 2008), .end: Date(28, February, 2009), .expected: 180},
948 {.start: Date(28, February, 2009), .end: Date(31, August, 2009), .expected: 180},
949 {.start: Date(31, August, 2009), .end: Date(28, February, 2010), .expected: 180},
950 {.start: Date(28, February, 2010), .end: Date(31, August, 2010), .expected: 180},
951 {.start: Date(31, August, 2010), .end: Date(28, February, 2011), .expected: 180},
952 {.start: Date(28, February, 2011), .end: Date(31, August, 2011), .expected: 180},
953 {.start: Date(31, August, 2011), .end: Date(29, February, 2012), .expected: 179},
954 };
955
956 terminationDate = Date(29, February, 2012);
957 dayCounter = Thirty360(Thirty360::ISDA, terminationDate);
958
959 for (auto x : data2) {
960 Date::serial_type calculated = dayCounter.dayCount(d1: x.start, d2: x.end);
961 if (calculated != x.expected) {
962 BOOST_ERROR("from " << x.start
963 << " to " << x.end << ":\n"
964 << " calculated: " << calculated << "\n"
965 << " expected: " << x.expected);
966 }
967 }
968
969 day_counters_test::Thirty360Case data3[] = {
970 // Example 3: Miscellaneous calculations
971 {.start: Date(31, January, 2006), .end: Date(28, February, 2006), .expected: 30},
972 {.start: Date(30, January, 2006), .end: Date(28, February, 2006), .expected: 30},
973 {.start: Date(28, February, 2006), .end: Date(3, March, 2006), .expected: 3},
974 {.start: Date(14, February, 2006), .end: Date(28, February, 2006), .expected: 16},
975 {.start: Date(30, September, 2006), .end: Date(31, October, 2006), .expected: 30},
976 {.start: Date(31, October, 2006), .end: Date(28, November, 2006), .expected: 28},
977 {.start: Date(31, August, 2007), .end: Date(28, February, 2008), .expected: 178},
978 {.start: Date(28, February, 2008), .end: Date(28, August, 2008), .expected: 180},
979 {.start: Date(28, February, 2008), .end: Date(30, August, 2008), .expected: 182},
980 {.start: Date(28, February, 2008), .end: Date(31, August, 2008), .expected: 182},
981 {.start: Date(28, February, 2007), .end: Date(28, February, 2008), .expected: 358},
982 {.start: Date(28, February, 2007), .end: Date(29, February, 2008), .expected: 359},
983 {.start: Date(29, February, 2008), .end: Date(28, February, 2009), .expected: 360},
984 {.start: Date(29, February, 2008), .end: Date(30, March, 2008), .expected: 30},
985 {.start: Date(29, February, 2008), .end: Date(31, March, 2008), .expected: 30}
986 };
987
988 terminationDate = Date(29, February, 2008);
989 dayCounter = Thirty360(Thirty360::ISDA, terminationDate);
990
991 for (auto x : data3) {
992 Date::serial_type calculated = dayCounter.dayCount(d1: x.start, d2: x.end);
993 if (calculated != x.expected) {
994 BOOST_ERROR("from " << x.start
995 << " to " << x.end << ":\n"
996 << " calculated: " << calculated << "\n"
997 << " expected: " << x.expected);
998 }
999 }
1000}
1001
1002
1003void DayCounterTest::testActual365_Canadian() {
1004
1005 BOOST_TEST_MESSAGE("Testing that Actual/365 (Canadian) throws when needed...");
1006
1007 Actual365Fixed dayCounter(Actual365Fixed::Canadian);
1008
1009 try {
1010 // no reference period
1011 dayCounter.yearFraction(d1: Date(10, September, 2018),
1012 d2: Date(10, September, 2019));
1013 BOOST_ERROR("Invalid call to yearFraction failed to throw");
1014 } catch (Error&) {
1015 ; // expected
1016 }
1017
1018 try {
1019 // reference period shorter than a month
1020 dayCounter.yearFraction(d1: Date(10, September, 2018),
1021 d2: Date(12, September, 2018),
1022 refPeriodStart: Date(10, September, 2018),
1023 refPeriodEnd: Date(15, September, 2018));
1024 BOOST_ERROR("Invalid call to yearFraction failed to throw");
1025 } catch (Error&) {
1026 ; // expected
1027 }
1028}
1029
1030
1031void DayCounterTest::testIntraday() {
1032#ifdef QL_HIGH_RESOLUTION_DATE
1033
1034 BOOST_TEST_MESSAGE("Testing intraday behavior of day counter...");
1035
1036 const Date d1(12, February, 2015);
1037 const Date d2(14, February, 2015, 12, 34, 17, 1, 230298);
1038
1039 const Time tol = 100*QL_EPSILON;
1040
1041 const DayCounter dayCounters[]
1042 = { ActualActual(ActualActual::ISDA), Actual365Fixed(), Actual360() };
1043
1044 for (DayCounter dc : dayCounters) {
1045 const Time expected = ((12*60 + 34)*60 + 17 + 0.231298)
1046 * dc.yearFraction(d1, d1+1)/86400
1047 + dc.yearFraction(d1, d1+2);
1048
1049 BOOST_CHECK_MESSAGE(
1050 std::fabs(dc.yearFraction(d1, d2) - expected) < tol,
1051 "can not reproduce result for day counter " << dc.name());
1052
1053 BOOST_CHECK_MESSAGE(
1054 std::fabs(dc.yearFraction(d2, d1) + expected) < tol,
1055 "can not reproduce result for day counter " << dc.name());
1056 }
1057#endif
1058}
1059
1060void DayCounterTest::testActualActualOutOfScheduleRange() {
1061 BOOST_TEST_MESSAGE("Testing usage of actual/actual out of schedule...");
1062
1063 Date today = Date(10, November, 2020);
1064 Date temp = Settings::instance().evaluationDate();
1065 Settings::instance().evaluationDate() = today;
1066
1067 Date effectiveDate = Date(21, May, 2019);
1068 Date terminationDate = Date(21, May, 2029);
1069 Period tenor = Period(1, Years);
1070 Calendar calendar = China(China::Market::IB);
1071 BusinessDayConvention convention = Unadjusted;
1072 BusinessDayConvention terminationDateConvention = convention;
1073 DateGeneration::Rule rule = DateGeneration::Backward;
1074 bool endOfMonth = false;
1075
1076 Schedule schedule = Schedule(effectiveDate, terminationDate, tenor, calendar, convention,
1077 terminationDateConvention, rule, endOfMonth);
1078 DayCounter dayCounter = ActualActual(ActualActual::Convention::Bond, schedule);
1079 bool raised = false;
1080
1081 try {
1082 dayCounter.yearFraction(d1: today, d2: today + Period(9, Years));
1083 } catch (const std::exception&) {
1084 raised = true;
1085 }
1086
1087 if (!raised) {
1088 BOOST_FAIL("Exception expected but did not happen!");
1089 }
1090
1091 Settings::instance().evaluationDate() = temp;
1092}
1093
1094
1095void DayCounterTest::testAct366() {
1096
1097 BOOST_TEST_MESSAGE("Testing Act/366 day counter...");
1098
1099 std::vector<Date> testDates = {
1100 Date(1, February, 2002),
1101 Date(4, February, 2002),
1102 Date(16, May, 2003),
1103 Date(17, December, 2003),
1104 Date(17, December, 2004),
1105 Date(19, December, 2005),
1106 Date(2, January, 2006),
1107 Date(13, March, 2006),
1108 Date(15, May, 2006),
1109 Date(17, March, 2006),
1110 Date(15, May, 2006),
1111 Date(26, July, 2006),
1112 Date(28, June, 2007),
1113 Date(16, September, 2009),
1114 Date(26, July, 2016)
1115 };
1116
1117 Time expected[] = {
1118 0.00819672131147541,
1119 1.27322404371585,
1120 0.587431693989071,
1121 1.0000000000000,
1122 1.00273224043716,
1123 0.0382513661202186,
1124 0.191256830601093,
1125 0.172131147540984,
1126 -0.16120218579235,
1127 0.16120218579235,
1128 0.19672131147541,
1129 0.920765027322404,
1130 2.21584699453552,
1131 6.84426229508197
1132 };
1133
1134 DayCounter dayCounter = Actual366();
1135
1136 Time calculated;
1137
1138 for (Size i=1; i<testDates.size(); i++) {
1139 calculated = dayCounter.yearFraction(d1: testDates[i-1],d2: testDates[i]);
1140 if (std::fabs(x: calculated-expected[i-1]) > 1.0e-12) {
1141 BOOST_ERROR("from " << testDates[i-1]
1142 << " to " << testDates[i] << ":\n"
1143 << std::setprecision(14)
1144 << " calculated: " << calculated << "\n"
1145 << " expected: " << expected[i-1]);
1146 }
1147 }
1148}
1149
1150void DayCounterTest::testAct36525() {
1151
1152 BOOST_TEST_MESSAGE("Testing Act/365.25 day counter...");
1153
1154 std::vector<Date> testDates = {
1155 Date(1, February, 2002),
1156 Date(4, February, 2002),
1157 Date(16, May, 2003),
1158 Date(17, December, 2003),
1159 Date(17, December, 2004),
1160 Date(19, December, 2005),
1161 Date(2, January, 2006),
1162 Date(13, March, 2006),
1163 Date(15, May, 2006),
1164 Date(17, March, 2006),
1165 Date(15, May, 2006),
1166 Date(26, July, 2006),
1167 Date(28, June, 2007),
1168 Date(16, September, 2009),
1169 Date(26, July, 2016)
1170 };
1171
1172 Time expected[] = {
1173 0.0082135523613963,
1174 1.27583846680356,
1175 0.588637919233402,
1176 1.00205338809035,
1177 1.00479123887748,
1178 0.0383299110198494,
1179 0.191649555099247,
1180 0.172484599589322,
1181 -0.161533196440794,
1182 0.161533196440794,
1183 0.197125256673511,
1184 0.922655715263518,
1185 2.22039698836413,
1186 6.85831622176591
1187 };
1188
1189 DayCounter dayCounter = Actual36525();
1190
1191 Time calculated;
1192
1193 for (Size i=1; i<testDates.size(); i++) {
1194 calculated = dayCounter.yearFraction(d1: testDates[i-1],d2: testDates[i]);
1195 if (std::fabs(x: calculated-expected[i-1]) > 1.0e-12) {
1196 BOOST_ERROR("from " << testDates[i-1]
1197 << " to " << testDates[i] << ":\n"
1198 << std::setprecision(14)
1199 << " calculated: " << calculated << "\n"
1200 << " expected: " << expected[i-1]);
1201 }
1202 }
1203}
1204
1205
1206void DayCounterTest::testActualConsistency() {
1207 BOOST_TEST_MESSAGE("Testing consistency between different actual day-counters...");
1208
1209 const std::vector<Date> todayDates = {
1210 Date(12, January, 2022)
1211#ifdef QL_HIGH_RESOLUTION_DATE
1212 ,
1213 Date(7, February, 2022, 11, 43, 12, 293, 32)
1214#endif
1215 };
1216
1217 const std::vector<Date> testDates = {
1218 Date(1, February, 2023), Date(4, February, 2023), Date(16, May, 2024),
1219 Date(17, December, 2024),Date(17, December, 2025), Date(19, December, 2026),
1220 Date(2, January, 2027), Date(13, March, 2028), Date(15, May, 2028),
1221 Date(26, July, 2036)
1222#ifdef QL_HIGH_RESOLUTION_DATE
1223 ,
1224 Date(23, August, 2025, 18, 1, 22, 927, 832),
1225 Date(23, August, 2032, 2, 23, 22, 0, 636)
1226#endif
1227 };
1228
1229 const DayCounter actual365 = Actual365Fixed();
1230 const DayCounter actual366 = Actual366();
1231 const DayCounter actual364 = Actual364();
1232 const DayCounter actual36525 = Actual36525();
1233 const DayCounter actual360 = Actual360();
1234 const DayCounter actual360incl = Actual360(true);
1235
1236 for (const auto& today: todayDates)
1237 for (const auto& d: testDates) {
1238 const Time t365 = actual365.yearFraction(d1: today, d2: d);
1239 const Time t366 = actual366.yearFraction(d1: today, d2: d);
1240 const Time t364 = actual364.yearFraction(d1: today, d2: d);
1241 const Time t360 = actual360.yearFraction(d1: today, d2: d);
1242 const Time t360incl = actual360incl.yearFraction(d1: today, d2: d);
1243 const Time t36525 = actual36525.yearFraction(d1: today, d2: d);
1244
1245 QL_CHECK_SMALL(t365*365/366.0 - t366, 1e-14);
1246 QL_CHECK_SMALL(t365*365/364.0 - t364, 1e-14);
1247 QL_CHECK_SMALL(t365*365/360.0 - t360, 1e-14);
1248 QL_CHECK_SMALL(t365*365/364.0 - t364, 1e-14);
1249 QL_CHECK_SMALL(t365*365/365.25 - t36525, 1e-14);
1250 QL_CHECK_SMALL(t365*365/360.0 - (t360incl*360-1)/360, 1e-14);
1251 }
1252}
1253
1254
1255void DayCounterTest::testYearFraction2DateBulk() {
1256 BOOST_TEST_MESSAGE("Testing bulk dates for YearFractionToDate ...");
1257
1258 const auto dayCounters = std::vector<DayCounter>{
1259 Actual365Fixed(),
1260 Actual365Fixed(Actual365Fixed::NoLeap),
1261 Actual360(), Actual360(true),
1262 Actual36525(), Actual36525(true),
1263 Actual364(),
1264 Actual366(), Actual366(true),
1265 ActualActual(ActualActual::ISDA),
1266 ActualActual(ActualActual::ISMA),
1267 ActualActual(ActualActual::Bond),
1268 ActualActual(ActualActual::Historical),
1269 ActualActual(ActualActual::Actual365),
1270 ActualActual(ActualActual::AFB),
1271 ActualActual(ActualActual::Euro),
1272 Business252(),
1273 Thirty360(Thirty360::USA),
1274 Thirty360(Thirty360::BondBasis),
1275 Thirty360(Thirty360::European),
1276 Thirty360(Thirty360::EurobondBasis),
1277 Thirty360(Thirty360::Italian),
1278 Thirty360(Thirty360::German),
1279 Thirty360(Thirty360::ISMA),
1280 Thirty360(Thirty360::ISDA),
1281 Thirty360(Thirty360::NASD),
1282 Thirty365(),
1283 SimpleDayCounter()
1284 };
1285
1286 for (const auto& dc : dayCounters)
1287 for (Integer i=-360; i < 730; ++i) {
1288 const Date today = Date(1, January, 2020) + Period(i, Days);
1289 const Date target = today + Period(i, Days);
1290
1291 const Time t = dc.yearFraction(d1: today, d2: target);
1292 const Date time2Date = yearFractionToDate(dayCounter: dc, referenceDate: today, t);
1293 const Time tNew = dc.yearFraction(d1: today, d2: time2Date);
1294
1295 if (!close_enough(x: t, y: tNew)) {
1296 BOOST_FAIL(
1297 "\ntoday : " << today
1298 << "\ntarget : " << target
1299 << "\ninverse : " << time2Date
1300 << "\ntime diff : " << t - tNew
1301 << "\nday counter: " << dc.name()
1302 );
1303 }
1304 }
1305}
1306
1307void DayCounterTest::testYearFraction2DateRounding() {
1308 BOOST_TEST_MESSAGE("Testing YearFractionToDate rounding to closer date...");
1309
1310 const std::vector<DayCounter> dayCounters
1311 = {Thirty360(Thirty360::USA), Actual360()};
1312 const Date d1(1, February, 2023), d2(17, February, 2124);
1313
1314 for (const DayCounter& dc : dayCounters) {
1315 Time t = dc.yearFraction(d1, d2);
1316 for (Time offset = 0; offset < 1 + 1e-10; offset+=0.05) {
1317 const Date inv = yearFractionToDate(dayCounter: dc, referenceDate: d1, t: t + offset/360);
1318 if (offset < 0.4999)
1319 BOOST_CHECK_EQUAL(inv, d2);
1320 else
1321 BOOST_CHECK_EQUAL(inv, d2 + Period(1, Days));
1322 }
1323 }
1324}
1325
1326
1327
1328test_suite* DayCounterTest::suite() {
1329 auto* suite = BOOST_TEST_SUITE("Day counter tests");
1330 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActualActual));
1331 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActualActualIsma));
1332 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActualActualWithSemiannualSchedule));
1333 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActualActualWithAnnualSchedule));
1334 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActualActualWithSchedule));
1335 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testSimple));
1336 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testOne));
1337 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testBusiness252));
1338 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testThirty365));
1339 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testThirty360_BondBasis));
1340 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testThirty360_EurobondBasis));
1341 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testThirty360_ISDA));
1342 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActual365_Canadian));
1343 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActualActualOutOfScheduleRange));
1344 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testAct366));
1345 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testAct36525));
1346 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testActualConsistency));
1347 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testYearFraction2DateBulk));
1348 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testYearFraction2DateRounding));
1349
1350#ifdef QL_HIGH_RESOLUTION_DATE
1351 suite->add(QUANTLIB_TEST_CASE(&DayCounterTest::testIntraday));
1352#endif
1353
1354 return suite;
1355}
1356

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