Hay un papel de Bruce Bartlett introduciendo un delta modificado para el modelo SABR que tiene en cuenta la correlación entre los procesos a plazo y de volatilidad. El principal resultado del artículo es que si $dF$ es la variación de un tipo de interés a plazo $F$ entonces el cambio medio en un parámetro de volatilidad SABR $\alpha$ es $\delta\alpha=\frac{\rho\nu}{F^\beta}dF$ .
Tomé el siguiente código calculando el delta de Bartlett del Modelos de mercado SABR y SABR LIBOR en la práctica libro:
from scipy.stats import norm
import math
def haganLogNormalApprox (y, expiry , F_0 , alpha_0 , beta , nu , rho ):
'''
Function which returns the Black implied volatility ,
computed using the Hagan et al. lognormal
approximation .
@var y: option strike
@var expiry: option expiry (in years)
@var F_0: forward interest rate
@var alpha_0: SABR Alpha at t=0
@var beta : SABR Beta
@var rho: SABR Rho
@var nu: SABR Nu
'''
one_beta = 1.0 - beta
one_betasqr = one_beta * one_beta
if F_0 != y:
fK = F_0 * y
fK_beta = math .pow(fK , one_beta / 2.0)
log_fK = math .log(F_0 / y)
z = nu / alpha_0 * fK_beta * log_fK
x = math .log (( math .sqrt (1.0 - 2.0 * rho *
z + z * z) + z - rho) / (1 - rho))
sigma_l = (alpha_0 / fK_beta / (1.0 + one_betasqr /
24.0 * log_fK * log_fK +
math .pow( one_beta * log_fK , 4) / 1920.0) *
(z / x))
sigma_exp = ( one_betasqr / 24.0 * alpha_0 * alpha_0 /
fK_beta / fK_beta + 0.25 * rho * beta *
nu * alpha_0 / fK_beta +
(2.0 - 3.0 * rho * rho) / 24.0 * nu * nu)
sigma = sigma_l * ( 1.0 + sigma_exp * expiry)
else:
f_beta = math .pow(F_0 , one_beta)
f_two_beta = math .pow(F_0 , (2.0 - 2.0 * beta ))
sigma = (( alpha_0 / f_beta) * (1.0 +
(( one_betasqr / 24.0) *
( alpha_0 * alpha_0 / f_two_beta ) +
(0.25 * rho * beta * nu * alpha_0 / f_beta) +
(2.0 - 3.0 * rho * rho) /
24.0 * nu * nu) * expiry))
return sigma
def dPlusBlack(F_0 , y, expiry , vol):
'''
Compute the d+ term appearing in the Black formula.
@var F_0: forward rate at time 0
@var y: option strike
@var expiry: option expiry (in years)
@var vol: Black implied volatility
'''
d_plus = ((math.log(F_0 / y) + 0.5 * vol * vol * expiry)
/ vol / math.sqrt(expiry ))
return d_plus
def dMinusBlack(F_0 , y, expiry , vol):
'''
Compute the d- term appearing in the Black formula.
@var F_0: forward rate at time 0
@var y: option strike
@var expiry: option expiry (in years)
@var vol: Black implied volatility
'''
d_minus = (dPlusBlack(F_0 = F_0 , y = y, expiry = expiry ,
vol = vol ) - vol * math.sqrt(expiry ))
return d_minus
def black(F_0 , y, expiry , vol , isCall ):
'''
Compute the Black formula.
@var F_0: forward rate at time 0
@var y: option strike
@var expiry: option expiry (in years)
@var vol: Black implied volatility
@var isCall: True or False
'''
option_value = 0
if expiry * vol == 0.0:
if isCall:
option_value = max(F_0 - y, 0.0)
else:
option_value = max(y - F_0 , 0.0)
else:
d1 = dPlusBlack(F_0 = F_0 , y = y, expiry = expiry ,
vol = vol)
d2 = dMinusBlack(F_0 = F_0 , y = y, expiry = expiry ,
vol = vol)
if isCall:
option_value = (F_0 * norm.cdf(d1) - y *
norm.cdf(d2))
else:
option_value = (y * norm.cdf(-d2) - F_0 *
norm.cdf(-d1))
return option_value
def computeFirstDerivative (v_u_plus_du , v_u_minus_du , du):
'''
Compute the first derivatve of a function using
central difference
@var v_u_plus_du: is the value of the function
computed for a positive bump amount du
@var v_u_minus_du : is the value of the function
computed for a negative bump amount du
@var du: bump amount
'''
first_derivative = (v_u_plus_du - v_u_minus_du ) / (2.0 * du)
return first_derivative
def computeSABRDelta (y, expiry , F_0 , alpha_0 , beta , rho , nu , isCall):
'''
Compute the SABR delta.
@var y: option strike
@var expiry: option expiry (in years)
@var F_0: forward interest rate
@var alpha_0: SABR Alpha at t=0
@var beta : SABR Beta
@var rho: SABR Rho
@var nu: SABR Nu
@var isCall: True or False
'''
small_figure = 0.0001
F_0_plus_h = F_0 + small_figure
avg_alpha = (alpha_0 + (rho * nu /
math .pow(F_0 , beta )) * small_figure )
vol = haganLogNormalApprox (y, expiry , F_0_plus_h , avg_alpha ,
beta , nu , rho)
px_f_plus_h = black(F_0_plus_h , y, expiry , vol , isCall)
F_0_minus_h = F_0 - small_figure
avg_alpha = (alpha_0 + (rho * nu /
math .pow(F_0 , beta )) * (-small_figure ))
vol = haganLogNormalApprox (y, expiry , F_0_minus_h ,
avg_alpha , beta ,
nu , rho)
px_f_minus_h = black(F_0_minus_h , y, expiry , vol , isCall)
sabr_delta = computeFirstDerivative (px_f_plus_h ,px_f_minus_h ,
small_figure )
return sabr_delta
El código parece correcto, sin embargo he encontrado un problema con los signos erróneos de los deltas para varios caplets (opción de compra sobre un tipo a plazo) y floorlets (opción de venta sobre un tipo a plazo) mientras trabajaba con el modelo SABR calibrado a la superficie real. Uno esperaría que la delta de una opción de compra fuera positiva y que la delta de una opción de venta fuera negativa, lo cual se viola en el siguiente caso
BartlettDeltaPut = computeSABRDelta(y=0.06, expiry=1.50, F_0=0.0962688131761622,
alpha_0=0.0895853076638471, beta=0.5, rho=0.235477576202461, nu=1.99479846430177,
isCall=False)
BartlettDeltaCall = computeSABRDelta(y=0.10, expiry=0.25, F_0=0.07942844548137806,
alpha_0=0.127693338654331, beta=0.5, rho=-0.473149790316068, nu=2.46284420168144,
isCall=True)
resultando en
0.21186868757223573
-0.0012938212806158644
Por el contrario, el delta negro de vainilla dado por
import numpy as np
def Delta(k, f, t, v, isCall=True):
d1 = (np.log(f/k) + v**2 * t/2) / (v * t**0.5)
if isCall:
delta = norm.cdf(d1)
else:
delta = norm.cdf(d1) - 1
return delta
vol1 = haganLogNormalApprox(y=0.06, expiry=1.50, F_0=0.0962688131761622,
alpha_0=0.0895853076638471, beta=0.5, nu=1.99479846430177, rho=0.235477576202461)
vol2 = haganLogNormalApprox(y=0.10, expiry=0.25, F_0=0.07942844548137806,
alpha_0=0.127693338654331, beta=0.5, nu=2.46284420168144, rho=-0.473149790316068)
BlackDeltaPut = Delta(k=0.06, f=0.0962688131761622, t=1.50, v=vol1, isCall=False)
BlackDeltaCall = Delta(k=0.10, f=0.07942844548137806, t=0.25, v=vol2, isCall=True)
junto con los valores de volatilidad calculados por Hagan et al. del código anterior funcionarían como se esperaba, produciendo un delta negativo para las opciones de venta y un delta positivo para las opciones de compra:
-0.16385166669719764
0.1753400660949036
¿Por qué los valores delta de Bartlett no tienen sentido en este caso? Miré a través del código cuidadosamente y a lo mejor de mi conocimiento no tiene ningún error o errores tipográficos.