Llevo unos días trabajando con el paquete QuantLib Python. Actualmente, estoy trabajando en la calibración de un modelo de un factor de Hull White para los tipos cortos. Estoy calibrando el modelo en la curva de rendimiento y en las volatilidades de los swaptions.
Dadas las circunstancias actuales del mercado, gran parte de la curva de rendimiento es negativa. Esto provoca algunos problemas a la hora de calibrar el modelo de un factor de Hull White, ya que la distribución lognormal no puede ser negativa. Después de buscar un poco encontré la posibilidad de especificar un desplazamiento. En el código siguiente se muestra la inicialización de mi SwaptionHelper incluyendo el desplazamiento.
Tenga en cuenta que actualmente estoy utilizando una curva de rendimiento, sin embargo, cuando se utiliza un flatforward de -0,00478 obtengo resultados similares.
Sin embargo, el uso del desplazamiento da lugar a un resultado poco realista para a (a=0,0000389, sigma=0,0222).
He estado buscando mucho una respuesta, pero no puedo encontrar información decente para situaciones con una tasa negativa. Si alguien pudiera explicar el uso de un desplazamiento o hacer una comprobación rápida en mi código para ver si algo está mal sería tan útil.
helper = ql.SwaptionHelper(
ql.Period(int(maturity), ql.Years),
ql.Period(int(tenor), ql.Years),
volatility,
index,
fixedLegTenor,
fixedLegDayCounter,
floatingLegDayCounter,
term_structure,
ql.BlackCalibrationHelper.RelativePriceError,
ql.nullDouble(),
1.0,
ql.ShiftedLognormal,
0.05 #shift to make rates non-negative
)
Código completo:
import csv
from QuantLib.QuantLib import SwaptionHelper
import matplotlib.pyplot as plt
import pandas as pd
import QuantLib as ql
def load_csv_input():
zero_curve = pd.read_csv('ZeroCurve.csv', delimiter=';')
dates = []
rates = []
for i in range(0,len(zero_curve)):
rates.append(float(zero_curve['Rate'][i]))
dates.append(ql.Date(int(zero_curve['Mat_Day'][i]),int(zero_curve['Mat_Month'][i]),int(zero_curve['Mat_Year'][i])))
return dates, rates
# Read the swaption volatilities from the csv file.
swaption_vols = pd.read_csv('SwaptionVol.csv', delimiter=';', index_col=0)
dates, rates = load_csv_input()
curve = ql.ZeroCurve(dates, rates, ql.Actual365Fixed())
term_structure = ql.YieldTermStructureHandle(curve)
model = ql.HullWhite(term_structure)
#engine = ql.TreeSwaptionEngine(model, 25)
engine = ql.JamshidianSwaptionEngine(model)
#engine = ql.G2SwaptionEngine(model, 10, 400)
index = ql.Euribor1Y(term_structure)
fixedLegTenor = ql.Period('1Y')
fixedLegDayCounter = ql.Actual360()
floatingLegDayCounter = ql.Actual360()
swaptions = []
ql.Settings.instance().evaluationDate = ql.Date(1, 10, 2020)
for maturity in swaption_vols.index:
for tenor in swaption_vols.columns:
volatility = ql.QuoteHandle(ql.SimpleQuote(swaption_vols.at[maturity,tenor]))
helper = ql.SwaptionHelper(
ql.Period(int(maturity), ql.Years),
ql.Period(int(tenor), ql.Years),
volatility,
index,
fixedLegTenor,
fixedLegDayCounter,
floatingLegDayCounter,
term_structure,
ql.BlackCalibrationHelper.RelativePriceError,
ql.nullDouble(),
1.0,
ql.ShiftedLognormal,
0.2 #shift to make rates non-negative
)
helper.setPricingEngine(engine)
swaptions.append(helper)
optimization_method = ql.LevenbergMarquardt(1.0e-8,1.0e-8,1.0e-8)
end_criteria = ql.EndCriteria(500000, 1000, 1e-6, 1e-8, 1e-8)
model.calibrate(swaptions, optimization_method, end_criteria)
params = model.params()
print(params)
Actualización
He cambiado el tipo de volatilidad de lognormal a normal, ya que los datos contienen volatilidades normales. Así que esto debería ser capaz de trabajar con los valores negativos.
Ahora me encuentro con un problema en el que la calibración sólo funciona si utilizo una pequeña selección de volatilidades. Mi matriz de volatilidad completa es de 10x10 (1, 2, 3, 4, 5, 10, 15, 20, 25, y 30Y vencimiento y tenores). Sin embargo, si utilizo más de la primera 5x5 obtengo un error de "root sin corchetes".
0 votos
No entiendo tu comentario "ya que la distribución lognormal no puede ser negativa". La tasa corta en el modelo HW es normal y puede ser negativa. ¿Lognormal son los precios de los bonos y estos son positivos?
0 votos
Tienes razón, el modelo HW es efectivamente normal. Sin embargo, por lo que sé, los cálculos de las swaptions incluyen una distribución lognormal.
0 votos
Supongo que primero comprobaré la plausibilidad de los parámetros calculados de Hull White construyendo la superficie de volatilidad implícita
0 votos
@Michielap quizás te referías a que tus vols de swaption de mercado se obtienen con la fórmula de Black, es decir, los datos en
SwaptionVol.csv
es lognormal. ¿Es ese el caso? Tal vez ayude también si pudieras compartir los datos.0 votos
Supuse que eran logarítmicos. Sin embargo, me puse en contacto con el proveedor de los datos y aparentemente son normales. Así que voy a investigar primero para ver si eso cambia mis resultados. Gracias por indicarme esa dirección.
0 votos
Re "root sin corchetes": ¿los tipos están en unidades naturales o en bps o en %? Para el QL hay que escalarlas a nats
0 votos
Creía que las volatilidades se daban en porcentajes. Sin embargo, en el hilo de abajo David mencionó que probablemente son bps, de lo contrario las volatilidades serían demasiado altas. He dividido los valores por 100 y ahora estoy obteniendo resultados. ¿A qué te refieres con escalar a nats?