QuantLib
QuantLib copied to clipboard
EOM Schedule Generation
Consider the schedules s1
and s2
generated by the following code
#include <iostream>
#include <ql/time/calendars/nullcalendar.hpp>
#include <ql/time/schedule.hpp>
using namespace QuantLib;
int main() {
Schedule s1(Date(15, January, 2020), Date(30, June, 2022), 1 * Months, NullCalendar(), Unadjusted, Unadjusted,
DateGeneration::Forward, true, Date(31, January, 2020), Null<Date>());
Schedule s2(Date(15, January, 2020), Date(30, June, 2022), 1 * Months, NullCalendar(), Unadjusted, Unadjusted,
DateGeneration::Backward, true, Date(31, January, 2020), Null<Date>());
std::cout << "Schedule 1:" << std::endl;
for (auto const &d : s1)
std::cout << d << std::endl;
std::cout << "\nSchedule 2:" << std::endl;
for (auto const &d : s2)
std::cout << d << std::endl;
return 0;
}
While s2
looks sort of expected, s1
is missing out the front date 15-01-2020
, see the output below.
I wonder if this is intended or a bug in the schedule generation?
Schedule 1:
January 31st, 2020
February 29th, 2020
March 31st, 2020
April 30th, 2020
May 31st, 2020
June 30th, 2020
July 31st, 2020
August 31st, 2020
September 30th, 2020
October 31st, 2020
November 30th, 2020
December 31st, 2020
January 31st, 2021
February 28th, 2021
March 31st, 2021
April 30th, 2021
May 31st, 2021
June 30th, 2021
July 31st, 2021
August 31st, 2021
September 30th, 2021
October 31st, 2021
November 30th, 2021
December 31st, 2021
January 31st, 2022
February 28th, 2022
March 31st, 2022
April 30th, 2022
May 31st, 2022
June 30th, 2022
Schedule 2:
January 15th, 2020
January 31st, 2020
February 29th, 2020
March 31st, 2020
April 30th, 2020
May 31st, 2020
June 30th, 2020
July 31st, 2020
August 31st, 2020
September 30th, 2020
October 31st, 2020
November 30th, 2020
December 31st, 2020
January 31st, 2021
February 28th, 2021
March 31st, 2021
April 30th, 2021
May 31st, 2021
June 30th, 2021
July 31st, 2021
August 31st, 2021
September 30th, 2021
October 31st, 2021
November 30th, 2021
December 31st, 2021
January 31st, 2022
February 28th, 2022
March 31st, 2022
April 30th, 2022
May 31st, 2022
June 30th, 2022
Do we think this is a bug? I am happy to work on this in this case.
I think in the first case there's code that clips the start date to the end of month. It did look wrong to me, but I'm not sure. Glad to hear a second opinion 😄
I suppose the same happens in reverse if you go backwards from a maturity in the middle of the month?
I'd have to check the second case, I don't know at the moment. I will have a look.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
I'm also having this issue; two more test cases in case it may be helpful:
void ScheduleTest::testEomKeepsStart() {
Schedule s1 = Schedule(
Date(3, August, 2021),
Date(31, July, 2024),
Period(6, Months),
UnitedStates(),
Unadjusted,
Unadjusted,
DateGeneration::Forward,
true,
Date(31, January, 2022),
Date(31, January, 2024)
);
BOOST_CHECK_EQUAL(s1[0], Date(3, August, 2021));
Schedule s2 = Schedule(
Date(21, December, 2020),
Date(16, February, 2031),
Period(6, Months),
UnitedStates(),
Unadjusted,
Unadjusted,
DateGeneration::Forward,
true,
Date(31, March, 2021),
Date(30, September, 2030)
);
BOOST_CHECK_EQUAL(s2[0], Date(21, December, 2020));
}
Ciao all, as Luigi suggested there is code that clips the start date of the schedule plan to the end of the month. It seems that this behaviour is happening when the Schedule constructor is invoked with: -endOfMonth argument true -DateGeneration::Rule Forward -terminationDateConvention Unadjusted -firstDate is on or after the last business day for that month -effectiveDate and firstDate are falling in the same month and in the same year
The code is in schedule.cpp, lines 370-386
Date d1 = dates_.front(), d2 = dates_.back();
if (terminationDateConvention != Unadjusted) {
d1 = calendar_.endOfMonth(dates_.front());
d2 = calendar_.endOfMonth(dates_.back());
} else {
// the termination date is the first if going backwards,
// the last otherwise.
if (*rule_ == DateGeneration::Backward)
d2 = Date::endOfMonth(dates_.back());
else
d1 = Date::endOfMonth(dates_.front());
}
// if the eom adjustment leads to a single date schedule
// we do not apply it
if(d1 != d2) {
dates_.front() = d1;
dates_.back() = d2;
Consider the following example: effectiveDate 16 January 2023, terminationDate 28 February 2023, firstDate 31 January 2023. A simple plan with 3 schedule lines. In the code we have d1 = dates_.front() = effective date, d2 = dates_.back = termination date. The assignment d1 = Date::endOfMonth(dates_.front()) will move the effective date at the end of that month, therefore it will be the same as the first date. With the assignment dates_.front() = d1 the first schedule line with the effective date is lost. Maybe in order to manage this exceptional case we could write something like this:
else if (effectiveDate.month() != firstDate_.month() || effectiveDate.year() != firstDate_.year())
d1 = Date::endOfMonth(dates_.front());
Any feedback from QuantLib experienced developers would be appreciated. Here is the source code used to test the case
#include <iostream>
#include <ql/time/all.hpp>
using namespace QuantLib;
int main(){
Schedule s1 =
MakeSchedule().from(Date(16, January, 2023))
.to(Date(28, February, 2023))
.withCalendar(NullCalendar())
.withTenor(1 * Months)
.withConvention(Unadjusted)
.withTerminationDateConvention(Unadjusted)
.forwards()
.endOfMonth()
.withFirstDate(Date(31, January, 2023));
for(auto const &d:s1)
std::cout << d << '\n';
return 0;
}
Hello @lballabio, which is your opinion about this issue? Could it be a corner case happening when effectiveDate and firstDate are falling in the same month and in the same year AND the effectiveDate is moved at the end of the month, effectively becoming equal to the firstDate? If you think that the analysis above makes sense I will try to give it a shot with a PR. Thanks.
Hopefully fixed by #1509.