El constructor de un Calendario
en QuantLib es:
ql.Schedule(fechaEfectiva, fechaTerminacion, plazo,
calendario, convencion, convencionFechaTerminacion, regla,
finDeMes, primeraFecha=Date(), penultimaFecha=Date()
)
De la forma en que lo has definido, básicamente estás estableciendo el rolleo en el día 30 y por eso tienes diferencia en esas 4 fechas:
misfechas = [
'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',
]
calendario = 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({
"misfechas": misfechas,
"fechasQL": [dt.ISO() for dt in calendario]
}).style.apply(lambda row: ["background: red; color: white"]*2 if row[0] != row[1] else [""]*2, axis = 1)
También, ten en cuenta que una de las fechas en tu lista no es hábil en el calendario de ql.UnitedStates
, por lo que no puedes generar esas fechas con una convención de día hábil ajustado:
calendario = ql.UnitedStates()
[calendario.isBusinessDay(ql.Date(dt, '%Y-%m-%d')) for dt in misfechas]
[True, True, True, True, True, True, True, False, True, True, True]
Por lo que mi sugerencia sería:
- Cambiar las convenciones a No ajustado ya que deseas una fecha que no es hábil
- Cambiar finDeMes a True
- Cambiar la regla de generación de fechas a Hacia atrás
-
Quitar el parámetro primeraFecha ya que no tienes ningún fragmento (y en realidad estabas estableciendo la fecha de rolleo en el 30 con esto)
calendario = 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({
"misfechas": misfechas,
"fechasQL": [dt.ISO() for dt in calendario]
}).style.apply(lambda row: ["background: red"]2 if row[0] != row[1] else [""]2, axis = 1)
Editar después de los comentarios: Como Dimitri señaló muy bien, no hay un solo calendario de "USD Holiday" y lo que tienes son varias combinaciones de feriados estadounidenses utilizados por diferentes mercados/clases de activos/bolsas.
En QuantLib, puedes especificar algunas de estas. Por ejemplo, esa fecha en cuestión (31-12-2021) será tratada de manera diferente en las alternativas:
mifecha = ql.Date(31,12,2021)
calendario = ql.UnitedStates()
alts = ['FederalReserve', 'GovernmentBond', 'LiborImpact', 'NERC', 'NYSE', 'Settlement']
for alt in alts:
micarlos = eval(f'ql.UnitedStates(ql.UnitedStates.{alt})')
print(alt, micarlos.isBusinessDay(mifecha))
FederalReserve True
GovernmentBond True
LiborImpact False
NERC True
NYSE True
Settlement False
También un punto muy acertado sobre los calendarios "generados por junior" y lo he visto algunas veces. Algunos calendarios, ya sea para bonos o derivados, simplemente no pueden generarse por reglas de convención de mercado, y en casos extremos tal vez simplemente tengas que insertarlos manualmente. En QuantLib, puedes hacerlo así:
calendario = ql.Schedule([ql.Date(dt, '%Y-%m-%d') for dt in misfechas])
print([*calendario])