Estoy tratando de modelar un préstamo a plazo en QuantLib-Python
que hace pagos de intereses trimestrales en CME Term SOFR 3M + 10bps + 525bps pagados atrasados con un fixing de 2 días hábiles.
El calendario de amortización es personalizado en el sentido de que no comienza hasta después de la primera fecha de pago de intereses y solo ocurre en el último día hábil de marzo, junio, septiembre y diciembre. La amortización anual es del 5% del monto principal inicial.
Mi código genera correctamente los flujos de efectivo de principal e interés para una tasa SOFR asumida del 5%. En lugar de envolver estos flujos de efectivo en ql.SimpleCashFlow
, preferiría aprovechar la clase ql.AmortizingFloatingRateBond
para poder pasar una curva SOFR. ¿Cuál es el enfoque recomendado para configurar este instrumento dados el calendario personalizado?
import QuantLib as ql
def quarter_end_business_days(start_date, end_date, calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond), quarter_end_months = [3,6,9,12]):
# Inicializar la fecha actual en la fecha de inicio
current_date = start_date
# Lista para almacenar los últimos días hábiles de los meses que terminan en cuartos
last_business_days = []
# Bucle para iterar a través de los meses y encontrar el último día hábil de los meses que terminan en cuartos
while current_date <= end_date:
# Determinar el final del mes actual
eom_date = ql.Date.endOfMonth(current_date)
# Verificar si es un mes que termina en cuarto
if eom_date.month() in quarter_end_months:
# Ajustar al último día hábil
last_business_day = calendar.adjust(eom_date, ql.Preceding)
last_business_days.append(last_business_day)
# Mover al primer día del mes siguiente, ajustando el año si es necesario
current_date = ql.Date(1, 1, current_date.year() + 1)
else:
current_date = ql.Date(1, current_date.month() + 1, current_date.year())
# Devolver la lista de últimos días hábiles
return last_business_days
# Establecer la fecha de evaluación y los parámetros básicos
today = ql.Date(9, 5, 2024)
ql.Settings.instance().evaluationDate = today
calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
# Convención de días de conteo
day_count = ql.Actual360()
# Configuración del calendario del préstamo
effective_date = ql.Date(10, 5, 2022)
maturity_date = ql.Date(1, 2, 2027)
first_amortization_date = ql.Date(30,9,2022)
tenor = ql.Period(ql.Quarterly)
# Definir manualmente las fechas para asegurarse de que sean finales de trimestres
dates = [effective_date] + quarter_end_business_days(start_date=effective_date, end_date=maturity_date, calendar=calendar) + [maturity_date]
# Crear el calendario directamente con estas fechas
schedule = ql.Schedule(dates, calendar, ql.Unadjusted)
# Definir el índice SOFR diario y configurar la curva forward para SOFR a 3 meses
sofr_index = ql.Sofr()
dates = [schedule[i] for i in range(len(schedule))]
rates = [0.05 + 0.0010 + 0.0525] * len(dates) # Tasas consistentes para simplificación; SOFR base + 10 bps + 525 bps
day_count = ql.Actual360()
sofr_curve = ql.ZeroCurve(dates, rates, day_count, calendar)
sofr_curve_handle = ql.YieldTermStructureHandle(sofr_curve)
# Crear un índice nocturno vinculado a la curva de rendimiento construida
three_month_sofr = ql.OvernightIndex("3M SOFR", 0, ql.USDCurrency(), calendar, day_count, sofr_curve_handle)
# Configuración de la mecánica del bono (préstamo)
valor_facial = 100 # Principal inicial
pago_principal = valor_facial * 0.05 / 4 # 5% anual, dividido por 4 para pagos trimestrales
# Inicializar los flujos de efectivo del préstamo
principal_restante = valor_facial
flujos_de_efectivo = []
for i in range(1, len(schedule)):
fecha = schedule[i]
# Calcular la tasa de interés efectiva con límite inferior
tasa_limite = 0.005 # Piso de 50 bps
tasa_tres_meses = max(tasa_limite, sofr_curve_handle.zeroRate(fecha, ql.Actual360(), ql.Continuous).rate())
pago_interes = principal_restante * tasa_tres_meses * day_count.yearFraction(schedule[i-1], schedule[i]) # Pagos trimestrales
pago_principal_actual = principal_restante if fecha == maturity_date else (pago_principal if fecha >= first_amortization_date else 0)
pago_total = pago_principal_actual + pago_interes
principal_restante -= pago_principal_actual
principal_restante = max(0, principal_restante) # Asegurar que no haya principal negativo
flujos_de_efectivo.append((fecha, pago_total, pago_interes, pago_principal_actual, principal_restante))
# Mostrar el calendario de amortización
for fecha, total, interes, principal, restante in flujos_de_efectivo:
print(f"Fecha: {fecha.ISO()}, Pago Total: {total:.2f}, Interés: {interes:.2f}, Principal: {principal:.2f}, Restante: {restante:.2f}")