4 votos

QuantLib Python Curva de Rendimiento Bootstrapping Fechas y Vencimientos de Swaps

Esto está algo relacionado con la pregunta que hice aquí pero más simple. Estoy intentando hacer un bootstrap de una curva de rendimiento a partir de swaps, y estoy teniendo un problema con las fechas/madurez que salen. El código que estoy utilizando es el siguiente, y el problema que estoy teniendo es que cuando los resultados se devuelven el campo de los vencimientos no coincide con los vencimientos que estoy introduciendo.

import QuantLib as ql
from pandas import DataFrame
import matplotlib.pyplot as plt
import csv

def get_spot_rates(yieldcurve, day_count, calendar=ql.UnitedStates(), months=121):
    spots = []
    tenors = []
    ref_date = yieldcurve.referenceDate()
    calc_date = ref_date
    for yrs in yieldcurve.times():
        d = calendar.advance(ref_date, ql.Period(int(yrs*365.25), ql.Days))
        compounding = ql.Compounded
        freq = ql.Semiannual
        zero_rate = yieldcurve.zeroRate(yrs, compounding, freq)
        tenors.append(yrs)
        eq_rate = zero_rate.equivalentRate(day_count,compounding,freq,calc_date,d).rate()
        spots.append(eq_rate*100)
    return DataFrame(list(zip(tenors, spots)),columns=["Maturities","Curve"],index=['']*len(tenors))

swap_maturities = [ql.Date(9,9,2016),
ql.Date(15,9,2016),
ql.Date(22,9,2016),
ql.Date(29,9,2016),
ql.Date(11,10,2016),
ql.Date(9,11,2016),
ql.Date(8,12,2016),
ql.Date(10,1,2017),
ql.Date(8,2,2017),
ql.Date(8,3,2017),
ql.Date(8,6,2017),
ql.Date(8,9,2017),
ql.Date(8,3,2018),
ql.Date(10,9,2018),
ql.Date(10,9,2019),
ql.Date(10,9,2020),
ql.Date(9,9,2021),
ql.Date(8,9,2022),
ql.Date(8,9,2023),
ql.Date(10,9,2024),
ql.Date(10,9,2025),
ql.Date(10,9,2026),
ql.Date(8,9,2028),
ql.Date(10,9,2031),
ql.Date(10,9,2036),
ql.Date(10,9,2041),
ql.Date(10,9,2046),
ql.Date(8,9,2056)
]

swap_periods = [ql.Period(1,ql.Days),
ql.Period(1,ql.Weeks),
ql.Period(2,ql.Weeks),
ql.Period(3,ql.Weeks),
ql.Period(1,ql.Months),
ql.Period(2,ql.Months),
ql.Period(3,ql.Months),
ql.Period(4,ql.Months),
ql.Period(5,ql.Months),
ql.Period(6,ql.Months),
ql.Period(9,ql.Months),
ql.Period(1,ql.Years),
ql.Period(18,ql.Months),
ql.Period(2,ql.Years),
ql.Period(3,ql.Years),
ql.Period(4,ql.Years),
ql.Period(5,ql.Years),
ql.Period(6,ql.Years),
ql.Period(7,ql.Years),
ql.Period(8,ql.Years),
ql.Period(9,ql.Years),
ql.Period(10,ql.Years),
ql.Period(12,ql.Years),
ql.Period(15,ql.Years),
ql.Period(20,ql.Years),
ql.Period(25,ql.Years),
ql.Period(30,ql.Years),
ql.Period(40,ql.Years)
]

swap_rates = [0.37,
0.4025,
0.4026,
0.399,
0.3978,
0.4061,
0.41,
0.4155,
0.4273,
0.4392,
0.461,
0.4805,
0.5118,
0.538,
0.587,
0.638,
0.7,
0.756,
0.818,
0.865,
0.913,
0.962,
1.045,
1.137,
1.2355,
1.281,
1.305,
1.346
]

""" Parameter Setup """
calc_date = ql.Date(1,9,2016)
ql.Settings.instance().evaluationDate = calc_date
calendar = ql.UnitedStates()
bussiness_convention = ql.ModifiedFollowing
day_count = ql.Actual360()
coupon_frequency = ql.Annual

""" SwapRateHelper """
swap_helpers = []
for rate,tenor in list(zip(swap_rates,swap_periods)):

swap_helpers.append(ql.SwapRateHelper(ql.QuoteHandle(ql.SimpleQuote(rate/100.0)),
        tenor, calendar,
        coupon_frequency, bussiness_convention,
        day_count,
        ql.Euribor3M()))

rate_helpers = swap_helpers
yc_linearzero = ql.PiecewiseLinearZero(calc_date,rate_helpers,day_count)
yc_cubiczero = ql.PiecewiseCubicZero(calc_date,rate_helpers,day_count)

max_maturity = 40*12

splz = get_spot_rates(yc_linearzero, day_count, months=max_maturity + 1)
spcz = get_spot_rates(yc_cubiczero, day_count, months=max_maturity + 1)

max_rate = swap_rates[-1]
min_rate = min(splz.Curve)
max_rate = max(splz.Curve)

"""Plotting"""
plt.plot(splz["Maturities"],splz["Curve"],'--', label="LinearZero")
plt.plot(spcz["Maturities"],spcz["Curve"],label="CubicZero")
plt.xlabel("Years", size=12)
plt.ylabel("Zero Rate", size=12)
plt.xlim(0,max_maturity/12.0)
plt.ylim([min_rate * 0.9,max_rate * 1.1])
plt.legend()

plt.show()

rows = zip(splz.Maturities,splz.Curve)

with open('OISBootstrap.csv','w',newline='') as f:
    writer = csv.writer(f)
    for row in rows:
        writer.writerow(row)

Cualquiera que tenga Python y QL puede ejecutarlo todo y ver los resultados, pero por ejemplo estoy obteniendo los siguientes valores para los últimos cinco vencimientos: 15,2361111111, 20,31111111, 25,3777777778, 30,45, 40,5972222222 en lugar de 15, 20, 25, 30 y 40.

Gracias de antemano por lo que supongo que es una pregunta bastante novata.

4voto

Brad Tutterow Puntos 5628

15 años sí corresponden a t=15,236 según el contador de días que le dijiste a la curva que utilizara.

En primer lugar, no puedes conseguir exactamente 15 de todos modos. Tu fecha de cálculo es el 1 de septiembre de 2016; según las convenciones habituales, el swap cuyo tipo estás cotizando comienza al contado, es decir, dos días hábiles después de la fecha de cálculo. Teniendo en cuenta el fin de semana (su fecha de cálculo es un jueves) y la festividad del Día del Trabajo (4 de septiembre), esto le lleva al 6 de septiembre. Por lo tanto, el swap a 15 años tendría su vencimiento el 6 de septiembre de 2031; pero eso es un sábado, por lo que se traslada al 8 de septiembre de 2031 (además, su vencimiento de entrada es el 10 de septiembre, por lo que también debería comprobarlo). Son 15 años y una semana después de su fecha de cálculo, que también es t=0 para su curva (ya que la está pasando a su constructor como fecha de referencia).

Segundo: estás pasando a la curva una convención de conteo de días de Actual/360, para la cual un año no corresponde a 1, sino a 365/360=1,01389 (o 366/360 en un año bisiesto).

Juntando todo, se obtiene que los 5485 días entre la fecha de cálculo y el vencimiento del swap a 15 años corresponden a 5485/360=5,23611. Cálculos similares se aplican a los demás vencimientos.

El uso de Actual/365 (Fijo) da resultados más cercanos a lo que se esperaría, ya que corresponde a t=1 para un año "regular"; pero como los años bisiestos corresponden a 366/365, se sigue obteniendo t=5485/365=15,0274.

Otros contadores de días tienen mejores propiedades para calcular los tipos (por ejemplo, Act/Act le da t=1 para 1 año) pero no aconsejo utilizarlos en las estructuras de plazos, ya que carecen de otras propiedades; por ejemplo, existen pares de fechas $d_1$ y $d_2$ para el que el tiempo $T(d_1,d_2)$ entre ellos es 0, o triples $d_1$ , $d_2$ , $d_3$ para lo cual $T(d_1,d_3) \neq T(d_1,d_2) + T(d_2,d_3)$ .

Lo que sugiero en cambio es que trabajes con las fechas directamente. En su get_spot_rates en lugar de intentar recalcular las fechas de vencimiento como

d = calendar.advance(ref_date, ql.Period(int(yrs*365.25), ql.Days))

llame a yieldcurve.nodes() que le proporciona la lista de pares (fecha,tipo) correspondientes a cada vencimiento. Puede utilizar esas fechas en la llamada a equivalentRate como ya lo hace, y también en la llamada a yieldcurve.zeroRate .

(Y por cierto, calendar.advance(ref_date, 365, Days) no hace lo que usted piensa. Adelanta la fecha en 365 negocio días, que es mucho más que un año).

0 votos

Además, tengo la molesta sensación de que el cálculo en el interior get_spot_rates podría ser más simple, pero no estoy muy seguro de qué tasas estás calculando exactamente...

0 votos

En realidad, la parte de get_spot_rates del código fue tomada directamente de su libro con Goutham en la página 76. He probado a modificarlo para que devuelva los vencimientos que me interesan en lugar de los mensuales. Volveré a jugar con él un poco más. Para ser completamente honesto, como todavía estoy aprendiendo muchas de las metodologías detrás del código mucho de lo que trabajo está basado en cosas del libro. Como en mi pregunta anterior a la que hice referencia, has sido una ayuda fantástica, Luigi.

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