Estoy aprendiendo QuantLib-Python y tratando de replicar una valoración de Swap de Tipos de Interés en una curva personalizada construida pasando listas de fechas y factores de descuento. Vea mi código a continuación
import QuantLib as ql
today = ql.Date(31, ql.May, 2023)
ql.Settings.instance().setEvaluationDate(today)
dates = [ql.Date(31, ql.May, 2023), ql.Date(28, ql.August, 2023), ql.Date(27, ql.November, 2023),
ql.Date(26, ql.February, 2024), ql.Date(27, ql.May, 2024), ql.Date(26, ql.May, 2025), ql.Date(26, ql.May, 2026)]
dfs = [1.00, 0.980626530240871, 0.961865563098355, 0.942995352345942, 0.923889577251464, 0.845638911735274, 0.770640534908645]
dayCounter = ql.ActualActual(ql.ActualActual.ISDA)
calendar = ql.UnitedStates()
curve = ql.DiscountCurve(dates, dfs, dayCounter, calendar)
curveHandle = ql.YieldTermStructureHandle(curve)
start = today
maturity = calendar.advance(start, ql.Period('1Y'))
fix_schedule = ql.MakeSchedule(start, maturity, ql.Period('1Y'))
float_schedule = ql.MakeSchedule(start, maturity, ql.Period('3M'))
customIndex = ql.IborIndex('index', ql.Period('3M'), 0, ql.USDCurrency(), ql.UnitedStates(), ql.ModifiedFollowing, True, dayCounter, curveHandle)
customIndex.addFixing(ql.Date(30, ql.May, 2023), 0.075)
notional = 100000000
swap = ql.VanillaSwap(ql.VanillaSwap.Payer, notional, fix_schedule, 0.0833, dayCounter,
float_schedule, customIndex, 0, dayCounter)
swap_engine = ql.DiscountingSwapEngine(curveHandle)
swap.setPricingEngine(swap_engine)
print(swap.NPV())
Existe la posibilidad de imprimir los flujos de caja no descontados de los tramos fijo y flotante mediante
print("Net Present Value: {0}".format(swap.NPV()))
print()
print("Fixed leg cashflows:")
for i, cf in enumerate(swap.leg(0)):
print("%2d %-18s %10.2f"%(i+1, cf.date(), cf.amount()))
print()
print("Floating leg cashflows:")
for i, cf in enumerate(swap.leg(1)):
print("%2d %-18s %10.2f"%(i+1, cf.date(), cf.amount()))
El Valor Actual Neto de un swap debería venir dado por la diferencia de los flujos de caja descontados de los tramos flotante y fijo. He intentado reproducir los cálculos manualmente mediante
fwd1 = curve.forwardRate(ql.Date(31, 5, 2023), ql.Date(31, 8, 2023), dayCounter, ql.Continuous).rate()
df1 = curve.discount(ql.Date(31, 8, 2023))
tau1 = dayCounter.yearFraction(ql.Date(31, 5, 2023), ql.Date(31, 8, 2023))
cashflow1 = df1 * notional * fwd1 * tau1
print(cashflow1)
print()
fwd2 = curve.forwardRate(ql.Date(1, 9, 2023), ql.Date(30, 11, 2023), dayCounter, ql.Simple).rate()
df2 = curve.discount(ql.Date(30, 11, 2023))
tau2 = dayCounter.yearFraction(ql.Date(1, 9, 2023), ql.Date(30, 11, 2023))
cashflow2 = df2 * notional * fwd2 * tau2
print(cashflow2)
print()
fwd3 = curve.forwardRate(ql.Date(1, 12, 2023), ql.Date(29, 2, 2024), dayCounter, ql.Simple).rate()
df3 = curve.discount(ql.Date(29, 2, 2024))
tau3 = dayCounter.yearFraction(ql.Date(1, 12, 2023), ql.Date(29, 2, 2024))
cashflow3 = df3 * notional * fwd3 * tau3
print(cashflow3)
print()
fwd4 = curve.forwardRate(ql.Date(1, 3, 2024), ql.Date(31, 5, 2024), dayCounter, ql.Simple).rate()
df4 = curve.discount(ql.Date(31, 5, 2024))
tau4 = dayCounter.yearFraction(ql.Date(1, 3, 2024), ql.Date(31, 5, 2024))
cashflow4 = df4 * notional * fwd4 * tau4
print(cashflow4)
print()
floatCashFlow = cashflow1 + cashflow2 + cashflow3 + cashflow4
print(floatCashFlow)
print()
df = df4
fixRate = 0.0833
tau = dayCounter.yearFraction(ql.Date(31, 5, 2023), ql.Date(31, 5, 2024))
fixCashFlow = df * notional * fixRate * tau
print(fixCashFlow)
print()
valueSwap = floatCashFlow - fixCashFlow
print(valueSwap)
y obtuvimos un VAN asombrosamente diferente. ¿Puede alguien mostrarme cómo llegar con precisión a una valoración VAN incorporada utilizando métodos incorporados para llamar a los factores de descuento y a los tipos de interés a plazo? Supongo que la diferencia se esconde en algún punto de los calendarios de pagos, pero no he conseguido hacer coincidir los números jugando con las fechas.