2 votos

Comprendiendo la recuperación de la tasa de fijación de SOFR para fechas futuras en QuantLib

Estoy usando QuantLib para calcular el índice SOFR para los flujos de efectivo de un bono (ISIN: US025816CL12). Mi objetivo es comprender cómo QuantLib calcula la tasa de fijación SOFR para fechas futuras. Aquí está mi código:

SOFRindex = ql.OvernightIndex("SOFR", 1, ql.USDCurrency(), calendario, day_count, estructura_de_tasas_de_descuento)

bono = ql.FloatingRateBond(0, valor_nominal, horario, SOFRindex, day_count, spreads=[spread])

#mostrar flujos de efectivo del bono
for i, flujo_efectivo in enumerate(bono.cashflows()):
    d = flujo_efectivo.date()
    try:
        resultado = SOFRindex.fixing(d, True)
        print(f"Valor del índice para {d} es {round(resultado*100,2)}")
    except Exception as e:
        print(f"Valor del índice para {d} es No Data")

El código anterior funciona, pero no estoy seguro de cómo QuantLib determina las tasas SOFR para fechas futuras. La salida es la siguiente:

Valor del índice para el 4 de agosto de 2023 es 5.17
Valor del índice para el 6 de noviembre de 2023 es 5.17
Valor del índice para el 5 de febrero de 2024 es 4.96
Valor del índice para el 6 de mayo de 2024 es 4.57
Valor del índice para el 5 de agosto de 2024 es 3.93
Valor del índice para el 4 de noviembre de 2024 es 3.93
Valor del índice para el 4 de febrero de 2025 es 3.33
Valor del índice para el 5 de mayo de 2025 es 3.33
Valor del índice para el 4 de agosto de 2025 es 3.06
Valor del índice para el 4 de noviembre de 2025 es 3.06
Valor del índice para el 4 de febrero de 2026 es 3.06
Valor del índice para el 4 de mayo de 2026 es 3.06
Valor del índice para el 4 de agosto de 2026 es 2.96
Valor del índice para el 4 de noviembre de 2026 es 2.96

Usando Bloomberg, podemos ver que el Índice Compuesto de la Tasa de Financiación Nocturna Garantizada SOFR proporciona una tasa de 5.10860 para el período del 05/04/2023 al 08/04/2023 utilizando la convención de fecha real hacia atrás que son el 05/02/2023 y el 08/02/2023. Como podemos ver, esto no coincide con el 5.17.

enter image description here

Para demostrar que Bloomberg es relevante y correcto, podemos tomar un ejemplo del pasado:

fecha de inicio: 11/04/2022 --> Convención hacia atrás: 11/02/2022

fecha de finalización: 02/06/2023 --> Convención hacia atrás: 02/02/2023

Da una tasa de 4.73020 que coincide con 4.730198

Así que creo que me falta algo pero aún no sé qué.

¿Es lo siguiente correcto?

SOFRindex = ql.OvernightIndex("SOFR", 1, ql.USDCurrency(), calendario, day_count, estructura_de_tasas_de_descuento)

Porque no estoy seguro de que esto utilice tasas directas para fechas futuras.

enter image description here

enter image description here

El código completo está justo debajo y se puede utilizar como un ejemplo reproducible

import QuantLib as ql
import datetime

fecha_de_calculo = ql.Date().todaysDate() #ql.Date(15, 6, 2023)

ql.Settings.instance().evaluationDate = fecha_de_calculo
yts = ql.RelinkableYieldTermStructureHandle()

index_for_curve_s490 = ql.OvernightIndex("USD Overnight Index", 0, ql.USDCurrency(), ql.UnitedStates(ql.UnitedStates.Settlement), ql.Actual360(),yts)

swaps = {
    ql.Period("1W"): 0.050881,
    ql.Period("2W"): 0.050907,
    ql.Period("3W"): 0.050901,
    ql.Period("1M"): 0.050985,
    ql.Period("2M"): 0.05155,
    ql.Period("3M"): 0.05202,
    ql.Period("4M"): 0.052316,
    ql.Period("5M"): 0.052405,
    ql.Period("6M"): 0.052419,
    ql.Period("7M"): 0.052346,
    ql.Period("8M"): 0.052213,
    ql.Period("9M"): 0.052052,
    ql.Period("10M"): 0.051765,
    ql.Period("11M"): 0.051434,
    ql.Period("12M"): 0.051021,
    ql.Period("18M"): 0.047224,
    ql.Period("2Y"): 0.044145,
    ql.Period("3Y"): 0.03992,
    ql.Period("4Y"): 0.037565,
    ql.Period("5Y"): 0.036239,
    ql.Period("6Y"): 0.035464,
    ql.Period("7Y"): 0.034974,
    ql.Period("8Y"): 0.034677,
    ql.Period("9Y"): 0.034518,
    ql.Period("10Y"): 0.03442,
    ql.Period("12Y"): 0.034378,
    ql.Period("15Y"): 0.03437,
    ql.Period("20Y"): 0.033933,
    ql.Period("25Y"): 0.032933,
    ql.Period("30Y"): 0.031949,
    ql.Period("40Y"): 0.029842,
    ql.Period("50Y"): 0.02773,
}

rate_helpers = []

for plazo, tasa in swaps.items():
    helper = ql.OISRateHelper(2, plazo, ql.QuoteHandle(ql.SimpleQuote(tasa)), index_for_curve_s490)
    rate_helpers.append(helper)

curva = ql.PiecewiseFlatForward(fecha_de_calculo, rate_helpers, ql.Actual360())
yts.linkTo(curva)

spread = 65/10000
#spread = 0

dias_de_liquidacion = 2
valor_nominal = 100
compounding = ql.Compounded
fecha_de_precios = fecha_de_calculo + ql.Period(f"{dias_de_liquidacion}D")
fecha_de_emision = ql.Date(4, 11, 2021)
fecha_de_vencimiento = ql.Date(4, 11, 2026)
plazo = ql.Period("3M")
calendario = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
day_count = ql.Actual360()
coupon_rate = 0.05729695699595476

horario = ql.Schedule( fecha_de_precios,
                      fecha_de_vencimiento,
                      plazo,
                      calendario,
                      ql.Unadjusted,
                      ql.Unadjusted,
                      ql.DateGeneration.Backward,
                      True)

forward_plano = ql.FlatForward(2,
                    calendario,
                    coupon_rate,
                    ql.Thirty360(ql.Thirty360.USA),
                    compounding)

estructura_de_tasas_de_descuento = ql.RelinkableYieldTermStructureHandle(curva)
estructura_de_tasas_de_indice = ql.RelinkableYieldTermStructureHandle(forward_plano)

SOFRindex = ql.OvernightIndex("SOFR", 1, ql.USDCurrency(), calendario, day_count, estructura_de_tasas_de_descuento)

#índice = index_for_curve_s490.clone(yts)

bono = ql.FloatingRateBond(2,valor_nominal, horario, SOFRindex, day_count,spreads=[spread])

#mostrar flujos de efectivo de bono desde la fecha de inicio hasta la fecha de finalización
for i, cashflow in enumerate(bono.cashflows()):
    d = cashflow.date()
    try:
        resultado = SOFRindex.fixing(d, True)
        print(f"Inicio de Devengo {d} : {round(resultado*100,2)}")
    except Exception as e:
        print(f" {d} No Data")
    #print(f"Cupón para {d} es {round(cashflow.amount(),2)}")

motor_de_precios = ql.DiscountingBondEngine(estructura_de_tasas_de_descuento)
bono.setPricingEngine(motor_de_precios)
print(f"Mi precio calculado es {bono.cleanPrice()} y el precio de Bloomberg es 99.13")

3voto

Brad Tutterow Puntos 5628

Si solicitas el índice SOFR para una fijación en la fecha de inicio del cupón, te lo devolverá, pero eso no es lo que paga el cupón. Un cupón que paga SOFR durante un período paga las fijaciones de SOFR acumuladas en todas las fechas del cupón; es decir, un cupón que comienza el 2 de mayo de 2023 y finaliza el 2 de agosto (después de aplicar el rezago de la fijación) pagará la fijación del 2 de mayo acumulada por un día, compuesta con la fijación del 3 de mayo acumulada por un día, compuesta con la fijación del 4 de mayo acumulada por un día, compuesta con la fijación del 5 de mayo acumulada por tres días (porque el próximo día hábil es el próximo lunes), y así sucesivamente hasta el final del cupón.

Eso no es lo que hace FloatingRateBond; modela el antiguo tipo de bonos basado en Libor en el que la fijación se toma al principio del cupón y se acumula durante toda la duración.

En este momento no hay una clase específica para bonos que pagan SOFR, pero sería solo por conveniencia y no es estrictamente necesario. Aún puedes crear una lista de cupones que pagan SOFR usando:

cupones = ql.OvernightLeg([nominal], schedule, SOFRindex)

y luego pasarlos para construir una instancia de la clase base Bond:

bono = Bond(días_de_liquidación, calendario, fecha_de_emisión, cupones)

Otra cosa: puede ser porque simplificaste el código para publicar, pero veo que estás usando la misma curva para descontar y para prever las fijaciones de SOFR. Esta se construye a partir de un conjunto de OIS, por lo que es correcto usarla para prever y te dará las tasas de cupón correctas, pero puede que no sea la correcta para descontar (ya que no incluye riesgo crediticio) y podría no darte el precio esperado al final. Es posible que debas ajustar una curva de descuento a partir de algunos precios de bonos cotizados para el mismo emisor.

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