1 votos

QuantLib python ql.schedule obtener fechas finales de mes

Estoy tratando de obtener el calendario de pagos de un bono que paga cupones trimestralmente. La fecha de valor es 2020-04-01, la primera fecha de cupón es 2020-06-30, y la fecha de vencimiento es 2022-09-30. Así que espero tener una lista de fechas:

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

Sin embargo, cuando uso el siguiente calendario

list(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'),
))

Obtuve las siguientes salidas, para marzo y diciembre no es el fin de mes. Puedo ver que hay endOfMonth pero si lo configuro en verdadero también cambia 2020-04-01 a 2020-04-30 lo cual no quiero que suceda. Según la documentación, endOfMonth se puede usar si la fecha de inicio es al final del mes, si se requiere que otras fechas sean programadas al final del mes (excepto la última fecha). Y en mi caso, la fecha de inicio no es al final del mes. ¿Alguna sugerencia para lograr las fechas de pago de cupones correctas?

2020-04-01

2020-06-30

2020-09-30

2020-12-30

2021-03-30

2021-06-30

2021-09-30

2021-12-30

2022-03-30

2022-06-30

2022-09-30

4voto

Chris Mc Puntos 31

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)

ingresar descripción de la imagen aquí

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)

ingresar descripción de la imagen aquí

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])

2voto

David Radcliffe Puntos 136

Me gustaría agregar un poco de color a la excelente respuesta de David Duarte.

Cuando se origina un bono corporativo o de mercados emergentes, alguna persona junior se encarga de hacer la lista de fechas de pago de cupones para el prospecto. Hoy en día suelen usar una herramienta que comprende convenciones comunes, pero hace décadas lo hacían manualmente y a veces se equivocaban. Solía verificar que mi biblioteca (no ql, pero un programador similar, un poco más flexible) coincidiera con las fechas en el prospecto, y terminé escribiendo algunas líneas de código en C++ que tomaban como entrada las fechas del prospecto y los parámetros en la terminal de Bloomberg (si estaban disponibles) y probaban diferentes combinaciones de los parámetros del programador intentando que coincidieran las fechas. A veces nos encontrábamos con un bono donde esto fallaba, por lo que el calendario del prospecto tenía que ser una lista personalizada de fechas. Esto rara vez sucede en la actualidad, no fue necesario para su bono, pero operacionalmente se debe estar preparado para esta eventualidad.

El código también indicaba si es necesario especificar firstDate para un primer período impar y penultimate (nextToLastDate) para un último período impar. En mi opinión, es mejor práctica no pasarlos si son redundantes, solo pásalos si hacen una diferencia. Muchos bonos tienen períodos iniciales y/o finales impares. (Muy pocos bonos tienen períodos impares en medio del calendario).

Puede ser útil escribir una herramienta como esta para el programador de QL y contribuirla a la comunidad.

Por favor, ten en cuenta que no existe algo como "USD Holiday". Mientras que para muchas otras monedas, hay un calendario que todos usan, por lo que se puede llamar al calendario de Londres "GBP Calendar", esto simplemente no funciona para EE. UU. Existen diferentes festivos que se utilizan en EE. UU. para acciones (NYSE) y bonos, y algunos otros también. Usar ql.UnitedStates() sin especificar a qué calendario te refieres (por ejemplo, GovernmentBond) puede llevar a confusiones. Echa un vistazo a https://www.sifma.org/resources/general/holiday-schedule/ y desplázate hasta "U.S. Holiday Recommendations". Haz clic en 2021, observa que dice "Cierre temprano (2:00 p.m., hora del este): viernes, 31 de diciembre de 2021".

Intentemos tu fecha problemática en QL:

NewYearsEve = ql.Date(31,12,2021) # Debido a que el día de Año Nuevo, 1 de enero de 2022, es un sábado, algunos calendarios de festivos tienen un festivo observado aquí

ql.UnitedStates().isBusinessDay(NewYearsEve) # ¡Incorrecto!

ql.UnitedStates(ql.UnitedStates.Settlement).isBusinessDay(NewYearsEve)

ql.UnitedStates(ql.UnitedStates.LiborImpact).isBusinessDay(NewYearsEve)

son Falso, pero

ql.UnitedStates(ql.UnitedStates.NYSE).isBusinessDay(NewYearsEve)

ql.UnitedStates(ql.UnitedStates.GovernmentBond).isBusinessDay(NewYearsEve)

ql.UnitedStates(ql.UnitedStates.NERC).isBusinessDay(NewYearsEve)

ql.UnitedStates(ql.UnitedStates.FederalReserve).isBusinessDay(NewYearsEve)

son Verdadero. No veo ninguna razón para no tener un cupón de bono en esta fecha.

Un ejemplo de acciones y bonos que utilizan calendarios diferentes

IndigenousPeoplesDay = ql.Date(11,10,2021) # también conocido como Día de Acción de Gracias en Canadá, Día de Colón

ql.UnitedStates().isBusinessDay(IndigenousPeoplesDay) # ¡Incorrecto!

ql.UnitedStates(ql.UnitedStates.Settlement).isBusinessDay(IndigenousPeoplesDay) )

ql.UnitedStates(ql.UnitedStates.GovernmentBond).isBusinessDay(IndigenousPeoplesDay)

son Falso, pero

ql.UnitedStates(ql.UnitedStates.NYSE).isBusinessDay(IndigenousPeoplesDay)

es Verdadero: puedes operar acciones.

Del mismo modo, Quantlib tiene UnitedKingdom()
https://rkapl123.github.io/QLAnnotatedSource/d4/deb/class_quant_lib_1_1_united_kingdom.html y festivos de Brazil(), que también presentan problemas similares. Por ejemplo, en Escocia, el Lunes de Pascua no es festivo bancario, pero el Hogmanay sí lo es, y el festivo del Banco de Verano es el primer lunes de agosto, en lugar del último. En Brasil, los festivos en los que no se publica el CDI (tasa de interés nocturna similar a SOFR) no son los mismos que los festivos en los que no se negocian los futuros de DI en bolsa.

La función CDR en la Terminal de Bloomberg es un excelente lugar para explorar los calendarios de festivos si te interesa.

Finanhelp.com

FinanHelp es una comunidad para personas con conocimientos de economía y finanzas, o quiere aprender. Puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X