En toda la brevedad
- ¿Cuál es la condición de terminación utilizada en el bootstrapping de curvas de QuantLib?
- ¿Puedo modificar esta configuración para adaptarla a mis necesidades, por ejemplo, puedo ajustarla a una mayor precisión?
Antecedentes
Al realizar el bootstrapping de las curvas de descuento y proyección, una condición necesaria es que las curvas implícitas pongan precio al instrumentos de referencia en el mercado, es decir, valorar los instrumentos que se suministraron originalmente al algoritmo de construcción de la curva lo más cerca posible de sus precios de entrada. Con una curva que utiliza un mecanismo de interpolación local ( bootstrap iterativo ), supondría que todos los instrumentos de referencia se reajustan a los precios de mercado con un error mínimo, es decir
Supongamos un mecanismo de curva/bootstrap que utilice la interpolación local, por ejemplo, la interpolación lineal de los factores de descuento logarítmicos. Dada una comilla de mercado, y dada la curva construida "hasta ahora", el mecanismo bootstrap selecciona el "próximo" instrumento de referencia y actualiza la curva desplazando un tipo en un punto del nodo hasta que el instrumento de referencia tenga un precio de mercado.
Mi observación y pregunta
Vuelvo a fijar el precio de mis instrumentos de referencia en la curva que acabo de calcular y descubro que éstos no tienen un precio tan cercano a cero como habría supuesto, véase el ejemplo mínimo que aparece a continuación:
import QuantLib as ql
today = ql.Date(23,ql.June,2020)
ql.Settings.instance().evaluationDate = today
eonia = ql.Eonia()
Suponiendo comillas OIS "planas" en el mercado al 1% por tenor y recogiendo las ayudas a la construcción de la curva:
quotes = {str(k)+'Y' : ql.SimpleQuote(0.01) for k in range(1,21)}
ois_helpers = []
for k,v in quotes.items():
ois_helpers.append(ql.OISRateHelper(
settlementDays = 2,
tenor = ql.Period(k),
rate = ql.QuoteHandle(v),
index = eonia,
telescopicValueDates =True))
eonia_curve = ql.PiecewiseLinearZero(2,ql.TARGET(),ois_helpers,ql.Actual365Fixed())
val_curve = ql.YieldTermStructureHandle(eonia_curve)
Crear otro eonia_index
esta vez con una curva de proyección adjunta; y un motor de valoración:
eonia_index = ql.Eonia(val_curve)
swap_engine = ql.DiscountingSwapEngine(val_curve)
Ahora configuro los instrumentos de referencia como instrumentos "reales" y obtengo sus VAN. Tenga en cuenta que asumo un nocional de 1 million
currs:
print('TENOR \t PV \t fairrate% \t fairrate% + fairspread%')
for p in quotes.keys():
schedule = ql.MakeSchedule(today, today + ql.Period(p), ql.Period('1d'), calendar=ql.TARGET())
fixedRate = quotes[p].value()
ois_swap = ql.OvernightIndexedSwap(
ql.OvernightIndexedSwap.Receiver,
1E6,
schedule,
fixedRate,
ql.Actual360(),
eonia_index)
ois_swap.setPricingEngine(swap_engine)
print(p + "\t" +
str(round(ois_swap.NPV(),2)) + " \t " +
str(round(ois_swap.fairRate()*100,4)) + "\t\t" +
str(100*(ois_swap.fairRate()+ois_swap.fairSpread())))
Resultando en
TENOR NPV fairrate% fairrate% + fairspread%
1Y 50.25 0.995 1.0
2Y 100.55 0.995 1.0
3Y 149.95 0.995 1.0
4Y 199.23 0.995 1.0
5Y 247.63 0.995 1.0
6Y 295.67 0.995 1.0
7Y 343.23 0.995 1.0
8Y 390.7 0.995 1.0
9Y 437.44 0.995 1.0
10Y 483.46 0.995 1.0
11Y 529.01 0.995 1.0
12Y 574.48 0.995 1.0
13Y 619.49 0.995 1.0
14Y 663.69 0.995 1.0
15Y 707.68 0.995 1.0
16Y 751.11 0.995 1.0
17Y 794.1 0.995 1.0
18Y 836.66 0.995 1.0
19Y 879.03 0.995 1.0
20Y 920.98 0.995 1.0
Evidentemente, el tipo justo implícito no es exactamente el 1%, pero el tipo justo implícito más el diferencial implícito dan el 1%. Además, el VAN de cada swap está cerca, pero no "muy" cerca de cero.
Me pregunto si
- ¿hay algo raro en la configuración de mis instrumentos?
- Qué condición terminal está aplicando aquí la metodología bootstrap de QuantLib, y
- Si puedo poner límites más estrictos a ese mecanismo.
Muchas gracias por cualquier aportación/opinión/consejo.
Mi configuración
He construido la curva de descuento siguiendo el QuantLib Python Cookbook de Luigi y Goutham a partir de 2019-JUNIO-01; estoy utilizando el QuantLib Python SWIG; versión 1.19.