4 votos

La delta de Bartlett da señales erróneas para las opciones de compra y venta

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.

6voto

BC. Puntos 9229

El delta de Bartlett, tal como se calcula en su código, es una simple diferencia finita (FD), también llamada bump y reprice, de los valores de Black. No creo que haya nada mal aquí, aparte del hecho de que usted no está comparando semejante por semejante. En resumen, el delta de Bartlett describe con mayor precisión el cambio en el valor de la opción porque "incorpora" un componente Vega (o vanna /Ddelta Dvol). En determinadas condiciones, esto puede dar lugar a signos diferentes en relación con los deltas negros (que ignoran la forma de la superficie vol).

El delta negro simple es un delta "manteniendo todo lo demás constante", lo que significa que el IVOL es fijo y estable. Lo que se calcula con el delta de Bartlett es un valor FD con diferentes IV para ambos turnos.

Utilizando su haganLogNormalApprox da como resultado los siguientes vols (he cambiado el nombre de algunas variables por preferencia personal):

F_0=0.07942844548137806 
small_figure = 0.0001
y=0.10
expiry=0.25
alpha_0=0.127693338654331
beta=0.5
rho=-0.473149790316068
nu=2.46284420168144
F_pos = F_0 + small_figure
F_neg = F_0 - small_figure
avg_alpha_pos = (alpha_0 + (rho * nu / math.pow(F_0 , beta )) * small_figure )
avg_alpha_neg = (alpha_0 + (rho * nu / math.pow(F_0 , beta )) * (-small_figure ))
vol_0 = vol_0 = haganLogNormalApprox(y=0.10, expiry=0.25, F_0=F_0, 
                       alpha_0=0.127693338654331, beta=0.5, nu=2.46284420168144, rho=-0.473149790316068)
vol_neg = haganLogNormalApprox (y, expiry , F_neg ,avg_alpha_neg , beta ,nu , rho)
vol_pos = haganLogNormalApprox (y, expiry , F_pos ,avg_alpha_pos , beta ,nu , rho)

print(f' Vol Black = {vol_0}')
print(f' Vol Positive = {vol_pos}')
print(f' Vol Negative = {vol_neg}')
vol_avg = (vol_pos + vol_neg)/2
print(f' Vol Average (Centred) = {vol_avg}')

Resultado:

 Vol Black = 0.44137660374291093
 Vol Positive = 0.4396541985333539
 Vol Negative = 0.4431007329951456
 Vol Average (Centred) = 0.44137746576424974

También reescribí Black porque me resulta más fácil trabajar con funciones menos enrevesadas:

import numpy as np
from scipy.stats import norm

def B(F,K,t, sigma, cp_flag):
    d1 = ((np.log(F/K)  + 0.5 * sigma **2 * t) / (sigma * np.sqrt (t)))
    d2 = d1 - sigma * np.sqrt(t) 
    opt = cp_flag*(F*norm.cdf(cp_flag*d1) - K*norm.cdf(cp_flag*d2))
    delta = cp_flag*norm.cdf(cp_flag*d1)
    return opt, delta

Ahora, echemos cuentas:

up = B(F_pos,0.10,0.25, vol_pos, 1)
down = B(F_neg,0.10,0.25, vol_neg , 1)
centred = B(F_0,0.10,0.25, vol_0 , 1)

print(f'Call Val Up = {up[0]}, Delta Up = {up[1]}')
print(f'Call Val Down = {down[0]}, Delta Down = {down[1]}')
print(f'Call Val Centred = {centred[0]}, Delta Centred = {centred[1]}')
print(f'Delta FD = {(up[0]-down[0])/(2.0*small_figure)}')
print(f'Delta FD centred = {(B(F_pos,0.10,0.25, vol_0, 1)[0]- B(F_neg,0.10,0.25, vol_0, 1)[0])/(2.0*small_figure)}')

para obtener

Call Val Up = 0.0015010173779386893, Delta Up = 0.1756511129543487
Call Val Down = 0.0015012761421948125, Delta Down = 0.17503196098493057
Call Val Centred = 0.0015011434512114622, Delta Centred = 0.1753400660949036
Delta FD = -0.0012938212806158644
Delta FD centred = 0.1753410636751336

Como puede ver, utilizando un vol constante (la media de los vol al alza y a la baja específicamente) se obtiene prácticamente el mismo delta que en Black. Lo que se introduce en FD es un IV diferente para las subidas y las bajadas, lo que coincide con el modelo SABR. Estoy utilizando la fórmula de este responder que son las citas que figuran en la página 91 del artículo Gestionar el riesgo Smile de Hagan et. al en la revista Willmott. Reescrito en Python, el código tiene este aspecto:

def vol(,, , , t_ex, f, K):
        A =  /(((f*K)**((1-)/2))*(1+((1-)**2)/24*np.emath.logn(2,(f/K))+ ((1-)**4)/1920*np.emath.logn(4,(f/K))))
        B = 1+(((1-)**2)/24*(**2/(f*K)**(1-))+(1/4)****/((f*K)**((1-)/2))+(2-3***2)/24***2)*t_ex
        z = /*(f*K)**((1-)/2)*np.log(f/K)
        _z = np.log((np.sqrt(1-2**z+z**2)+z-)/(1-))
        atm = /(f**(1-))*(1+(((1-)**2)/24*(**2/(f*K)**(1-))+(1/4)****/((f*K)**((1-)/2))+(2-3***2)/24***2)*t_ex)
        cond = f==K
        return atm if cond else A*z/_z*B

Si trazamos esto en función de F, está claro que tiene sentido que IV sea sobre Negro IV si F está conmocionado hacia abajo, y debajo de Negro IV es F está conmocionado, incluso si usted no ajustaría $$ (lo que hace que el efecto sea un poco más pronunciado):

, , , , t_ex, f = 0.5, 0.127693338654331, -0.473149790316068, 2.46284420168144, 0.25, 0.07942844548137806
K=0.1

plt.plot( np.arange(0.05,0.14,0.003), [vol(, alpha_0, , , t_ex, F, K) for F in np.arange(0.05,0.14,0.003)], label = 'SABR Vols')
font = {'family':'serif','color':'blue','size':20}
plt.xlabel("Forward",fontdict = font)
plt.ylabel("IVOL",fontdict = font)
plt.title("SABR Model",fontdict = font)
plt.vlines(F_0, 0, vol(, alpha_0, , , t_ex, F_0, K), colors ="r", label = 'Current Forward')
plt.legend(loc = 'lower right')
plt.show()

enter image description here

La línea roja corresponde al IV en el avance actual. A pesar de ser una implementación diferente, el IV resultante es casi idéntico a su implementación y también resulta en un delta negativo similar cuando se utiliza un método FD con IV ajustado.

Después de todo, toda la razón por la que Bartlett (2006) proporciona una delta refinada bajo el modelo SABR es para tener en cuenta el efecto de vol. Si se mueve a lo largo de la superficie de vol cuando el subyacente cambia, tendrá diferentes valores de mercado, no sólo debido a los cambios en el subyacente, sino también debido a los cambios en IV.

Sin embargo, se demostró que para una cartera que está cubierta tanto delta como vega, las griegas SABR originales dadas por Hagan et al. (2002) proporcionan esencialmente el mismo resultado que las nuevas griegas SABR de Bartlett. Véase, por ejemplo Cobertura según SABR modelo de Bruce Bartlett en Wilmott Feb 2006. Esta afirmación también es fácil de verificar si añadimos vega a la fórmula Black:

def B(F,K,t, sigma, cp_flag):
    d1 = ((np.log(F/K)  + 0.5 * sigma **2 * t) / (sigma * np.sqrt (t)))
    d2 = d1 - sigma * np.sqrt(t) 
    opt = cp_flag*(F*norm.cdf(cp_flag*d1) - K*norm.cdf(cp_flag*d2))
    delta = cp_flag*norm.cdf(cp_flag*d1)
    vega = F*norm.pdf(d1)*np.sqrt(t)
    return opt, delta , vega

Si calcula el valor de la nueva opción para un $F_{pos}$ valor del subyacente, se obtiene (utilizando el nuevo vol según la superficie vol):

res = B(F_pos,0.10,0.25, vol_pos , 1)
print(f'Call New = {res[0]}')

Llamada Nueva = 0,0015010173779386893

Utilizando una aproximación de Taylor con delta de Black se obtendría:

print(f'Value according to Black Delta = {centred[0] + centred[1]*(F_pos-F_0)}')

Valor según Black Delta = 0,001518677457820953

Sin embargo, es demasiado alto. Utilizando el delta de Bartlett (que en este caso es negativo) se obtiene

print(f"Value according to Bartlett's Delta = {centred[0] + (up[0]-down[0])/(2.0*small_figure)*(F_pos-F_0)}")

Valor según Delta de Bartlett = 0,0015010140690834006

que es casi idéntico al valor obtenido utilizando Black delta y Vega juntos (y muy cercano al valor real de Black si lo hubieras calculado directamente sin griegas).

print(f'Value according to Black Delta and Vega = {centred[0] + centred[1]*(F_pos-F_0) + centred[2]*(vol_pos-vol_0)}')

Valor según Black Delta y Vega = 0,0015010228771048103

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