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.
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.
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")