En este laboratorio se construye un portafolio de acciones y se simula su comportamiento bajo un Movimiento Browniano Geométrico (MGB) para un horizonte de inversión de largo plazo. A partir de tres activos accionarios (MSFT, NKE y NEE) se estima un portafolio de media-varianza, que servirá posteriormente como subyacente para la valuación de opciones europeas y americanas y el diseño de estrategias de cobertura.
La inversión total considerada es de 10 millones de dólares, y se utiliza información histórica de precios desde el 01/10/2023 en adelante. En la primera parte se construye el portafolio y se simulan las trayectorias de precios del portafolio bajo un modelo MGB. En la segunda parte se analiza el desempeño del portafolio mediante volatilidad, índice de Sharpe y medidas de riesgo como el VaR. Finalmente, en la tercera parte se valúan opciones europeas y americanas sobre cada activo y se diseña una estrategia de cobertura del 85 % de la inversión, apalancada a la tasa del bono del Tesoro a 10 años.
# Definir las tres acciones del portafolio
tickers <- c("MSFT", "NKE", "NEE")
# Fecha de inicio de la base de datos
fecha_inicio <- "2023-10-01"
# Descarga de precios ajustados diarios
precios <- tq_get(tickers,
from = fecha_inicio,
get = "stock.prices") %>%
select(symbol, date, adjusted) %>%
pivot_wider(names_from = symbol, values_from = adjusted)
head(precios)
## # A tibble: 6 × 4
## date MSFT NKE NEE
## <date> <dbl> <dbl> <dbl>
## 1 2023-10-02 317. 91.1 49.1
## 2 2023-10-03 309. 91.6 49.7
## 3 2023-10-04 314. 92.4 47.6
## 4 2023-10-05 315. 92.3 46.5
## 5 2023-10-06 322. 93.6 47.3
## 6 2023-10-09 325. 93.3 46.4
En la tabla anterior se observan los precios ajustados diarios de las acciones MSFT, NKE y NEE desde la fecha seleccionada.
# Ordenar por fecha y calcular retornos logarítmicos diarios
ret_diarios <- precios %>%
arrange(date) %>%
mutate(across(-date,
~ log(.x / dplyr::lag(.x)),
.names = "ret_{col}")) %>%
select(date, starts_with("ret_")) %>%
drop_na()
head(ret_diarios)
## # A tibble: 6 × 4
## date ret_MSFT ret_NKE ret_NEE
## <date> <dbl> <dbl> <dbl>
## 1 2023-10-03 -0.0265 0.00559 0.0120
## 2 2023-10-04 0.0176 0.00838 -0.0418
## 3 2023-10-05 0.00125 -0.00104 -0.0234
## 4 2023-10-06 0.0244 0.0137 0.0158
## 5 2023-10-09 0.00779 -0.00237 -0.0185
## 6 2023-10-10 -0.00435 0.00761 0.0448
# Matriz de retornos (sin la columna date)
R <- ret_diarios %>%
select(-date) %>%
as.matrix()
# Vector de medias de retornos diarios (mu) y matriz de covarianza (Sigma)
mu <- colMeans(R)
Sigma <- cov(R)
mu
## ret_MSFT ret_NKE ret_NEE
## 0.0009216998 -0.0007568996 0.0009706351
Sigma
## ret_MSFT ret_NKE ret_NEE
## ret_MSFT 1.935298e-04 6.585597e-05 6.467272e-06
## ret_NKE 6.585597e-05 5.495114e-04 7.697382e-05
## ret_NEE 6.467272e-06 7.697382e-05 3.128712e-04
A partir de la serie de precios diarios desde el 01/10/2023 se calcularon retornos logarítmicos para las tres acciones MSFT, NKE y NEE. Los promedios de los retornos diarios (vector mu) permiten identificar cuáles activos han presentado, en el período de estudio, una mayor tendencia de crecimiento esperado.
De forma complementaria, la matriz de covarianza (Sigma) muestra cómo se mueven conjuntamente los activos. Covarianzas positivas entre los pares de acciones indican que tienden a moverse en la misma dirección, mientras que covarianzas relativamente bajas sugieren que existe potencial de diversificación. En este caso, la estructura de Sigma es coherente con un portafolio de renta variable diversificada: los activos están correlacionados, pero no de forma perfecta, lo que abre espacio para reducir riesgo mediante la combinación adecuada de pesos.
n <- length(mu) # número de activos
# Matrices para solve.QP (minimizar 1/2 x' D x - d' x)
Dmat <- 2 * Sigma
dvec <- rep(0, n)
# Restricciones en forma: t(Amat) %*% w >= bvec
# 1) sum(w) = 1 (primera restricción, tratada como igualdad)
# 2) w_i >= 0 (no short-selling)
Amat <- cbind(
rep(1, n), # sum(w) >= 1
diag(n) # w_i >= 0
)
bvec <- c(1, rep(0, n))
sol <- solve.QP(Dmat, dvec, Amat, bvec, meq = 1) # meq=1 → primera restricción es igualdad
w_opt <- sol$solution
names(w_opt) <- tickers
w_opt
## MSFT NKE NEE
## 0.56178668 0.09882289 0.33939043
sum(w_opt) # Debe ser ~1
## [1] 1
inversion_total <- 10e6 # 10 millones de dólares
monto_por_accion <- w_opt * inversion_total
monto_por_accion
## MSFT NKE NEE
## 5617866.8 988228.9 3393904.3
# Precio al último día disponible
precios_ult <- precios %>%
arrange(date) %>%
tail(1) %>%
select(-date) %>%
as.numeric()
names(precios_ult) <- tickers
precios_ult
## MSFT NKE NEE
## 514.33 61.23 81.69
# Cantidad de acciones compradas de cada activo
cantidades <- monto_por_accion / precios_ult
cantidades
## MSFT NKE NEE
## 10922.69 16139.62 41546.14
Con los parámetros mu y Sigma se construyó un portafolio de varianza mínima global, sujeto a dos condiciones: (i) la suma de los pesos es igual a 1 y (ii) no se permiten posiciones cortas. Los pesos óptimos obtenidos reflejan qué proporción de la inversión total se asigna a cada acción para minimizar el riesgo total del portafolio.
El hecho de que algunos activos reciban un peso mayor indica que, dadas las covarianzas observadas, estos instrumentos contribuyen de manera más eficiente a reducir la volatilidad agregada. En otras palabras, no solo importa el riesgo y el retorno individual de cada acción, sino también cómo interactúa con el resto del portafolio. La solución de Markowitz aprovecha precisamente esos efectos de diversificación: si dos activos no se mueven exactamente igual, una combinación adecuada puede lograr un nivel de riesgo menor que el de cualquiera de ellos por separado.
A continuación se simulan trayectorias de precios para cada activo y para el portafolio utilizando un Movimiento Browniano Geométrico (MGB). Se considera un horizonte de 2 años con pasos diarios de mercado (aprox. 252 días al año).
set.seed(123) # Para reproducibilidad
# Parámetros de simulación
anios <- 2
dias_por_anio <- 252
N <- anios * dias_por_anio
dt <- 1 / dias_por_anio
# Precio inicial de cada acción (último precio observado)
S0 <- precios_ult
S0
## MSFT NKE NEE
## 514.33 61.23 81.69
mu
## ret_MSFT ret_NKE ret_NEE
## 0.0009216998 -0.0007568996 0.0009706351
Sigma
## ret_MSFT ret_NKE ret_NEE
## ret_MSFT 1.935298e-04 6.585597e-05 6.467272e-06
## ret_NKE 6.585597e-05 5.495114e-04 7.697382e-05
## ret_NEE 6.467272e-06 7.697382e-05 3.128712e-04
# Función para simular trayectorias MGB correlacionadas
simular_mgb <- function(S0, mu, Sigma, dt, N, n_sim = 1000) {
n_activos <- length(S0)
L <- chol(Sigma) # descomposición de Cholesky
# Array con dimensiones: tiempo x simulación x activo
S <- array(NA, dim = c(N + 1, n_sim, n_activos))
# Condición inicial: todos los escenarios parten de S0
S[1, , ] <- matrix(rep(S0, each = n_sim),
nrow = n_sim,
byrow = FALSE)
for (t in 2:(N + 1)) {
# Z ~ N(0, I)
Z <- matrix(rnorm(n_sim * n_activos),
nrow = n_sim,
ncol = n_activos)
# Choques correlacionados
eps <- Z %*% L
# Drift y difusión
drift <- (mu - 0.5 * diag(Sigma)) * dt # vector (n_activos)
diffu <- sqrt(dt) * eps # matriz n_sim x n_activos
# Actualización de precios
S[t, , ] <- S[t - 1, , ] * exp(
matrix(rep(drift, each = n_sim),
nrow = n_sim,
byrow = FALSE) +
diffu
)
}
return(S)
}
# Número de simulaciones
n_sim <- 1000
S_sim <- simular_mgb(S0 = S0,
mu = mu,
Sigma = Sigma,
dt = dt,
N = N,
n_sim = n_sim)
dim(S_sim) # tiempo x simulación x activos
## [1] 505 1000 3
w <- w_opt
n_act <- length(w)
tiempo <- 0:N
# Matriz con el valor del portafolio: filas = tiempo, columnas = simulaciones
V <- matrix(NA, nrow = N + 1, ncol = n_sim)
for (t in 1:(N + 1)) {
# precios en el tiempo t: matriz n_sim x n_activos
S_t <- S_sim[t, , ]
S_t <- matrix(S_t, nrow = n_sim, ncol = n_act)
# valor del portafolio en cada simulación
V[t, ] <- S_t %*% w
}
# Estadísticos descriptivos por tiempo
V_media <- rowMeans(V)
V_q05 <- apply(V, 1, quantile, 0.05)
V_q95 <- apply(V, 1, quantile, 0.95)
head(V_media)
## [1] 322.7195 322.7248 322.7241 322.7175 322.7076 322.6937
La simulación mediante Movimiento Browniano Geométrico (MGB) permite proyectar trayectorias posibles del valor del portafolio durante un horizonte de dos años. El modelo asume retornos lognormales, volatilidad constante y shocks aleatorios con distribución normal, correlacionados según la matriz Sigma estimada.
La curva media de la simulación representa el valor esperado del portafolio en cada punto del tiempo, mientras que las bandas formadas por los cuantiles 5 % y 95 % muestran un intervalo de confianza aproximado del 90 %. Una banda más ancha indica una mayor incertidumbre sobre el valor futuro de la inversión, es decir, mayor riesgo. La tendencia general de la media (creciente, estable o volátil) refleja la interacción entre el retorno esperado y la volatilidad de los activos.
En conjunto, estos resultados permiten visualizar no solo un escenario puntual, sino un conjunto de escenarios probables para la evolución de la inversión de 10 millones de dólares, lo cual es fundamental para la toma de decisiones de cobertura con derivados.
En esta sección se evalúa el comportamiento del portafolio construido mediante el modelo de media-varianza. A partir de los retornos históricos desde el 01/10/2023 se calculan:
# Tasa libre de riesgo (bono del Tesoro de EE. UU. a 10 años)
rf_anual <- 0.04 # 4 % anual
rf_diaria <- rf_anual / 252
rf_anual
## [1] 0.04
rf_diaria
## [1] 0.0001587302
Se adopta una tasa libre de riesgo del 4 % anual, correspondiente al rendimiento de referencia de los bonos del Tesoro de Estados Unidos a 10 años. Esta tasa se utilizará para el cálculo del índice de Sharpe y la valuación de derivados en la estrategia de cobertura.
# Retornos históricos del portafolio (combinación lineal de los activos)
R_port_hist <- R %*% w_opt # R es la matriz de retornos de los activos
colnames(R_port_hist) <- "Portafolio"
# Volatilidad diaria histórica (por activo y para el portafolio)
sigma_diaria_activos <- apply(R, 2, sd)
sigma_diaria_port <- sd(R_port_hist)
sigma_diaria_activos
## ret_MSFT ret_NKE ret_NEE
## 0.01391150 0.02344166 0.01768817
sigma_diaria_port
## [1] 0.0108363
La volatilidad diaria de cada activo mide la dispersión de sus retornos alrededor de la media, es decir, qué tan “nervioso” es el comportamiento del precio en el corto plazo. En general, acciones con volatilidad más alta implican un mayor riesgo de fluctuaciones fuertes, tanto al alza como a la baja.
Al comparar la volatilidad de cada acción con la volatilidad del portafolio, se observa que la combinación de MSFT, NKE y NEE logra un nivel de riesgo agregado que puede ser menor al de algunos activos por separado. Esto es consistente con el principio de diversificación: al combinar activos que no se mueven de forma perfectamente sincronizada, el riesgo total puede reducirse, aun cuando cada activo individualmente tenga una volatilidad considerable.
El índice de Sharpe permite medir la relación entre el rendimiento excedente del portafolio frente a la tasa libre de riesgo y el riesgo asumido:
Sharpe = (mu_portafolio - rf) / sigma_portafolio.
dias_por_anio <- 252
# Medias diarias históricas (activos y portafolio)
mu_diaria_activos <- colMeans(R)
mu_diaria_port <- mean(R_port_hist)
# Anualización de medias y volatilidades
mu_anual_activos <- (1 + mu_diaria_activos)^dias_por_anio - 1
mu_anual_port <- (1 + mu_diaria_port)^dias_por_anio - 1
sigma_anual_activos <- sigma_diaria_activos * sqrt(dias_por_anio)
sigma_anual_port <- sigma_diaria_port * sqrt(dias_por_anio)
# Índice de Sharpe para cada activo y para el portafolio
Sharpe_activos <- (mu_anual_activos - rf_anual) / sigma_anual_activos
Sharpe_port <- (mu_anual_port - rf_anual) / sigma_anual_port
Sharpe_activos
## ret_MSFT ret_NKE ret_NEE
## 1.0021963 -0.5742998 0.8438981
Sharpe_port
## [1] 1.016128
El índice de Sharpe compara el rendimiento excedente sobre la tasa libre de riesgo con la volatilidad asumida. Un Sharpe más alto indica que el inversor está siendo mejor compensado por cada unidad de riesgo asumido.
En este caso, se utilizó una tasa libre de riesgo del 4 % anual, correspondiente al bono del Tesoro estadounidense a 10 años. Al calcular el Sharpe para cada acción y para el portafolio, se observa si la combinación de activos ofrece una relación retorno/riesgo más eficiente que las inversiones individuales.
Si el índice de Sharpe del portafolio supera al de la mayoría de los activos, se concluye que la estrategia de diversificación no solo reduce la volatilidad, sino que también mejora la calidad del retorno ajustado por riesgo. En cambio, si el portafolio presenta un Sharpe inferior, ello sugeriría que la selección de pesos o de activos podría ser revisada para alcanzar una asignación más eficiente.
A partir de la simulación MGB realizada en la parte 1, se estiman los precios esperados de cada activo y del portafolio al cierre de cada trimestre durante los dos años simulados.
# Un trimestre ≈ 252 / 4 ≈ 63 días
dias_trimestre <- round(dias_por_anio / 4)
# Tabla de trimestres desde t=0 hasta el final de los 2 años (8 trimestres)
trimestres <- tibble(
trimestre = 0:(anios * 4),
dia = pmin(trimestre * dias_trimestre, N)
) %>%
mutate(indice = dia + 1) # +1 porque en S_sim y V el índice de tiempo arranca en 1
trimestres
## # A tibble: 9 × 3
## trimestre dia indice
## <int> <dbl> <dbl>
## 1 0 0 1
## 2 1 63 64
## 3 2 126 127
## 4 3 189 190
## 5 4 252 253
## 6 5 315 316
## 7 6 378 379
## 8 7 441 442
## 9 8 504 505
n_act <- length(tickers)
# Precios esperados por activo (media de las simulaciones)
precios_esp_activos <- map_dfc(1:n_act, function(j) {
# extraer precios simulados en los índices de los trimestres
S_trimestral <- S_sim[trimestres$indice, , j]
S_trimestral <- matrix(S_trimestral,
nrow = nrow(trimestres),
ncol = n_sim)
apply(S_trimestral, 1, mean)
})
colnames(precios_esp_activos) <- paste0("E_", tickers)
# Precio esperado del portafolio (media de V en los trimestres)
V_trimestral <- V[trimestres$indice, ]
V_trimestral <- matrix(V_trimestral,
nrow = nrow(trimestres),
ncol = n_sim)
precio_port_esp <- apply(V_trimestral, 1, mean)
tabla_trimestres <- bind_cols(
trimestres %>% select(trimestre, dia),
precios_esp_activos,
tibble(E_Portafolio = precio_port_esp)
)
tabla_trimestres
## # A tibble: 9 × 6
## trimestre dia E_MSFT E_NKE E_NEE E_Portafolio
## <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0 0 514. 61.2 81.7 323.
## 2 1 63 515. 61.2 81.7 323.
## 3 2 126 515. 61.2 81.8 323.
## 4 3 189 515. 61.2 81.8 323.
## 5 4 252 515. 61.2 81.8 323.
## 6 5 315 515. 61.1 81.8 323.
## 7 6 378 515. 61.1 81.8 323.
## 8 7 441 515. 61.1 81.8 323.
## 9 8 504 515. 61.0 81.8 323.
El análisis de precios esperados por trimestre, derivado de la simulación MGB, permite trasladar la información diaria a un horizonte más relevante para la toma de decisiones financieras y la estructura de pagos trimestrales de las opciones que se utilizarán en la cobertura.
La serie de valores esperados del portafolio por trimestre muestra la trayectoria promedio anticipada de la inversión. Si la serie es claramente ascendente, puede interpretarse como una valorización esperada del portafolio, coherente con los retornos positivos estimados. Por el contrario, una trayectoria plana o con ligera tendencia a la baja reflejaría un entorno de menor crecimiento esperado o incluso de corrección en el mercado.
Este análisis resulta clave porque conecta la dinámica simulada del subyacente con el diseño temporal de los instrumentos de cobertura, los cuales suelen liquidarse de forma trimestral.
Para evaluar el riesgo de pérdidas extremas, se calcula el Valor en Riesgo (VaR) del portafolio:
# Serie de retornos diarios del portafolio en formato xts
R_port_xts <- xts::xts(R_port_hist,
order.by = ret_diarios$date)
colnames(R_port_xts) <- "Portafolio"
# VaR histórico al 1 % (nivel de confianza 99 %) y 5 % (95 %)
VaR_1_hist <- VaR(R_port_xts,
p = 0.99,
method = "historical") # cola izquierda 1 %
VaR_5_hist <- VaR(R_port_xts,
p = 0.95,
method = "historical") # cola izquierda 5 %
VaR_1_hist
## Portafolio
## VaR -0.03155151
VaR_5_hist
## Portafolio
## VaR -0.01753037
El VaR histórico al 1 % indica la pérdida máxima diaria que no se espera superar más que en el 1 % de los peores días observados en la muestra. De forma análoga, el VaR al 5 % representa la pérdida máxima diaria con un nivel de confianza del 95 %.
# Primer trimestre: del tiempo inicial (trimestre 0) al final del trimestre 1
ind_inicio <- trimestres$indice[1]
ind_fin <- trimestres$indice[2]
# Retorno del portafolio en el primer trimestre para cada simulación
ret_q1_sim <- V[ind_fin, ] / V[ind_inicio, ] - 1 # vector de longitud n_sim
# VaR al 1 % y 5 % a un trimestre (cola izquierda)
VaR_1_sim <- -quantile(ret_q1_sim, 0.01)
VaR_5_sim <- -quantile(ret_q1_sim, 0.05)
VaR_1_sim
## 1%
## 0.01518787
VaR_5_sim
## 5%
## 0.01004976
El Valor en Riesgo (VaR) proporciona una medida cuantitativa de las pérdidas extremas que podrían ocurrir con baja probabilidad. El VaR histórico diario al 1 % y al 5 % refleja las pérdidas máximas observadas en la muestra bajo niveles de confianza del 99 % y 95 %, respectivamente. En otras palabras, solo en el 1 % o 5 % de los peores días se esperarían pérdidas superiores a esos valores.
Por otro lado, el VaR simulado a un trimestre se obtiene a partir de las trayectorias generadas mediante el modelo MGB para el portafolio. Este enfoque incorpora no solo la información histórica, sino también los supuestos del modelo sobre el comportamiento futuro de los precios. El VaR trimestral al 1 % y al 5 % indica la pérdida máxima esperada en ese horizonte, con los respectivos niveles de confianza.
La comparación entre el VaR histórico diario y el VaR trimestral simulado permite tener una visión más completa del riesgo: el primero está anclado en la experiencia pasada, mientras que el segundo incorpora escenarios futuros generados estocásticamente. Si ambos valores son consistentes (es decir, ninguno resulta desproporcionadamente más alto o más bajo), se refuerza la credibilidad del modelo. En caso contrario, podría ser necesario revisar los supuestos de volatilidad, correlaciones o el horizonte temporal considerado.
En esta sección se valúan opciones europeas y americanas de tipo call y put para cada uno de los tres activos (MSFT, NKE, NEE), y se diseña una estrategia de cobertura que protege el 85 % de la inversión total, apalancada a la tasa libre de riesgo (4 %).
Se tendrá en cuenta la tasa de dividendo de cada empresa, siguiendo las instrucciones del enunciado, modelada como una tasa de dividendo continua (q).
Supondremos un horizonte de 2 años para las opciones (coherente con el horizonte de simulación) y strikes at-the-money (cercanos al precio spot actual). Las tasas de dividendo son aproximadas y pueden ajustarse según la información de mercado.
# Horizonte de la opción (en años)
T_opt <- 2
# Strikes at-the-money: iguales al precio spot actual
K_vec <- S0
K_vec
## MSFT NKE NEE
## 514.33 61.23 81.69
# Volatilidades anuales de cada activo (desde el bloque de Sharpe)
sigma_anual_activos
## ret_MSFT ret_NKE ret_NEE
## 0.2208382 0.3721248 0.2807909
sigma_vec <- sigma_anual_activos # renombrar para claridad
# Tasas de dividendo anuales aproximadas (continuas)
q_vec <- c(MSFT = 0.008, # ~0.8 % anual
NKE = 0.009, # ~0.9 % anual
NEE = 0.025) # ~2.5 % anual
q_vec
## MSFT NKE NEE
## 0.008 0.009 0.025
El uso de tasas de dividendo continuas (q) permite ajustar el modelo de Black–Scholes–Merton al hecho de que algunas acciones reparten dividendos periódicamente. Un mayor dividendo tiende a reducir el precio teórico de las opciones call (ya que parte del valor futuro se distribuye en efectivo) y a incrementar el valor relativo de las opciones put.
A continuación se implementa el modelo de Black–Scholes–Merton con dividendos continuos para valuar opciones europeas call y put.
# Función de Black-Scholes-Merton con dividendos continuos
bs_european_div <- function(type = c("call", "put"),
S0, K, r, q, sigma, T) {
type <- match.arg(type)
d1 <- (log(S0 / K) + (r - q + 0.5 * sigma^2) * T) / (sigma * sqrt(T))
d2 <- d1 - sigma * sqrt(T)
if (type == "call") {
price <- S0 * exp(-q * T) * pnorm(d1) - K * exp(-r * T) * pnorm(d2)
} else {
price <- K * exp(-r * T) * pnorm(-d2) - S0 * exp(-q * T) * pnorm(-d1)
}
return(price)
}
# Calcular precios europeos call y put para cada activo
res_eur <- tibble(
Activo = tickers,
S0 = as.numeric(S0),
K = as.numeric(K_vec),
sigma = as.numeric(sigma_vec),
q = as.numeric(q_vec[tickers])
) %>%
rowwise() %>%
mutate(
Call_Eur = bs_european_div("call", S0, K, rf_anual, q, sigma, T_opt),
Put_Eur = bs_european_div("put", S0, K, rf_anual, q, sigma, T_opt)
) %>%
ungroup()
res_eur
## # A tibble: 3 × 7
## Activo S0 K sigma q Call_Eur Put_Eur
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 MSFT 514. 514. 0.221 0.008 77.8 46.4
## 2 NKE 61.2 61.2 0.372 0.009 14.0 10.4
## 3 NEE 81.7 81.7 0.281 0.025 13.2 10.9
Las opciones call europeas valoradas con este modelo representan el derecho, mas no la obligación, de comprar la acción a un precio de ejercicio K al vencimiento de 2 años. Las opciones put otorgan el derecho de vender la acción al mismo strike.
En general, para un mismo activo:
Estos valores europeos servirán como referencia para comparar con las opciones americanas, que permiten ejercicio anticipado.
Para las opciones americanas se utiliza un modelo binomial recombinante con 8 pasos (un paso por trimestre durante 2 años). El ejercicio puede ocurrir en cualquier nodo del árbol, lo que las hace más valiosas (o al menos no menos valiosas) que las opciones europeas equivalentes.
# Función de valuación binomial para opciones americanas con dividendos
binom_american <- function(type = c("call", "put"),
S0, K, r, q, sigma, T, steps) {
type <- match.arg(type)
dt <- T / steps
u <- exp(sigma * sqrt(dt))
d <- 1 / u
# Probabilidad neutral al riesgo ajustada por dividendos
p <- (exp((r - q) * dt) - d) / (u - d)
# Precios del subyacente al vencimiento
ST <- S0 * d^(0:steps) * u^(steps:0)
# Payoff al vencimiento
if (type == "call") {
V <- pmax(ST - K, 0)
} else {
V <- pmax(K - ST, 0)
}
# Descuento hacia atrás
disc <- exp(-r * dt)
for (j in steps:1) {
V <- disc * (p * V[2:(j + 1)] + (1 - p) * V[1:j])
# Valores intrínsecos en el nodo (posible ejercicio anticipado)
S_j <- S0 * d^(0:(j - 1)) * u^((j - 1):0)
if (type == "call") {
V_intr <- pmax(S_j - K, 0)
} else {
V_intr <- pmax(K - S_j, 0)
}
V <- pmax(V, V_intr)
}
return(V[1])
}
steps_binom <- 8 # 8 trimestres en 2 años
res_amer <- res_eur %>%
rowwise() %>%
mutate(
Call_Amer = binom_american("call", S0, K, rf_anual, q, sigma, T_opt, steps_binom),
Put_Amer = binom_american("put", S0, K, rf_anual, q, sigma, T_opt, steps_binom)
) %>%
ungroup()
res_amer
## # A tibble: 3 × 9
## Activo S0 K sigma q Call_Eur Put_Eur Call_Amer Put_Amer
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 MSFT 514. 514. 0.221 0.008 77.8 46.4 66.5 53.7
## 2 NKE 61.2 61.2 0.372 0.009 14.0 10.4 20.4 8.50
## 3 NEE 81.7 81.7 0.281 0.025 13.2 10.9 18.0 9.07
res_opc <- res_amer %>%
select(Activo, S0, K, sigma, q, Call_Eur, Put_Eur, Call_Amer, Put_Amer)
res_opc
## # A tibble: 3 × 9
## Activo S0 K sigma q Call_Eur Put_Eur Call_Amer Put_Amer
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 MSFT 514. 514. 0.221 0.008 77.8 46.4 66.5 53.7
## 2 NKE 61.2 61.2 0.372 0.009 14.0 10.4 20.4 8.50
## 3 NEE 81.7 81.7 0.281 0.025 13.2 10.9 18.0 9.07
En teoría, para un mismo activo y parámetros:
En el contexto de cobertura, las opciones americanas put son especialmente útiles, ya que permiten ejercer anticipadamente si el precio de la acción cae por debajo del strike antes del vencimiento, protegiendo de forma más flexible la inversión.
Se desea cubrir el 85 % de la inversión total de 10 millones de dólares utilizando opciones. Una estrategia clásica de cobertura de caída de precios es la compra de puts de protección (protective puts): el inversionista mantiene la posición larga en la acción y compra opciones put que fijan un precio mínimo de venta.
Primero, se calcula cuántas acciones de cada activo se desea cubrir (85 % de la cantidad total de acciones).
porc_cobertura <- 0.85
acciones_cubiertas <- cantidades * porc_cobertura
acciones_cubiertas
## MSFT NKE NEE
## 9284.286 13718.677 35314.219
Supondremos que la cobertura se realiza con puts americanas (Put_Amer), ya que ofrecen mayor flexibilidad que las europeas.
# Juntamos la información relevante en una tabla de cobertura
tabla_cobertura <- tibble(
Activo = tickers,
Peso_Portafolio = as.numeric(w_opt),
S0 = as.numeric(S0),
Cant_Acciones = as.numeric(cantidades),
Acciones_Cubiertas= as.numeric(acciones_cubiertas),
Put_Amer = res_opc$Put_Amer
) %>%
mutate(
Costo_Puts = Acciones_Cubiertas * Put_Amer,
Notional_Cubierto = Acciones_Cubiertas * S0
)
tabla_cobertura
## # A tibble: 3 × 8
## Activo Peso_Portafolio S0 Cant_Acciones Acciones_Cubiertas Put_Amer
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 MSFT 0.562 514. 10923. 9284. 53.7
## 2 NKE 0.0988 61.2 16140. 13719. 8.50
## 3 NEE 0.339 81.7 41546. 35314. 9.07
## # ℹ 2 more variables: Costo_Puts <dbl>, Notional_Cubierto <dbl>
El costo total de la estrategia de cobertura con puts es:
costo_total_cobertura <- sum(tabla_cobertura$Costo_Puts)
costo_total_cobertura
## [1] 935188.4
Este costo se financia mediante apalancamiento a la tasa libre de riesgo (4 % anual). Si se considera un horizonte de 2 años, el valor futuro aproximado del costo de cobertura sería:
costo_futuro_cobertura <- costo_total_cobertura * (1 + rf_anual)^T_opt
costo_futuro_cobertura
## [1] 1011500
La estrategia de cobertura planteada consiste en:
Desde el punto de vista financiero:
Protección a la baja:
Si el precio de alguna acción cae por debajo del strike K
(aproximadamente el precio actual), la opción put entra in the money y
la pérdida en el valor de la acción se ve compensada por la ganancia en
la opción. Esto limita la pérdida máxima sobre el 85 % de la posición
cubierta.
Participación al alza:
Si el precio de la acción sube por encima del strike, las puts expiran
out of the money y se pierde la prima pagada, pero el inversionista se
beneficia de la apreciación del subyacente. Así, la estrategia mantiene
exposición a ganancias, a costa de un costo de seguro.
Costo de la cobertura y apalancamiento:
El costo total de las primas de las puts representa el “seguro” por la
protección. Al financiar dicho costo con deuda al 4 % anual, se
incrementa el apalancamiento del portafolio. Este apalancamiento genera
un compromiso de pago futuro (costo_futuro_cobertura), que debe ser
comparado con el beneficio de la reducción de riesgo: si el mercado
efectivamente presenta caídas fuertes, la cobertura puede ahorrar
pérdidas mucho mayores que el costo financiado de las opciones.
Reparto del dinero apalancado entre
activos:
El reparto de la cobertura se hace proporcional a las cantidades
de acciones y a los pesos del portafolio, es decir, cada activo
recibe una parte del “presupuesto de cobertura” acorde a su importancia
en la inversión total. De esta manera, la protección es coherente con la
estructura del portafolio: los activos más representativos en términos
de valor reciben una proporción mayor de opciones de cobertura.
En resumen, la estrategia de puts americanas apalancadas al 4 % anual permite transformar el perfil de riesgo del portafolio: se limita la pérdida potencial en escenarios adversos a cambio de pagar (y financiar) una prima de seguro. Esta lógica es consistente con la gestión moderna de riesgo: sacrificar una fracción del retorno esperado para reducir la probabilidad de pérdidas extremas sobre el capital invertido.
Aunque el modelo binomial se utilizó previamente para valuar las opciones americanas, es importante visualizar explícitamente la estructura del árbol binomial, ya que esta representa de forma gráfica la evolución posible del precio del subyacente y el proceso de valuación hacia atrás.
A continuación se construye el árbol binomial recombinante de 8 pasos trimestrales para la acción MSFT, usando los mismos parámetros de volatilidad, tasa libre de riesgo y dividendo empleados en la valuación de opciones.
library(dplyr)
library(tidyr)
# Asegurar nombres correctos de las volatilidades
names(sigma_anual_activos) <- tickers
# Parámetros para MSFT
steps_binom <- 8
T_opt <- 2
sigma_msft <- as.numeric(sigma_anual_activos["MSFT"])
S0_msft <- as.numeric(S0["MSFT"])
dt_bin <- T_opt / steps_binom
u_msft <- exp(sigma_msft * sqrt(dt_bin))
d_msft <- 1 / u_msft
# Árbol completo de nodos (sin bucles)
tree_df <- expand_grid(
step = 0:steps_binom,
i = 0:steps_binom
) %>%
filter(i <= step) %>% # sólo nodos válidos
mutate(
S = S0_msft * u_msft^(step - i) * d_msft^i, # precio en cada nodo
y = step - 2 * i # posición vertical
)
head(tree_df)
## # A tibble: 6 × 4
## step i S y
## <int> <int> <dbl> <dbl>
## 1 0 0 514. 0
## 2 1 0 574. 1
## 3 1 1 461. -1
## 4 2 0 641. 2
## 5 2 1 514. 0
## 6 2 2 412. -2
Cada nodo del árbol representa un precio posible de MSFT en un trimestre específico, partiendo del precio actual \(S_0\). En cada paso, el precio puede subir multiplicándose por \(u\) o bajar multiplicándose por \(d\). Las probabilidades neutras al riesgo \(p\) y \(1-p\) se utilizan para valuar las opciones bajo la medida neutral al riesgo, consistente con la tasa libre de riesgo y los dividendos.
Sobre esta misma estructura de precios se calculan los valores de la opción americana (call o put) usando un procedimiento de inducción hacia atrás:
Desde el punto de vista de cobertura, el árbol binomial permite visualizar cómo, a medida que el precio de MSFT evoluciona en el tiempo, la opción put de protección gana o pierde valor, compensando parcialmente las caídas del subyacente. Esto hace evidente la lógica de la cobertura dinámica: en escenarios de fuertes caídas (rama descendente del árbol), la put entra en el dinero y protege el valor de la inversión; en escenarios alcistas, la put expira sin valor, pero el inversionista se beneficia de la apreciación de la acción.
A lo largo del laboratorio se desarrollaron tres etapas principales:
Construcción del portafolio y simulación
MGB:
Se seleccionaron tres acciones (MSFT, NKE y NEE) y se construyó un
portafolio de mínima varianza para una inversión de 10 millones de
dólares. Con base en retornos históricos desde el 01/10/2023 se
estimaron los parámetros de media y covarianza, y se simuló la evolución
del portafolio bajo un Movimiento Browniano Geométrico durante dos
años.
Análisis de riesgo y desempeño:
Se evaluó la volatilidad diaria y anual, el índice de Sharpe, los
precios esperados por trimestre y el Valor en Riesgo (VaR) tanto
histórico como simulado. Estos resultados permitieron caracterizar el
perfil de riesgo-retorno del portafolio, identificando el impacto de la
diversificación y cuantificando las pérdidas extremas esperadas en
distintos horizontes.
Valuación de opciones y cobertura del 85
%:
Se valúan opciones europeas y americanas call y put para cada activo,
incorporando la tasa libre de riesgo del 4 % y tasas de dividendo
continuas. Con las opciones put americanas se diseña una estrategia de
cobertura que protege el 85 % de la inversión, financiando el costo de
las primas mediante apalancamiento a la tasa del bono del Tesoro. El
análisis muestra cómo esta estrategia acota la pérdida máxima del
portafolio ante choques negativos, manteniendo al mismo tiempo la
posibilidad de participar en las ganancias si el mercado se
recupera.
Desde el punto de vista financiero, el laboratorio integra los componentes clave de la gestión de portafolios y derivados: selección eficiente de activos, modelación estocástica de precios, medición de riesgo y diseño de coberturas utilizando opciones. Esto proporciona una visión completa de cómo un ingeniero financiero puede combinar herramientas cuantitativas y criterios de mercado para tomar decisiones de inversión más robustas frente a la incertidumbre.