1 votos

Ajustando una sonrisa de volatilidad con pySABR - Implementación de Python del modelo SABR

Para modelar algunas sonrisas de volatilidad estoy usando el paquete de python pySABR.

Me encontré con una situación en la que tengo dos piezas de código casi idénticas para dos sonrisas de volatilidad diferentes que faltan las comillas ATM y el pySABR puede ajustar correctamente la volatilidad ATM en un caso y no puede en otro.

Este es el caso en el que todo funciona perfectamente:

import pysabr
import numpy as np  
from pysabr import Hagan2002LognormalSABR as sabr  
from pysabr import hagan_2002_lognormal_sabr as hagan2002

testStrikes = np.array([0.04, 0.06, 0.08, 0.10]) 
testVols = np.array([23.52, 16.24, 20.17, 26.19])
forward_3m_6m = (1/0.25) * (-1 + (1+0.0753*0.5) / (1+0.0747*0.25))

calibration = sabr(f = forward_3m_6m, shift = 0, t = 0.5, beta = 0.5).fit(testStrikes, testVols)
smile = []
test_smile = []
for strike in testStrikes:
    smile.append(sabr(f = forward_3m_6m, shift = 0, t = 0.5, v_atm_n = 136.75/10000.00, beta = 0.5, rho = calibration[1], volvol = calibration[2]).lognormal_vol(strike) * 100.00)
    test_smile.append(hagan2002.lognormal_vol(strike, forward_3m_6m, 0.5, calibration[0], beta, calibration[1], calibration[2]) * 100.00)

print(smile)
print(test_smile)
print(hagan2002.lognormal_vol(k = 0.0745136, f = forward_3m_6m, t = 0.5, alpha = calibration[0], beta = 0.5, rho = calibration[1], volvol = calibration[2]) * 100.00)

La salida es

[23.52579980722943, 16.22619971530687, 20.186954023608315, 26.176954813043512]

[23.52656681608356, 16.227190950406076, 20.188104613955648, 26.178058303454062]

18.369296025878036

La diferencia entre las listas de volatilidad primera y segunda es que la primera fue construida usando la comilla de volatilidad normal ATM en lugar del parámetro alfa.

Este es el código para el segundo caso cuando me encontré con un problema con resultados diferentes para diferentes métodos y un valor inadecuado de la volatilidad lognormal ATM resultante:

import pysabr
import numpy as np  
from pysabr import Hagan2002LognormalSABR as LNsabr 
from pysabr import hagan_2002_lognormal_sabr as hagan2002LN

strikes = np.array([0.05, 0.055, 0.06, 0.0650, 0.07, 0.08, 0.0850, 0.09, 0.095, 0.10])
LogNormalVols = np.array([18.90, 17.30, 16.34, 16.29, 17.19, 20.29, 21.89, 23.42, 24.84, 26.16])
vol_n_ATM = 141.01 / 10000.00
forward_3m_6m = (1 / 0.25) * (- 1 + ( 1+ 0.0756 * 0.5) / (1 + 0.0746 * 0.25))
discount_0m_6m = 0.0753

beta = 0.5 
calibration_LN = LNsabr(forward_3m_6m, 0, 0.5, beta).fit(strikes, LogNormalVols)
modelVols_LN = []
test_LN = []
for strike in strikes:
    modelVols_LN.append(LNsabr(forward_3m_6m, 0, 0.5, vol_n_ATM, beta, calibration_LN[1], calibration_LN[2]).lognormal_vol(strike) * 100.00)
    test_LN.append(hagan2002LN.lognormal_vol(strike, f = forward_3m_6m, t = 0.5, alpha = calibration_LN[0], beta = beta, rho = calibration_LN[1], volvol = calibration_LN[2]) * 100.00)

print(modelVols_LN)
print(test_LN)
print(hagan2002LN.lognormal_vol(k = 0.0753, f = forward_3m_6m, t = 0.5, alpha = calibration_LN[0], beta = beta, rho = calibration_LN[1], volvol = calibration_LN[2]) * 100.00)

La salida es

[20.14451199912703, 18.54257849585578, 17.499371075753768, 17.1951750118971, 17.677189811525658, 20.011518064279755, 21.365538860352675, 22.691679608999, 23.95616514380161, 25.148945356594965]

[68.433853990843, 67.98546902874503, 67.8392636042873, 67.91509150013229, 68.15026017672005, 68.91958756908917, 69.39185669606091, 69.89474554951227, 70.41454229587038, 70.94145252003263]

68.64132679555016

Se puede ver fácilmente que al reproducir la sonrisa de volatilidad utilizando los cuatro parámetros calibrados lleva a volatilidades lognormales mucho más altas de lo esperado.

¿Qué estoy haciendo mal? ¿Dónde está el error en el segundo código? Cualquier ayuda será apreciada.

0 votos

¿Cuál es el resultado del ajuste? es decir, tu objeto calibration_LN.

0 votos

@will, la salida es [0.18123299730498071, 0.5465080547109832, 0.8266797808547465], aquí el primer elemento es alpha, el segundo - rho, el tercero - volvol. Tenga en cuenta que ambos métodos que producen volatilidades lognormales utilizan los mismos parámetros de calibración.

0 votos

Lo siento, no era lo que quise decir. Normalmente, las funciones de ajuste devuelven un objeto que te indica si el ajuste fue exitoso, el número de iteraciones, evaluaciones de la función, el error de ajuste, etc.

4voto

xrost Puntos 129

Editar: Necesitas especificar los argumentos de palabras clave en tu segundo ejemplo

La mala calibración de tu segundo ejemplo se debe al hecho de que no definiste los argumentos de palabras clave en el objeto LNsabr antes de la función .fit().

En lugar de escribir:

calibration_LN = LNsabr(forward_3m_6m, 0, 0.5, beta).fit(strikes, LogNormalVols)

Solo necesitas hacer el ligero cambio de definir los argumentos de palabras clave:

calibration_LN = LNsabr(f = forward_3m_6m, shift = 0, t = 0.5, beta = beta).fit(strikes, LogNormalVols)

Esto resulta en un buen ajuste:

calibración y ajuste

He ampliado mi apéndice actual para contener una versión ligeramente reescrita de tu código de Python proporcionado anteriormente. Puedes ejecutar esto tú mismo y ver si obtienes los mismos parámetros calibrados. No es del todo evidente por qué la calibración se vuelve pobre cuando no se especifican los argumentos de palabras clave. Espero que esto resuelva tu problema. Por favor, proporciona algún comentario cuando tengas tiempo.

Dejaré mi respuesta anterior abajo para que los lectores futuros la vean.


Respuesta anterior: Podría haber un error con la rutina de optimización en el paquete pysabr

La mala calibración podría provenir de la rutina de optimización en el paquete Pysabr que alcanza un mínimo local o similar.

Reimplementé tu segundo ejemplo (que causó problemas) en MATLAB usando su función integrada de la misma expansión polinómica lognormal SABR que también se proporciona en el paquete pysabr en Python (Mi código se proporciona en un apéndice al final de mi respuesta.). La documentación de MATLAB se puede encontrar aquí, y un ejemplo claro de calibración del modelo SABR está documentado aquí. En este último, recorren ambas formas de calibrar el modelo SABR: primero, calibrando $\alpha, \rho, \nu$ directamente y finalmente calibrando $\rho$ y $\nu$ implicando $\alpha$ desde la volatilidad ATM. Siguiendo el primer ejemplo de calibración en MATLAB con tus vols y strikes especificados, obtengo los parámetros calibrados a:

$$\left[\alpha, \rho, \nu\right] = \left[0.0504, \: 0.6407, \:0.8797\right],$$

donde $\nu$ es el parámetro de vol de vol. Si insertas estas estimaciones en el objeto de lista calibration_LN, observarás un ajuste muy bueno para tu modelo SABR log-normal, al calibrar $\alpha$ libremente:

introducir descripción de la imagen aquí

De esto, creo que la rutina de optimización en el paquete pysabr podría tener algunos problemas al calibrar la smile. Actualizaré mi respuesta si encuentro la razón por la cual el paquete produce un ajuste tan malo. Por ahora, esta es la única ayuda que puedo proporcionar.


Apéndice:

Código de Matlab

%Las funciones integradas de Matlab utilizan fechas de liquidación y vencimiento para
%calcular T internamente. Estos son solo elegidos para obtener T=0.5:
Settle = '01-Ene-2020';
ExerciseDate = '01-Jul-2020';

%Comprobando si liquidación y fecha de ejercicio se convierten en T=0.5.
T = yearfrac(Settle, ExerciseDate, 1);

Beta1 = 0.5;

%Siguiendo la guía de MATLAB:
MarketStrikes = [0.05, 0.055, 0.06, 0.0650, 0.07, 0.08, 0.0850, 0.09, 0.095, 0.10]';
MarketVolatilities = [18.90, 17.30, 16.34, 16.29, 17.19, 20.29, 21.89, 23.42, 24.84, 26.16]'/100;

CurrentForwardValue = (1 / 0.25) * (- 1 + ( 1+ 0.0756 * 0.5) / (1 + 0.0746 * 0.25));

objFun = @(X) MarketVolatilities - ...
     blackvolbysabr(X(1), Beta1, X(2), X(3), Settle, ...
     ExerciseDate, CurrentForwardValue, MarketStrikes);

 options_SABR = optimoptions(@lsqnonlin, 'Display', 'iter', ...
    'MaxFunctionEvaluations', 5000, 'MaxIterations', 10000);

%Minimizador:
X = lsqnonlin(objFun, [0.5 0 1], [0 -1 0], [Inf 1 Inf], options_SABR);

Alpha1 = X(1);
Rho1 = X(2);
Nu1 = X(3);

Sabr_vol = blackvolbysabr(Alpha1, Beta1, Rho1, Nu1, Settle, ExerciseDate, 
CurrentForwardValue, MarketStrikes);

[Alpha1, Rho1, Nu1]

plot(MarketStrikes/T, MarketVolatilities)
hold on 
plot(MarketStrikes/T, Sabr_vol) %desplazado para ver la curvatura
legend("Volatilidades del Mercado", "Volatilidades de Sabr")

Código de Python

import pysabr
import numpy as np  
from pysabr import Hagan2002LognormalSABR as LNsabr 
from pysabr import hagan_2002_lognormal_sabr as hagan2002LN
import matplotlib.pyplot as plt

strikes = np.array([0.05, 0.055, 0.06, 0.0650, 0.07, 0.08, 0.0850, 0.09, 
0.095, 0.10])
LogNormalVols = np.array([18.90, 17.30, 16.34, 16.29, 17.19, 20.29, 21.89, 23.42, 24.84, 26.16])
vol_n_ATM = 141.01 / 10000.00
forward_3m_6m = (1 / 0.25) * (- 1 + ( 1+ 0.0756 * 0.5) / (1 + 0.0746 * 0.25))
discount_0m_6m = 0.0753

beta = 0.5 
calibration_LN = LNsabr(f = forward_3m_6m, shift = 0, t = 0.5, beta = beta).fit(strikes, LogNormalVols)
Matlab_LN = [0.0504, 0.6407, 0.8797] #¡Parámetros calibrados de MATLAB! ¡FUNCIONA!
modelVols_LN = []
test_LN = []
for strike in strikes:
    modelVols_LN.append(LNsabr(forward_3m_6m, 0, 0.5, vol_n_ATM, beta, calibration_LN[1], calibration_LN[2]).lognormal_vol(strike) * 100.00)
    test_LN.append(hagan2002LN.lognormal_vol(strike, f = forward_3m_6m, t = 0.5, alpha = calibration_LN[0], beta = beta, rho = calibration_LN[1], volvol = calibration_LN[2]) * 100.00)

print(np.around(modelVols_LN,3))
print(np.around(test_LN,3))

plt.plot(strikes, modelVols_LN, "r--", strikes, test_LN)
print("------------------------------------------------------------------------------------------")
print("------------------------------------------------------------------------------------------")
print("Parámetros calibrados de MATLAB: %s. \nParámetros calibrados de pysabr: %s" % (Matlab_LN, calibration_LN))

plt.plot(strikes, modelVols_LN, "r--", strikes, test_LN)

1 votos

Esta respuesta resolvió todos mis problemas. ¡Muchas gracias — una recompensa bien merecida!

0 votos

@Hasek ¡Me alegra poder ayudar :-)

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