The constructor for a Schedule
in QuantLib is:
ql.Schedule(effectiveDate, terminationDate, tenor,
calendar, convention, terminationDateConvention, rule,
endOfMonth, firstDate=Date(), nextToLastDate=Date()
)
The way you have it defined, you are basically rolling on the 30th and that's why you have a difference in those 4 dates:
mydates = [
'2020-04-01',
'2020-06-30', '2020-09-30', '2020-12-31', '2021-03-31', '2021-06-30',
'2021-09-30', '2021-12-31', '2022-03-31', '2022-06-30', '2022-09-30',
]
schedule = ql.Schedule(
ql.Date('01-04-2020', '%d-%m-%Y'),
ql.Date('30-09-2022', '%d-%m-%Y'),
ql.Period("3m"),
ql.UnitedStates(),
ql.ModifiedFollowing,
ql.ModifiedFollowing,
ql.DateGeneration.Forward,
False,
ql.Date('30-06-2020', '%d-%m-%Y'))
pd.DataFrame({
"mydates": mydates,
"qlDates": [dt.ISO() for dt in schedule]
}).style.apply(lambda row: ["background: red; color: white"]*2 if row[0] != row[1] else [""]*2, axis = 1)
Also, notice that one of the dates in your list is not a business day in the ql.UnitedStated
calendar so you can't generate those dates with an adjusted business day convention:
cal = ql.UnitedStates()
[cal.isBusinessDay(ql.Date(dt, '%Y-%m-%d')) for dt in mydates]
[True, True, True, True, True, True, True, False, True, True, True]
So my sugestion would be:
- Change the conventions to Unadjusted since you want a date that is not a business day
- Change the endOfMonth to True
- Change the date generation rule to Backward
- Loose the firstDate parameter as you don't have any stub (and you were actually setting the roll date to the 30th with this)
schedule = ql.Schedule(
ql.Date('01-04-2020', '%d-%m-%Y'),
ql.Date('30-09-2022', '%d-%m-%Y'),
ql.Period("3m"),
ql.UnitedStates(),
ql.Unadjusted,
ql.Unadjusted,
ql.DateGeneration.Backward,
True)
pd.DataFrame({
"mydates": mydates,
"qlDates": [dt.ISO() for dt in schedule]
}).style.apply(lambda row: ["background: red"]*2 if row[0] != row[1] else [""]*2, axis = 1)
Edit after comments:
As Dimitri very well pointed out there is no single "USD Holiday" calendar and what you have are several combinations of US holidays used by different markets/asset classes/exchanges.
In QuantLib, you can specify some of these. For example, that date in question (31-12-2021) will be treated diferently throughout the alternatives:
mydate = ql.Date(31,12,2021)
cal = ql.UnitedStates()
alts = ['FederalReserve', 'GovernmentBond', 'LiborImpact', 'NERC', 'NYSE', 'Settlement']
for alt in alts:
mycalendar = eval(f'ql.UnitedStates(ql.UnitedStates.{alt})')
print(alt, mycalendar.isBusinessDay(mydate))
FederalReserve True
GovernmentBond True
LiborImpact False
NERC True
NYSE True
Settlement False
Also a great point about "junior generated" schedules and I've seen it a few times. Some schedules, and it can be for bonds or derivatives, simply can't be generated by market convention rules, and in extreme cases you might just have to insert them manually. In QuantLib, you can do it like so:
schedule = ql.Schedule([ql.Date(dt, '%Y-%m-%d') for dt in mydates])
print([*schedule])