How End-of-Month Scheduling Can Interact with Business-Day Conventions
Summary
The question examines a monthly QuantLib schedule with an end-of-month flag, a Following business-day convention, and a holiday on March 31. The generated schedule places a date on March 30 rather than rolling it forward as the questioner expected. The replies treat this behavior as likely a QuantLib bug or a problem in how the schedule is constructed, rather than confirming it as market practice.
One explanation is that end-of-month handling may have become associated with moving from one month-end business day to another, reflecting conventions used in some interest-rate swaps. Another reply says the flag should resolve an ambiguous roll date, and should have little effect when the roll day is already clear; it suggests checking the roll setting and compares the result with another schedule library, whose adjusted accrual dates roll into April. The exchange is a debugging discussion, not a definitive statement of market convention, and it recommends reporting the behavior to the library maintainers.
Key ideas
- An end-of-month flag can affect schedule generation alongside a business-day adjustment convention.
- The example produces March 30 despite a Following convention and a holiday on March 31.
- Respondents suspect a library bug or schedule-construction issue rather than establishing accepted market practice.
- One explanation links the behavior to conventions that move dates between month-end business days.
- Comparing schedule outputs across libraries can help isolate unexpected date-adjustment behavior.
Tags
Full text
# End of Month (Eom) Rule overrides convention rule in Quantlib Schedule Is this the Market Practice?
# End of Month (Eom) Rule overrides convention rule in Quantlib Schedule Is this the Market Practice?
```
import QuantLib as ql
startdate = ql.Date(31,1,2020)
maturityDate = ql.Date(31,1,2021)
calendar = ql.NullCalendar()
tenor = ql.Period(ql.Monthly)
calendar.addHoliday(ql.Date(31,3,2020))
aschedule =ql.Schedule(startdate,maturityDate,tenor,calendar,ql.Following,ql.Unadjusted,ql.DateGeneration.Forward,True)
list(aschedule)
Output [Date(31,1,2020), Date(29,2,2020), Date(30,3,2020), Date(30,4,2020), Date(31,5,2020), Date(30,6,2020), Date(31,7,2020), Date(31,8,2020), Date(30,9,2020), Date(31,10,2020), Date(30,11,2020), Date(31,12,2020), Date(31,1,2021)]
```
Even though I have specified ql.Following for the date adjustment convention, the schedule generates a date of 30 March 2020 instead of the expected 1 April 2020. Given the holiday on 31 March 2020 and the End-of-Month (EOM) flag being set to True, I expected the date to roll forward to 1 April 2020.
It seems that when the endOfMonth flag is enabled, the day convention rule is ignored. Is this behavior consistent with market practice? Should I follow this approach?
## Answer by Luigi Ballabio (score 2, accepted)
https://quant.stackexchange.com/a/81710
I think it's a bug, as @Attack68 suggested, and it's a bug old enough to drink. My guess is that somehow, decades ago, eom=true was wrongly conflated with "jump from last business day of the month to last business day of the month", because that was the observed behavior for swaps using Euribor or LIBOR conventions (where eom=true and convention=Modified Following).
It would be helpful if you opened an issue on GitHub about this so it doesn't get forgotten.
## Answer by Attack68 (score 2)
https://quant.stackexchange.com/a/81707
This looks like either a bug in Quantlib or a bug in your construction (maybe its your Unadjusted flag).
"eom" as True/False is a flag that should be used to infer a roll date when it is ambiguous. In your case it is not ambiguous (Friday start and end are 31st) so this parameter shouldn't really have any impact. The "roll" is 31, which is synonymous with "eom".
Maybe you might be able to circumvent in Quantlib this by setting a roll as 31 and set EoM flag to False?
If you compare this Schedule generation with that in rateslib, you will see that it has a number of accrual start dates that roll into the next month.
```
from rateslib import Schedule, dt, Cal # Python 3.12, rateslib 1.7.0
cal = Cal(holidays=[dt(2020, 3, 31)], week_mask=[5,6])
s = Schedule(
effective=dt(2020, 1, 31),
termination=dt(2021, 1, 31),
frequency="M",
eom=True,
modifier="F",
calendar=cal,
)
print(s)
freq: M, stub: SHORTFRONT, roll: eom, pay lag: 2, modifier: F
Period Unadj Acc Start Unadj Acc End Acc Start Acc End Payment
0 Regular 2020-01-31 2020-02-29 2020-01-31 2020-03-02 2020-03-04
1 Regular 2020-02-29 2020-03-31 2020-03-02 2020-04-01 2020-04-03
2 Regular 2020-03-31 2020-04-30 2020-04-01 2020-04-30 2020-05-04
3 Regular 2020-04-30 2020-05-31 2020-04-30 2020-06-01 2020-06-03
4 Regular 2020-05-31 2020-06-30 2020-06-01 2020-06-30 2020-07-02
5 Regular 2020-06-30 2020-07-31 2020-06-30 2020-07-31 2020-08-04
6 Regular 2020-07-31 2020-08-31 2020-07-31 2020-08-31 2020-09-02
7 Regular 2020-08-31 2020-09-30 2020-08-31 2020-09-30 2020-10-02
8 Regular 2020-09-30 2020-10-31 2020-09-30 2020-11-02 2020-11-04
9 Regular 2020-10-31 2020-11-30 2020-11-02 2020-11-30 2020-12-02
10 Regular 2020-11-30 2020-12-31 2020-11-30 2020-12-31 2021-01-04
11 Regular 2020-12-31 2021-01-31 2020-12-31 2021-02-01 2021-02-03
```Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.