Después de las vacaciones, estoy de regreso con otro nuevo tutorial. Espero que se la hayan pasado muy bien!
Intro
En este tutorial vamos a cubrir lo siguiente:
Put-Call parity
El modelo Black-Scholes
Sobre la volatilidad
1 Put-Call parity
Put-Call parity es un concepto que establece la relación de los precios de los calls y puts europeos sobre un subyacente. La proposición dice lo siguiente:
Sea
C_0
el precio de un call europeo con precio de ejercicioK
sobre una acción cuyo precio actual esS_0
. SeaP_0
el precio de un put europeo sobre la misma acción con el mismo precio de ejercicioK
. Si ambas opciones tienen la misma fecha de expiraciónT
y la tasa de interés compuesta continua esr
, entonces:C_0 + K*exp(-r*T) = P_0 + S_0
Podemos probarlo de la siguiente manera:
Imaginemos que en nuestro portafolio compramos el call y un bono cupón cero que expira al mismo tiempo que las opciones por el valor del precio de ejercicio. Nuestro desembolso dería de -(C_0 + K*exp(-r*T))
. Al mismo tiempo, shorteamos el put y la acción, recibiendo entonces P_0 + S_0
. En el tiempo 0 entonces nuestro portafolio vale:
- C_0 - K*exp(-r*T) + P_0 + S_0
Al tiempo T
hay de dos: o el precio de la acción, S_T
, es menor que el strike K
o mayor que él. Veamos que sucede en ambos casos:
Si es menor (S_T < K
):
El call expira out-of-the-money (OTM) y por lo tanto no vale nada. Recibo K
por mi inversión en el bono cupón cero. El put expira in-the-money (ITM) y por lo tanto vale -(K - S_T)
. Cierro el short de la acción, esta transacción tiene valor -S_T
. Mi ganancia es de:
K - (K - S_T) - S_T = 0
Si es mayor (S_T > K
):
El call expira ITM y por lo tanto vale S_T - K
. Recibo K
por mi inversión en el bono cupón cero. El put expira OTM y por lo tanto no vale nada. Cierro el short de la acción, esta transacción tiene valor -S_T
. Mi ganancia es de:
S_T - K + K - S_T = 0
Dado que esta estrategia tiene una utilidad de 0 en el tiempo T
independientemente de lo que pase con el precio de la acción, concluímos que necesariamente el costo de entrar a ella es también de 01. Por lo tanto:
- C_0 - K*exp(-r*T) + P_0 + S_0 = 0
Y la proposición inicial queda demostrada.
2 El modelo Black-Scholes
Dado una acción cuyo precio sigue una distribución log-normal, el precio de un call europeo sobre esta acción, según el modelo de Black-Scholes, está dado por:
C = S*N(d1) - K*exp(-r*T)*N(d2)
con d1 = [ln(S/K) + (r + 𝜎^2/2)*T]/(𝜎*T^1/2)
d2 = d1 - 𝜎*T^1/2
donde S : el precio de la acción
K : el strike de la opción
r : la tasa libre de riesgo
T : el tiempo a expiración de la opción
N(): el valor de la distribución normal estándar
𝜎 : la desviación estándar de los retornos logarítmicos
El precio del put, según este mismo modelo, está dado por:
P = K*exp(-r*T)*N(-d2) - S*N(-d1)
2.1 Implementación del modelo Black-Scholes en R
Vamos a implementar este modelo en nuestro archivo “herramientas.R”
para poder usarlo en los siguientes ejemplos:
black.scholes.model <- function(strike, spot, rf = 0.02, t = 0,
sigma = 0.2, tipo = "c") {
# Calcula el precio teórico de una opción usando el
# modelo de Black-Scholes (1973)
# Se necesita:
# - strike: strike de la opción
# - spot: precio spot del subyacente al momento de valuación
# - rf: tasa libre de riesgo (anualizada)
# - t: tiempo (en años) que falta para que expire la opción
# - sigma: volatilidad (anualizada) del subyacente
# - tipo: "c" (call) o "p" (put)
# Se regresa:
# El precio de la opción según el modelo Black-Scholes (1973)
tipo = tolower(tipo)
if (!(tipo %in% c("c", "p"))) {
stop("El tipo de la opción es incorrecto.
Elige uno de 'c' (call) o 'p' (put).")
}
if (t == 0) {
return(calcular.profit(strike, spot, preccio = 0, tipo))
}
d1 <- (log(spot/strike) + (rf + sigma^2/2)*t)/(sigma*sqrt(t))
d2 <- d1 - sigma*sqrt(t)
if (tipo == "c") {
return(spot*pnorm(d1) - strike*exp(-rf*t)*pnorm(d2))
} else {
return(strike*exp(-rf*t)*pnorm(-d2) - spot*pnorm(-d1))
}
}
Podemos ver que la implementación en R es muy simple. Noten como, si el tiempo a expiración es cero (t == 0
), podemos usar la función que vimos en el tutorial pasado, calcular.profit()
, para calcular el valor de la opción. Veamos ahora cómo podemos usar esta función para calcular el precio de una opción en específico:
# 0. Limpiar la sesión
rm(list=ls())
if (names(dev.cur()) != "null device") {
dev.off()
}
cat("\014")
# 1. Cargar herramientas
source("scripts/herramientas.R")
# 2. Calcular el valor de un call y un put usando Black-Scholes
strike <- 45
spot <- 50
rf <- 0.04
t <- 0.75
sigma <- 0.3
call <- black.scholes.model(strike, spot, rf, t, sigma, "c")
put <- black.scholes.model(strike, spot, rf, t, sigma, "p")
En nuestro caso, la acción se vende a $50 y queremos calcular el precio del call y el put con strike de $45. El call va a estar ITM por $5 (S - K = $5), así que mínimo debe de valer estos $5. El put está OTM, o sea que su valor intrínseco es 0 (nadie vendería por $45 algo que puede vender a $50). Vemos que, según el modelo de Black-Scholes, el call y el put deben de valer en este punto $8.64 y $2.31, respectivamente:
2.2 El valor intrínseco y extrínseco de una opción
Vimos anteriormente que, aunque el call sólo está $5 ITM y el put $0 ITM, el valor del call es mayor a $5 y el valor del put es mayor a $0. Al valor de la opción si fuera ejercida en este momento, el $5 y el $0 en el caso del call y put anteriores, respectivamente, se le conoce como el valor intrínseco de la opción. A la diferencia entre el precio y el valor intrínseco, $3.64 y $2.31 en el caso del call y put anteriores, se le conoce como el valor extrínseco de la opción.
El valor extrínseco viene de dos fuentes principales: el tiempo que falta para expiración y la volatilidad implícita, es decir la volatilidad que el mercado espera para el precio de la acción en cuestión. A mayor tiempo para expiración y mayor volatilidad implícita, es más probable que la opción termine estando ITM. Modifiquen los valores en el script anterior y verán como incrementa el valor del put al aumentar estos dos parámetros, aunque el strike no se mueva y la opción siga estando OTM.
Comparemos ahora el valor intrínseco y el valor de las opciones según el modelo Black-Scholes:
# 0. Limpiar la sesión
rm(list=ls())
if (names(dev.cur()) != "null device") {
dev.off()
}
cat("\014")
# 1. Cargar herramientas
source("scripts/herramientas.R")
# 2. Calcular el valor intrínseco vs. el valor de Black-Scholes
strike <- 45
spot <- seq(from = 5, to = 80, by = 5)
rf <- 0.04
t <- 1
sigma <- 0.3
call_bs <- black.scholes.model(strike, spot, rf, t, sigma, "c")
call_intrinsic <- calcular.profit(strike, spot, 0, "c")
par(mar = c(5.1, 4.1, 4.1, 8.1))
plot(x = spot, y = call_bs, type = "l", lty = 2, lwd = 2,
main = paste("Opción call (Strike = $",
strike, ")", sep = ""),
xlab = "Precio del subyacente", ylab = "Valor")
lines(x = spot, y = call_intrinsic, col = "red", lwd = 2)
legend("topright", inset = c(-0.2, 0), cex = 0.7,
legend = c("Black-Scholes", "Intrínseco"),
lty = c(2, 1), col = c("black", "red"), lwd = 2,
title = "Valor", box.lty = 0, xpd = TRUE)
put_bs <- black.scholes.model(strike, spot, rf, t, sigma, "p")
put_intrinsic <- calcular.profit(strike, spot, 0, "p")
plot(x = spot, y = put_bs, type = "l", lty = 2, lwd = 2,
main = paste("Opción put (Strike = $",
strike, ")", sep = ""),
xlab = "Precio del subyacente", ylab = "Valor")
lines(x = spot, y = put_intrinsic, col = "red", lwd = 2)
legend("topright", inset = c(-0.2, 0), cex = 0.7,
legend = c("Black-Scholes", "Intrínseco"),
lty = c(2, 1), col = c("black", "red"), lwd = 2,
title = "Valor", box.lty = 0, xpd = TRUE)
plot(x = spot, y = call_bs, type = "l", lwd = 2,
main = paste("Opción call vs put (Strike = $",
strike, ")", sep = ""),
xlab = "Precio del subyacente", ylab = "Valor")
lines(x = spot, y = put_bs, col = "red", lwd = 2)
legend("topright", inset = c(-0.15, 0), cex = 0.7,
legend = c("Call", "Put"),
lty = 1, col = c("black", "red"), lwd = 2,
title = "Opción", box.lty = 0, xpd = TRUE)
2.3 ¿Por qué el valor extrínseco del put es negativo?
Una pregunta que seguramente se les vino a la mente al ver las gráficas anteriores puede ser la siguiente: ¿por qué el valor calculado con Black-Scholes cuando el put está ITM es menor que el valor intrínseco? O, dicho de otra manera, ¿por qué el valor extrínseco del put es negativo cuando está ITM? La respuesta es más fácil de explicar si consideramos el siguiente caso extremo:
# 0. Limpiar la sesión
rm(list=ls())
if (names(dev.cur()) != "null device") {
dev.off()
}
cat("\014")
# 1. Cargar herramientas
source("scripts/herramientas.R")
# 2. Calcular el valor de un call y un put usando Black-Scholes
strike <- 200
spot <- 50
rf <- 0.015
t <- 1
sigma <- 0.3
call <- black.scholes.model(strike, spot, rf, t, sigma, "c")
put <- black.scholes.model(strike, spot, rf, t, sigma, "p")
En este escenario, tenemos un put con strike de $200 sobre una acción que se vende a $50. El valor intrínseco de este put es de $150 ($200 - $50). Sin embargo, el valor según el modelo Black-Scholes es menor:
La diferencia se da porque el modelo Black-Scholes da precios para opciones europeas. Si la opción fuera americana, sin duda tendría que valer mínimo $150, puesto que la puedo ejercer en este mismo instante. Sin embargo, para el modelo Black-Scholes esta no es una posibilidad y entonces debo esperar 1 año para ejercer la opción. Los $200 que recibiré en un año no son lo mismo que $200 en este momento. Si la tasa libre de riesgo es de 1.5%, esos $200 valen en este momento $197.0224 y, dado que la acción vale $50, entonces el put vale $147.0224:
Por esta razón el valor del put ITM usando Black-Scholes es menor que el valor intrínseco del mismo put.
3 Sobre la volatilidad
Como vimos en la sección anterior, uno de los elementos principales para determinar el precio de las opciones usando el modelo Black-Scholes es la volatilidad del subyacente. Vamos a calcular los precios para opciones at-the-money (ATM) del ETF SPY para ejemplificar el procedimiento. Lo primero es calcular la volatilidad usando retornos históricos. En la práctica, hay gente que utiliza los retornos de los últimos 30 días hasta varios años, hay gente que prefiere retornos diarios, otros semanales, otros mensuales, y hay gente que para anualizar retornos diarios utiliza 250, 252, o 365 días. Yo voy a usar los retornos diarios del último año y utilizar 252 días para anualizar. Tomando los precios ajustados de 2021-01-09 a 2022-01-09, la volatilidad anualizada del SPY es de 12.99%:
Si no recuerdas cómo calcular esto, date una vuelta por este post.
Comparemos ahora los precios calculados con esta volatilidad vs. los precios reales que existen para las opciones ATM del SPY que hay disponibles en este momento para el 2022:
# 0. Limpiar la sesión
rm(list=ls())
if (names(dev.cur()) != "null device") {
dev.off()
}
cat("\014")
# 1. Cargar herramientas
library(quantmod)
source("scripts/herramientas.R")
# 2. Bajar datos de opciones
fechas <- c("2022-01-21", "2022-02-18",
"2022-03-18", "2022-04-14", "2022-05-20",
"2022-06-17", "2022-09-16", "2022-12-16")
subyacente <- "SPY"
strike <- 465
calls <- c()
puts <- c()
for (fecha in fechas){
# print(fecha)
string_call <- paste(subyacente,
gsub(pattern = "-", replacement = "",
gsub("^20", "", fecha)),
"C", "00", strike, "000", sep = "")
string_put <- paste(subyacente,
gsub(pattern = "-", replacement = "",
gsub("^20", "", fecha)),
"P", "00", strike, "000", sep = "")
temp <- getOptionChain(subyacente, Exp = fecha)
calls <- c(calls, temp$calls[string_call, ]$Last)
puts <- c(puts, temp$puts[string_put, ]$Last)
}
calls_ts <- xts(x = calls, order.by = as.Date(fechas))
puts_ts <- xts(x = puts, order.by = as.Date(fechas))
# 3. Calcular precios teóricos con modelo Black-Scholes
spot <- 466.09
t <- as.numeric((as.Date(fechas) - Sys.Date())/365)
rf <- 0.0043
sigma <- 0.1298833
calls_bs <- black.scholes.model(strike, spot, rf,
t, sigma, "c")
puts_bs <- black.scholes.model(strike, spot, rf,
t, sigma, "p")
calls_bs_ts <- xts(x = calls_bs, order.by = as.Date(fechas))
puts_bs_ts <- xts(x = puts_bs, order.by = as.Date(fechas))
calls_comp <- cbind(calls_ts, calls_bs_ts)
puts_comp <- cbind(puts_ts, puts_bs_ts)
print(plot(calls_comp, legend.loc = "topleft"))
print(plot(puts_comp, legend.loc = "topleft"))
Estamos usando el strike de 465, el spot lo tenemos correctamente definido en 466.09 (a eso cerró el 2022-01-07). Estamos usando la función getOptionChain()
del paquete quantmod
para ir a la sección de opciones sobre el SPY en Yahoo! Finance y bajar los datos de esa página. La tasa libre de riesgo, rf
, la saqué de esta página para el 2022-01-07. Pueden “descomentar” la línea 24 si quieren ver en su consola el progreso con la descarga de los datos.
Podemos ver que, en la práctica, los precios de las opciones no son muy ceranos a lo que calculamos con el modelo Black-Scholes. Este modelo tiene muchísimas suposiciones que no necesariamente se apegan a la realidad, por ejemplo:
Supone volatilidad y tasa libre de riesgo constante durante la vida de la opción, aunque sabemos que, en la práctica, estos valores cambian constantemente
Supone trading continuo y sin costos, ignorando limitantes de liquidez y comisiones de los brokers
Supone que los precios de las acciones siguen una distribución log-normal, ignorando los grandes cambios en precios que son observados ocasionalmente en la vida real
Supone que la acción no repartirá dividendos
Supone que la opción es europea, o sea que no hay posibilidad de ejercicio temprano
Supone que no hay arbitraje e ignora requerimientos de margen al tener posiciones en corto
Por todas esas, y más cosas, el modelo no es muy usado para determinar precios reales de opciones. Sin embargo, entre otros usos que veremos después, es de mucha utilidad para calcular la volatilidad implícita del mercado (o sea, la expectativa del mercado sobre la volatilidad de una acción en el futuro). Veamos a continuación cómo podemos determinar esto.
3.1 La volatilidad implícita
Dado que el modelo Black-Scholes tiene muchas suposiciones que no son realistas, calcular el precio de una opción con este modelo tiene poca utilidad. Sin embargo, lo que podemos hacer es, dado el precio de mercado de una opción, calcular la volatilidad que nos daría ese precio según el modelo Black-Scholes. Para eso, usamos la siguiente función que pueden agregar a su archivo de herramientas.R
:
vol.imp.bs <- function(strike, spot, rf = 0.02, t = 0,
precio = 1, tipo = "c") {
# Calcula la volatilidad implícita de una opción con el
# modelo Black-Scholes (1973)
# Se necesita:
# - strike: strike de la opción
# - spot: precio spot del subyacente al momento de valuación
# - rf: tasa libre de riesgo (anualizada)
# - t: tiempo (en años) que falta para que expire la opción
# - precio: precio de la opción
# - tipo: "c" (call) o "p" (put)
# Se regresa:
# La volatilidad con la cual el modelo Black-Scholes (1973)
# (black.scholes.model) da el precio proporcionado
vol.alta <- 10
vol.baja <- 0
tolerancia <- 1e-6
iter <- 1
max.iter <- 1000
while((vol.alta - vol.baja) > tolerancia & iter < max.iter) {
if (black.scholes.model(strike, spot, rf, t,
(vol.alta + vol.baja)/2,
tipo) > precio) {
vol.alta <- (vol.alta + vol.baja)/2
} else {
vol.baja <- (vol.alta + vol.baja)/2
}
iter <- iter + 1
}
return((vol.alta + vol.baja)/2)
}
En esta función estamos implementando el algoritmo de bisección para encontrar los ceros de una función. En este caso, la función es la diferencia entre el precio real de la opción, precio
, y el precio calculado con el modelo Black-Scholes. Una vez que encontramos la volatilidad que corresponde a ese precio, regresamos el valor. Añadiendo al script de la sección 3, podemos calcular la volatilidad implícita de la siguiente manera:
# 4. Calcular vol. implícita según el modelo Black-Scholes
vol_calls <- c()
vol_calls <- mapply(vol.imp.bs, strike = strike, spot = spot,
rf = rf, t = t, precio = calls, tipo = "c")
vol_puts <- mapply(vol.imp.bs, strike = strike, spot = spot,
rf = rf, t = t, precio = puts, tipo = "p")
Y podemos ver que, aunque la volatilidad del último año para el SPY fue de 12.99%, los precios actuales de los calls y puts indican una expectativa de mayor volatilidad para el siguiente año:
La diferencia entre la volatilidad implícita de los calls y puts viene de varios factores, incluyendo los pagos de dividendos y las comisiones más altas que implica shortear una acción.
Eso fue todo por hoy. Espero que este tutorial les haya servido. Si tienen cualquier duda o comentario pueden dejarlo en la sección de abajo. Si quieren compartir este blog con sus amigos y compañeros o suscribirse, les dejo los botones aquí:
Hasta el próximo tutorial!
Este es un hecho fundamental en finanzas: si una estrategia tiene ganancia de 0 en el futuro, su costo de entrada entonces también debe ser 0. Lo contrario tambien es cierto: si una estrategia tiene ganancia mayor a 0 en el futuro, su costo de entrada debe ser mayor a 0.