# =================== PARÁMETROS DEL EJERCICIO ===================
capital <- 35e6 # USD
f_inicio <- as.Date("2026-10-30") # fecha inicial de inversión
f_hist <- as.Date("2016-10-30") # 10 años de historia
horizonte <- 4 # años
tickers <- c("AAPL", "AMGN", "PEP")
indice <- "^NDX" # Nasdaq-100
criterio <- "max_sharpe" # "max_sharpe" (tangente) o "min_var"
rebalanceo <- TRUE # recalcular N en cada roll-over
q_ndx <- 0.007 # rendimiento por dividendo del NDX (supuesto)
nsim <- 10000 # simulaciones Monte Carlo
deriva <- "historica" # "historica" o "neutral" (mu = r - q, sin alfa)
retiro_exceso <- TRUE # retirar el saldo de margen por encima del inicial
set.seed(2026)
# Contrato de futuros: E-mini Nasdaq-100 (NQ) — CME Group
# IMPORTANTE: verificar márgenes vigentes al 30-oct-2026 en
# https://www.cmegroup.com/markets/equities/nasdaq/e-mini-nasdaq-100.margins.html
fut <- list(
nombre = "E-mini Nasdaq-100 (NQ) — CME Group (Globex)",
subyacente = "Índice Nasdaq-100 (NDX)",
vencimientos = "Trimestrales: marzo (H), junio (M), septiembre (U), diciembre (Z)",
multiplicador = 20, # USD por punto del índice
tick = 0.25, # puntos
valor_tick = 5, # USD por tick (0.25 x 20)
margen_inicial = 33000, # USD por contrato <-- ACTUALIZAR CON CME
margen_mant = 30000, # USD por contrato <-- ACTUALIZAR CON CME
liquidacion = "Financiera (cash settlement) contra la cotización especial de apertura (SOQ) del NDX el tercer viernes del mes de vencimiento",
mtm = "Diaria (mark-to-market)"
)
mult <- fut$multiplicador; IM <- fut$margen_inicial; MM <- fut$margen_mantdescargar <- function(t, desde, hasta, tipo = "Ad") {
x <- getSymbols(t, src = "yahoo", from = desde, to = hasta, auto.assign = FALSE)
if (tipo == "Ad") Ad(x) else Cl(x)
}
# Precios ajustados (incluyen dividendos y splits) -> retorno total
precios <- na.omit(do.call(merge, lapply(c(tickers, indice), descargar,
desde = f_hist, hasta = f_inicio + 1)))
colnames(precios) <- c(tickers, "NDX")
# Precios de cierre (solo splits) -> evolución del precio de mercado
cierres <- na.omit(do.call(merge, lapply(tickers, descargar, desde = f_hist,
hasta = f_inicio + 1, tipo = "Cl")))
colnames(cierres) <- tickers
precios_m <- precios[endpoints(precios, on = "months"), ]
ret_m <- na.omit(diff(log(precios_m))) # retornos logarítmicos mensuales
# Tasas libres de riesgo observadas en la fecha inicial
tasa <- function(t) {
x <- tryCatch(getSymbols(t, src = "yahoo", from = f_inicio - 15, to = f_inicio + 1,
auto.assign = FALSE), error = function(e) NULL)
if (is.null(x)) return(NA)
v <- as.numeric(last(na.omit(Cl(x))))
if (v > 20) v <- v / 10
v / 100
}
rf_tnx <- tasa("^TNX"); rf_fvx <- tasa("^FVX"); rf_irx <- tasa("^IRX")
rf <- rf_tnx # tasa usada (anual)
rf_c <- ifelse(is.na(rf_irx), rf_tnx, rf_irx) # tasa corta: carry, financiamiento y descuento
P0 <- as.numeric(last(precios)); names(P0) <- colnames(precios)
S0 <- unname(P0["NDX"])Se invierten $35,000,000 el 30 de octubre de 2026 en un portafolio de tres acciones del Nasdaq-100 Apple (AAPL), Amgen (AMGN) y PepsiCo (PEP) con horizonte de 4 años. Se usan 120 meses de historia (oct-2016 a oct-2026) para optimizar el portafolio, medir su riesgo y diseñar una cobertura con futuros E-mini Nasdaq-100 (NQ) con roll-over trimestral.
ficha <- data.frame(
Característica = c("Contrato", "Activo subyacente", "Vencimientos", "Multiplicador",
"Tick mínimo", "Valor del tick", "Margen inicial (por contrato)",
"Margen de mantenimiento (por contrato)", "Liquidación", "Mark-to-market"),
Detalle = c(fut$nombre, fut$subyacente, fut$vencimientos, paste0(usd(mult), " × índice"),
paste(fut$tick, "puntos"), usd(fut$valor_tick), usd(IM), usd(MM),
fut$liquidacion, fut$mtm))
tabla(ficha, "Ficha técnica del futuro E-mini Nasdaq-100 (CME Group)")| Característica | Detalle |
|---|---|
| Contrato | E-mini Nasdaq-100 (NQ) — CME Group (Globex) |
| Activo subyacente | Índice Nasdaq-100 (NDX) |
| Vencimientos | Trimestrales: marzo (H), junio (M), septiembre (U), diciembre (Z) |
| Multiplicador | $20 × índice |
| Tick mínimo | 0.25 puntos |
| Valor del tick | $5 |
| Margen inicial (por contrato) | $33,000 |
| Margen de mantenimiento (por contrato) | $30,000 |
| Liquidación | Financiera (cash settlement) contra la cotización especial de apertura (SOQ) del NDX el tercer viernes del mes de vencimiento |
| Mark-to-market | Diaria (mark-to-market) |
met <- function(tk) {
x <- as.numeric(cierres[, tk]); d <- index(cierres); p0 <- tail(x, 1)
r <- function(a) (p0 / tail(x[d <= f_inicio - round(365.25 * a)], 1))^(1 / a) - 1
ult <- x[d > f_inicio - 365]
c(`Precio 30-oct-2026` = usd(p0, 2), `Rend. 1 año` = pct(r(1)),
`Rend. anual 3 años` = pct(r(3)), `Rend. anual 5 años` = pct(r(5)),
`Máx. 52 semanas` = usd(max(ult), 2), `Mín. 52 semanas` = usd(min(ult), 2),
`Máx. caída (10 años)` = pct(max(1 - x / cummax(x))))
}
tabla(t(sapply(tickers, met)), "Evolución reciente del precio de mercado")| Precio 30-oct-2026 | Rend. 1 año | Rend. anual 3 años | Rend. anual 5 años | Máx. 52 semanas | Mín. 52 semanas | Máx. caída (10 años) | |
|---|---|---|---|---|---|---|---|
| AAPL | $344.63 | 26.98% | 26.49% | 18.13% | $348.91 | $246.63 | 38.73% |
| AMGN | $466.01 | 59.59% | 20.98% | 17.62% | $466.01 | $296.30 | 26.72% |
| PEP | $137.34 | -6.92% | -5.41% | -3.20% | $170.49 | $123.73 | 36.91% |
base <- merge(cierres, precios$NDX)
base100 <- sweep(coredata(base), 2, as.numeric(coredata(base)[1, ]), "/") * 100
fig1 <- plot_ly()
for (j in colnames(base))
fig1 <- add_lines(fig1, x = index(base), y = base100[, j], name = j)
layout(fig1, title = "Precio base 100 (oct-2016 = 100)",
yaxis = list(title = "Índice base 100", type = "log"), xaxis = list(title = ""),
hovermode = "x unified")# Completar con el último 10-Q / 10-K y el consenso de analistas (Investing.com, Yahoo Finance)
fund <- data.frame(
Indicador = c("Ingresos últimos 12 meses (USD mil M)", "Margen neto", "Deuda / Patrimonio",
"P/E (TTM)", "Precio objetivo consenso 12 m (USD)"),
AAPL = NA, AMGN = NA, PEP = NA, check.names = FALSE)
fund[is.na(fund)] <- "Por completar"
tabla(fund, "Situación financiera (fuente: último reporte trimestral y consenso de analistas)")| Indicador | AAPL | AMGN | PEP |
|---|---|---|---|
| Ingresos últimos 12 meses (USD mil M) | Por completar | Por completar | Por completar |
| Margen neto | Por completar | Por completar | Por completar |
| Deuda / Patrimonio | Por completar | Por completar | Por completar |
| P/E (TTM) | Por completar | Por completar | Por completar |
| Precio objetivo consenso 12 m (USD) | Por completar | Por completar | Por completar |
# Expectativa de precio a 1 año según el modelo (log-normal con parámetros históricos)
mu_a <- colMeans(ret_m[, tickers]) * 12
sd_a <- apply(ret_m[, tickers], 2, sd) * sqrt(12)
P0c <- as.numeric(last(cierres)); names(P0c) <- tickers
esper <- data.frame(
Acción = tickers, `Precio actual` = usd(P0c, 2),
`Precio esperado 1 año` = usd(P0c * exp(mu_a + sd_a^2 / 2), 2),
`Intervalo 95% inferior` = usd(P0c * exp(mu_a - 1.96 * sd_a), 2),
`Intervalo 95% superior` = usd(P0c * exp(mu_a + 1.96 * sd_a), 2), check.names = FALSE)
tabla(esper, "Expectativa de precio a 12 meses según el modelo histórico")| Acción | Precio actual | Precio esperado 1 año | Intervalo 95% inferior | Intervalo 95% superior | |
|---|---|---|---|---|---|
| AAPL | AAPL | $344.63 | $462.48 | $264.61 | $752.81 |
| AMGN | AMGN | $466.01 | $555.88 | $342.82 | $853.81 |
| PEP | PEP | $137.34 | $147.20 | $105.16 | $200.52 |
Hay que tomar estos precios con cuidado. El modelo proyecta lo que pasó en los últimos diez años, que fueron muy buenos para estas acciones. Para AAPL espera USD 462 en un año, 34% por encima del precio de hoy, con un intervalo que va de 265 a 753. Si el precio objetivo de los analistas queda muy por debajo, es probable que el retorno histórico esté inflando la expectativa.
Lógica de la selección: La idea inicial era juntar una acción de crecimiento con beta alta (AAPL) y dos defensivas de sectores distintos, para ganar por diversificación. AMGN entró con 30.5%, por su baja correlación con Apple. PEP se quedó por fuera porque lo que aporta en diversificación no compensa su bajo retorno.
Frecuencia: mensual (último precio ajustado de cada mes), 120 observaciones. Se eligió mensual porque el VaR y la liquidación de futuros del ejercicio son mensuales. Retorno: logarítmico, \(r_t=\ln(P_t/P_{t-1})\). Anualización: \(\mu_{anual}=12\,\bar r_{mensual}\), \(\sigma_{anual}=\sqrt{12}\,\sigma_{mensual}\), \(\Sigma_{anual}=12\,\Sigma_{mensual}\) (supone retornos independientes entre meses, por eso la varianza escala con el tiempo y la desviación con su raíz).
est <- data.frame(
`Retorno mensual promedio` = pct(colMeans(ret_m[, tickers])),
`Retorno anual promedio` = pct(mu_a),
`Desv. estándar mensual` = pct(apply(ret_m[, tickers], 2, sd)),
`Desv. estándar anual` = pct(sd_a), check.names = FALSE)
tabla(est, "Estadísticas por acción")| Retorno mensual promedio | Retorno anual promedio | Desv. estándar mensual | Desv. estándar anual | |
|---|---|---|---|---|
| AAPL | 2.15% | 25.86% | 7.70% | 26.67% |
| AMGN | 1.24% | 14.93% | 6.72% | 23.28% |
| PEP | 0.46% | 5.58% | 4.75% | 16.46% |
S_a <- cov(ret_m[, tickers]) * 12
tabla(round(S_a, 5), "Matriz de varianzas y covarianzas anualizada")| AAPL | AMGN | PEP | |
|---|---|---|---|
| AAPL | 0.07114 | 0.01311 | 0.01288 |
| AMGN | 0.01311 | 0.05419 | 0.01574 |
| PEP | 0.01288 | 0.01574 | 0.02711 |
| AAPL | AMGN | PEP | |
|---|---|---|---|
| AAPL | 1.0000 | 0.2111 | 0.2934 |
| AMGN | 0.2111 | 1.0000 | 0.4107 |
| PEP | 0.2934 | 0.4107 | 1.0000 |
plot_ly(x = tickers, y = tickers, z = C, type = "heatmap", colorscale = "RdBu",
zmin = -1, zmax = 1, text = round(C, 2), texttemplate = "%{text}") |>
layout(title = "Mapa de calor de correlaciones")AAPL tiene el retorno promedio más alto (25.9% anual), pero también es la más volátil (26.7%). PEP está en el otro extremo: 5.6% de retorno y 16.5% de volatilidad. Ninguna correlación pasa de 0.41, y la más baja es la de AAPL con AMGN (0.21). Por eso esas dos terminan juntas en el portafolio óptimo.
Se evalúan todas las combinaciones de pesos sin ventas en corto (\(w_i\ge 0\), \(\sum w_i=1\)) en una malla de 0.5%:
\[\mu_p=\mathbf w'\boldsymbol\mu,\qquad \sigma_p=\sqrt{\mathbf w'\Sigma\mathbf w},\qquad Sharpe=\frac{\mu_p-r_f}{\sigma_p}\]
paso <- 0.005
g <- expand.grid(a = seq(0, 1, paso), b = seq(0, 1, paso))
g <- g[g$a + g$b <= 1 + 1e-9, ]
W <- cbind(g$a, g$b, pmax(0, round(1 - g$a - g$b, 10))); colnames(W) <- tickers # evita pesos -1e-16
mu_g <- as.numeric(W %*% mu_a)
sd_g <- sqrt(rowSums((W %*% S_a) * W))
sh_g <- (mu_g - rf) / sd_g
i_tan <- which.max(sh_g); i_mvp <- which.min(sd_g)
w <- if (criterio == "max_sharpe") W[i_tan, ] else W[i_mvp, ]
mu_p <- sum(w * mu_a); sd_p <- sqrt(as.numeric(t(w) %*% S_a %*% w))
sharpe_p <- (mu_p - rf) / sd_p
comp <- data.frame(
Portafolio = c("Máximo Sharpe (tangente)", "Mínima varianza"),
AAPL = pct(c(W[i_tan, 1], W[i_mvp, 1])), AMGN = pct(c(W[i_tan, 2], W[i_mvp, 2])),
PEP = pct(c(W[i_tan, 3], W[i_mvp, 3])),
`Retorno esperado` = pct(c(mu_g[i_tan], mu_g[i_mvp])),
`Desv. estándar` = pct(c(sd_g[i_tan], sd_g[i_mvp])),
Sharpe = num(c(sh_g[i_tan], sh_g[i_mvp]), 3), check.names = FALSE)
tabla(comp, paste("Portafolios candidatos — rf =", pct(rf)))| Portafolio | AAPL | AMGN | PEP | Retorno esperado | Desv. estándar | Sharpe |
|---|---|---|---|---|---|---|
| Máximo Sharpe (tangente) | 69.50% | 30.50% | 0.00% | 22.52% | 21.20% | 0.830 |
| Mínima varianza | 16.50% | 19.00% | 64.50% | 10.70% | 15.03% | 0.384 |
asig <- data.frame(Acción = tickers, Peso = pct(w), `Monto invertido` = usd(capital * w),
`Precio de compra` = usd(P0[tickers], 2),
`N° de acciones` = num(floor(capital * w / P0[tickers]), 0), check.names = FALSE)
tabla(asig, "Portafolio seleccionado y distribución de los USD 35 millones")| Acción | Peso | Monto invertido | Precio de compra | N° de acciones | |
|---|---|---|---|---|---|
| AAPL | AAPL | 69.50% | $24,325,000 | $344.63 | 70,582 |
| AMGN | AMGN | 30.50% | $10,675,000 | $466.01 | 22,907 |
| PEP | PEP | 0.00% | $0 | $137.34 | 0 |
Portafolio elegido (máximo Sharpe): retorno esperado 22.52% anual, desviación estándar 21.20% y Sharpe 0.830.
set.seed(1); idx <- sample(length(mu_g), min(5000, length(mu_g)))
x_cml <- c(0, sd_g[i_tan] * 1.4)
plot_ly() |>
add_markers(x = sd_g[idx], y = mu_g[idx], name = "Combinaciones",
marker = list(color = sh_g[idx], colorscale = "Viridis", size = 4,
colorbar = list(title = "Sharpe")),
text = sprintf("AAPL %.1f%%<br>AMGN %.1f%%<br>PEP %.1f%%",
100 * W[idx, 1], 100 * W[idx, 2], 100 * W[idx, 3]),
hoverinfo = "text") |>
add_lines(x = x_cml, y = rf + sh_g[i_tan] * x_cml, name = "Línea del mercado de capitales",
line = list(dash = "dash", color = "gray")) |>
add_markers(x = sqrt(diag(S_a)), y = mu_a, text = tickers, name = "Acciones",
marker = list(size = 12, color = "black", symbol = "diamond")) |>
add_markers(x = sd_g[i_tan], y = mu_g[i_tan], name = "Máximo Sharpe",
marker = list(size = 15, color = "red", symbol = "star")) |>
add_markers(x = sd_g[i_mvp], y = mu_g[i_mvp], name = "Mínima varianza",
marker = list(size = 15, color = "orange", symbol = "star")) |>
layout(title = "Frontera eficiente (sin ventas en corto)",
xaxis = list(title = "Desviación estándar anual", tickformat = ".0%"),
yaxis = list(title = "Retorno esperado anual", tickformat = ".0%"))r_p <- as.numeric(coredata(ret_m[, tickers]) %*% w) # retorno mensual histórico del portafolio
mu_pm <- mean(r_p); sd_pm <- sd(r_p)
z <- qnorm(c(0.05, 0.01))
var_par <- -(exp(mu_pm + z * sd_pm) - 1)
var_hist <- -(exp(quantile(r_p, c(0.05, 0.01))) - 1)
tab_var <- data.frame(
Método = rep(c("Paramétrico (normal)", "Histórico"), each = 2),
Confianza = rep(c("95% (VaR 5%)", "99% (VaR 1%)"), 2),
`VaR mensual %` = pct(c(var_par, var_hist)),
`VaR mensual USD` = usd(capital * c(var_par, var_hist)), check.names = FALSE)
tabla(tab_var, "VaR mensual del portafolio")| Método | Confianza | VaR mensual % | VaR mensual USD |
|---|---|---|---|
| Paramétrico (normal) | 95% (VaR 5%) | 7.86% | $2,752,654 |
| Paramétrico (normal) | 99% (VaR 1%) | 11.63% | $4,070,157 |
| Histórico | 95% (VaR 5%) | 8.80% | $3,078,403 |
| Histórico | 99% (VaR 1%) | 10.69% | $3,740,078 |
plot_ly(x = r_p, type = "histogram", nbinsx = 30, name = "Retornos mensuales") |>
add_segments(x = log(1 - var_par[1]), xend = log(1 - var_par[1]), y = 0, yend = 20,
name = "VaR 95%", line = list(color = "orange", width = 3)) |>
add_segments(x = log(1 - var_par[2]), xend = log(1 - var_par[2]), y = 0, yend = 20,
name = "VaR 99%", line = list(color = "red", width = 3)) |>
layout(title = "Distribución histórica de retornos mensuales del portafolio",
xaxis = list(title = "Retorno log mensual", tickformat = ".0%"), yaxis = list(title = "Frecuencia"))Interpretación: Con el VaR paramétrico, en 95 de cada 100 meses el portafolio no debería perder más de USD 2,752,653, que es el 7.86% del capital. En el 1% de los meses más malos la pérdida podría pasar de USD 4,070,157 (11.63%).
El histórico no da lo mismo. Al 95% sale más alto (USD 3,078,400) y al 99% más bajo (USD 3,740,070). Al 99% hay que desconfiar un poco: con 120 meses, ese dato depende de uno o dos meses malos.
Este es el riesgo que el gerente quiere reducir. Pero los futuros sobre el índice solo cubren lo que se mueve con el Nasdaq-100. Si Apple tiene un mal trimestre por su cuenta, por ejemplo, eso no se cubre.
\[R_{i,t}-r_{f,t}=\alpha_i+\beta_i\,(R_{NDX,t}-r_{f,t})+\varepsilon_{i,t},\qquad \beta_p=\sum_i w_i\beta_i\]
La tasa libre de riesgo histórica mensual es la letra del Tesoro a 13 semanas (^IRX).
irx <- getSymbols("^IRX", src = "yahoo", from = f_hist, to = f_inicio + 1, auto.assign = FALSE)
irx_m <- na.omit(Cl(irx))[endpoints(na.omit(Cl(irx)), on = "months")]
rf_h <- xts(log(1 + as.numeric(irx_m) / 100 / 12), order.by = as.yearmon(index(irx_m)))
rf_h <- stats::lag(rf_h, 1); colnames(rf_h) <- "rf"
ret_ym <- xts(coredata(ret_m), order.by = as.yearmon(index(ret_m)))
dc <- na.omit(merge(ret_ym, rf_h))
ex <- sweep(coredata(dc[, c(tickers, "NDX")]), 1, as.numeric(dc$rf), "-")
capm <- t(sapply(tickers, function(tk) {
m <- lm(ex[, tk] ~ ex[, "NDX"]); s <- summary(m)
c(Alfa_mensual = coef(m)[1], Beta = coef(m)[2], EE = s$coefficients[2, 2],
p_valor = s$coefficients[2, 4], R2 = s$r.squared)
}))
colnames(capm) <- c("Alfa mensual", "Beta", "Error estándar beta", "p-valor beta", "R²")
beta_i <- capm[, "Beta"]; beta_p <- sum(w * beta_i)
tabla(cbind(round(capm, 4), Peso = pct(w), `Beta × peso` = round(w * beta_i, 4)),
"Regresiones CAPM contra el Nasdaq-100")| Alfa mensual | Beta | Error estándar beta | p-valor beta | R² | Peso | Beta × peso | |
|---|---|---|---|---|---|---|---|
| AAPL | 0.0066 | 1.0144 | 0.0895 | 0 | 0.5214 | 69.50% | 0.705 |
| AMGN | 0.0073 | 0.2433 | 0.1104 | 0.0295 | 0.0395 | 30.50% | 0.0742 |
| PEP | -1e-04 | 0.2128 | 0.0776 | 0.0071 | 0.0599 | 0.00% | 0 |
fig5 <- plot_ly()
for (tk in tickers) {
fig5 <- add_markers(fig5, x = ex[, "NDX"], y = ex[, tk], name = tk, opacity = 0.6)
xs <- range(ex[, "NDX"])
fig5 <- add_lines(fig5, x = xs, y = capm[tk, 1] + beta_i[tk] * xs,
name = paste0(tk, " β=", round(beta_i[tk], 2)))
}
layout(fig5, title = "Línea característica de cada acción",
xaxis = list(title = "Exceso de retorno NDX", tickformat = ".0%"),
yaxis = list(title = "Exceso de retorno acción", tickformat = ".0%"))Beta del portafolio: \(\beta_p=\) 0.7792. Por cada 1% que se mueva el Nasdaq-100, el portafolio tiende a moverse 0.78%.
El R² también dice bastante. El índice explica el 52% de lo que se mueve AAPL, pero solo el 4% de AMGN y el 6% de PEP. En esas dos el riesgo es casi todo de la propia empresa, y ahí los futuros sobre el índice no ayudan mucho.
\[N^*=\beta_p\,\frac{V_P}{F_0\times \text{multiplicador}}\]
nq <- tryCatch(getSymbols("NQ=F", src = "yahoo", from = f_inicio - 10, to = f_inicio + 1,
auto.assign = FALSE), error = function(e) NULL)
F0_mercado <- if (!is.null(nq)) as.numeric(last(na.omit(Cl(nq)))) else NA
T0 <- as.numeric(as.Date("2026-12-18") - f_inicio) / 365 # vence NQZ26 (3er viernes)
F0_teorico <- S0 * exp((rf_c - q_ndx) * T0) # tasa corta, no la de 10 años
F0 <- ifelse(is.na(F0_mercado), F0_teorico, F0_mercado)
carry <- if (is.na(F0_mercado)) rf_c - q_ndx else log(F0_mercado / S0) / T0 # carry implícito en la base observada
N_exacto <- beta_p * capital / (F0 * mult)
cand <- c(floor(N_exacto), ceiling(N_exacto))
beta_res <- beta_p - cand * F0 * mult / capital
N <- cand[which.min(abs(beta_res))]
tabla(data.frame(
Concepto = c("Nasdaq-100 spot (30-oct-2026)", "Futuro NQZ26 de mercado (NQ=F)",
"Futuro teórico S·e^{(r−q)T}", "Precio del futuro usado F0",
"Carry implícito (r − q)", "Valor nocional de 1 contrato", "Beta del portafolio", "N* exacto"),
Valor = c(num(S0), num(F0_mercado), num(F0_teorico), num(F0), pct(carry, 3), usd(F0 * mult),
num(beta_p, 4), num(N_exacto, 3))), "Insumos del cálculo")| Concepto | Valor |
|---|---|
| Nasdaq-100 spot (30-oct-2026) | 28,384.85 |
| Futuro NQZ26 de mercado (NQ=F) | 28,534.00 |
| Futuro teórico S·e^{(r−q)T} | 28,509.84 |
| Precio del futuro usado F0 | 28,534.00 |
| Carry implícito (r − q) | 3.904% |
| Valor nocional de 1 contrato | $570,680 |
| Beta del portafolio | 0.7792 |
| N* exacto | 47.789 |
tabla(data.frame(Redondeo = c("Hacia abajo", "Hacia arriba"), Contratos = cand,
`Nocional cubierto` = usd(cand * F0 * mult),
`Beta residual` = num(beta_res, 4), check.names = FALSE),
"Comparación de redondeo")| Redondeo | Contratos | Nocional cubierto | Beta residual |
|---|---|---|---|
| Hacia abajo | 47 | $26,821,960 | 0.0129 |
| Hacia arriba | 48 | $27,392,640 | -0.0034 |
Para el futuro teórico y el carry usamos la tasa corta (^IRX, 3.973%), no la de 10 años, porque financiar un futuro es un costo de corto plazo. Como sí había precio de mercado del NQZ26 (28,534), sacamos el carry de la base observada: ln(F0/S0)/T0 = 3.904%. Sin ese dato habríamos usado r_c − q.
Nos quedamos con 48 contratos porque es el número que deja la beta residual más cerca de cero (−0.0034; con 47 queda en 0.0129). Con 48 quedamos un poquito sobrecubiertos, es decir, ganamos algo si el mercado cae. Con 47 quedaríamos cortos de cobertura.
Quien ya tiene las acciones y le preocupa que el mercado caiga toma una posición corta. Si el índice baja, pierde en las acciones pero gana en el futuro que vendió. La posición larga es lo contrario: la usa alguien que va a comprar acciones más adelante (o que está corto en ellas) y teme que el mercado suba antes. En ambos casos lo que se cubre es el movimiento general del Nasdaq-100, no lo que le pase a cada empresa en particular.
Con la posición corta, si el mercado sube, las acciones ganan, el futuro pierde y toca meter plata a la cuenta de margen. Si el mercado baja, lo que gana el futuro compensa lo que pierden las acciones. Al final, en los dos casos el resultado se acerca a la tasa libre de riesgo.
Decisión: el gerente ya tiene las acciones y quiere protegerse de una caída, así que vendemos 48 contratos NQ. Con ese tamaño, cada tick (0.25 puntos) vale USD 240.
mov <- c(-0.20, -0.10, -0.05, 0, 0.05, 0.10, 0.20)
dF <- F0 * mov # supone base constante
pnl_acc <- beta_p * mov * capital
pnl_corto <- -N * mult * dF
esc <- data.frame(`Movimiento NDX` = pct(mov, 0), `Puntos` = num(dF, 1),
Ticks = num(dF / fut$tick, 0), `G/P acciones` = usd(pnl_acc),
`G/P futuro corto` = usd(pnl_corto), `G/P futuro largo` = usd(-pnl_corto),
`Resultado neto cubierto` = usd(pnl_acc + pnl_corto), check.names = FALSE)
tabla(esc, "Escenarios: acciones vs. futuros en ticks")| Movimiento NDX | Puntos | Ticks | G/P acciones | G/P futuro corto | G/P futuro largo | Resultado neto cubierto |
|---|---|---|---|---|---|---|
| -20% | -5,706.8 | -22,827 | -$5,454,466 | $5,478,528 | -$5,478,528 | $24,062 |
| -10% | -2,853.4 | -11,414 | -$2,727,233 | $2,739,264 | -$2,739,264 | $12,031 |
| -5% | -1,426.7 | -5,707 | -$1,363,616 | $1,369,632 | -$1,369,632 | $6,016 |
| 0% | 0.0 | 0 | $0 | $0 | $0 | $0 |
| 5% | 1,426.7 | 5,707 | $1,363,616 | -$1,369,632 | $1,369,632 | -$6,016 |
| 10% | 2,853.4 | 11,414 | $2,727,233 | -$2,739,264 | $2,739,264 | -$12,031 |
| 20% | 5,706.8 | 22,827 | $5,454,466 | -$5,478,528 | $5,478,528 | -$24,062 |
plot_ly(x = pct(mov, 0), y = pnl_acc, type = "bar", name = "Acciones") |>
add_bars(y = pnl_corto, name = "Futuro corto") |>
add_lines(y = pnl_acc + pnl_corto, name = "Neto", line = list(color = "black", width = 3)) |>
layout(title = "La posición corta compensa el movimiento del portafolio",
xaxis = list(title = "Movimiento del Nasdaq-100", categoryorder = "array",
categoryarray = pct(mov, 0)),
yaxis = list(title = "USD"), barmode = "group")# Cuánto del riesgo es cubrible
var_m_ndx <- var(as.numeric(ret_m$NDX))
frac_sis <- min(1, beta_p^2 * var_m_ndx / sd_pm^2)
sd_res <- sqrt(max(sd_pm^2 - beta_p^2 * var_m_ndx, 0))
mu_cub <- mu_pm - beta_p * (mean(as.numeric(ret_m$NDX)) - carry / 12) # media mensual cubierta
var_cub <- -(exp(mu_cub + qnorm(c(0.05, 0.01)) * sd_res) - 1) # incluye la media, comparable con var_parEn la tabla se ve que la cobertura compensa casi todo. Si el Nasdaq-100 cae 10%, las acciones pierden USD 2,727,232 y el futuro corto gana USD 2,739,264. Quedan USD 12,032 de diferencia, que vienen de estar un poco sobrecubiertos con 48 contratos.
El Nasdaq-100 explica el 48.9% de la varianza mensual del portafolio. Con la cobertura, el VaR al 95% bajaría de USD 2,752,653 a unos USD 2,111,679 (23% menos), y el VaR al 99% de USD 4,070,157 a USD 3,078,228 (24% menos). No baja más porque la otra mitad del riesgo es propia de AAPL y AMGN, y esa los futuros no la tocan. La media cubierta ya tiene en cuenta la posición corta y el carry, así que los dos VaR se pueden comparar.
Supuestos de la simulación
nmes <- horizonte * 12
series <- c(tickers, "NDX")
mu_sim <- colMeans(ret_m[, series])
if (deriva == "neutral") # acciones a rf, NDX a rf - q (sin alfa)
mu_sim <- c(rep(rf_c, 3), rf_c - q_ndx) / 12 - diag(cov(ret_m[, series])) / 2
set.seed(2026) # el set.seed(1) de la sección 3 anulaba esta semilla
Z <- MASS::mvrnorm(nsim * nmes, mu_sim, cov(ret_m[, series]))
sim <- array(Z, dim = c(nsim, nmes, 4))
precio_sim <- function(k) {
cs <- t(apply(sim[, , k], 1, cumsum))
cbind(P0[series[k]], P0[series[k]] * exp(cs))
}
Psim <- lapply(1:4, precio_sim)
acciones <- capital * w / P0[tickers]
V <- Reduce(`+`, lapply(1:3, function(k) acciones[k] * Psim[[k]])) # valor portafolio
I <- Psim[[4]] # Nasdaq-100
# Calendario y contratos
fechas_ext <- c(f_inicio, seq(as.Date("2026-12-01"), by = "month", length.out = nmes + 2) - 1)
fechas <- fechas_ext[1:(nmes + 1)]
mes <- as.integer(format(fechas, "%m"))
es_venc <- mes %in% c(3, 6, 9, 12); es_venc[1] <- FALSE
venc_vec <- c(which(es_venc), max(which(es_venc)) + 3)
contrato_de <- sapply(1:(nmes + 1), function(p) min(venc_vec[venc_vec >= p]))
venc_sig <- sapply(1:(nmes + 1), function(p) min(venc_vec[venc_vec > p]))
cod <- function(pos) {
f <- fechas_ext[pos]
paste0("NQ", c(`3` = "H", `6` = "M", `9` = "U", `12` = "Z")[as.character(as.integer(format(f, "%m")))],
format(f, "%y"))
}
# carry: definido en la sección 6 (implícito en la base observada o r_c - q)
tau <- function(v) matrix(rep((v - 1:(nmes + 1)) / 12, each = nsim), nsim)
F_liq <- I * exp(carry * tau(contrato_de)) # liquidación del contrato vigente
F_ape <- I * exp(carry * tau(venc_sig)) # precio del contrato que se abre
F_ape[, 1] <- F0
dFm <- cbind(0, F_liq[, -1] - F_ape[, -(nmes + 1)])
# Contratos vigentes en cada mes (con rebalanceo en cada roll)
N_ten <- matrix(N, nsim, nmes + 1)
for (p in 3:(nmes + 1)) {
N_ten[, p] <- if (es_venc[p - 1] && rebalanceo)
round(Reduce(`+`, lapply(1:3, function(k) acciones[k] * Psim[[k]][, p - 1] * beta_i[k])) / # beta con pesos vigentes
(F_ape[, p - 1] * mult)) else N_ten[, p - 1]
}
pnl_corta <- -N_ten * mult * dFm
G_fut <- matrix(0, nsim, nmes + 1) # cuenta de caja de los futuros
for (p in 2:(nmes + 1)) G_fut[, p] <- G_fut[, p - 1] * exp(rf_c / 12) + pnl_corta[, p] # financia/capitaliza a la tasa corta
V_cub <- V + G_fut
i_rep <- which.min(abs(I[, nmes + 1] - median(I[, nmes + 1])))
liquidar_margen <- function(pnl, Nt) {
n <- nrow(pnl); m <- ncol(pnl); saldo <- Nt[, 1] * IM
o <- lapply(1:7, function(i) matrix(0, n, m))
names(o) <- c("ini", "aporte", "pre", "llamado", "repo", "retiro", "fin")
for (j in 1:m) {
if (j > 1) {
dep <- ifelse(Nt[, j] != Nt[, j - 1], pmax(Nt[, j] * IM - saldo, 0), 0)
saldo <- saldo + dep; o$aporte[, j] <- dep
}
o$ini[, j] <- saldo; saldo <- saldo + pnl[, j]; o$pre[, j] <- saldo
ll <- saldo < Nt[, j] * MM
rp <- ifelse(ll, Nt[, j] * IM - saldo, 0); saldo <- saldo + rp
o$llamado[, j] <- ll; o$repo[, j] <- rp
rt <- if (retiro_exceso) pmax(saldo - Nt[, j] * IM, 0) else 0 # retira el excedente sobre IM
saldo <- saldo - rt; o$retiro[, j] <- rt; o$fin[, j] <- saldo
}
o
}
pp <- 2:(nmes + 1)
mg <- liquidar_margen(pnl_corta[i_rep, pp, drop = FALSE], N_ten[i_rep, pp, drop = FALSE])
mgA <- liquidar_margen(pnl_corta[, pp], N_ten[, pp])tab8 <- data.frame(
Mes = 1:nmes, Fecha = format(fechas[pp], "%b-%Y"), Contrato = cod(contrato_de[pp]),
N = N_ten[i_rep, pp], `F inicial` = F_ape[i_rep, pp - 1], `F final` = F_liq[i_rep, pp],
`ΔF` = dFm[i_rep, pp], Ticks = round(dFm[i_rep, pp] / fut$tick),
`G/P larga` = -pnl_corta[i_rep, pp], `G/P corta` = pnl_corta[i_rep, pp],
`Aporte por roll` = mg$aporte[1, ], `Saldo antes del llamado` = mg$pre[1, ],
`Llamado` = ifelse(mg$llamado[1, ] == 1, "SÍ", "No"), `Reposición` = mg$repo[1, ],
`Retiro` = mg$retiro[1, ], `Saldo final` = mg$fin[1, ], check.names = FALSE)
datatable(tab8, rownames = FALSE, extensions = "Buttons",
options = list(pageLength = 12, scrollX = TRUE, dom = "Bfrtip",
buttons = c("copy", "csv", "excel")),
caption = "Liquidación mensual de la posición (trayectoria representativa)") |>
formatRound(c("F inicial", "F final", "ΔF"), 2) |>
formatCurrency(c("G/P larga", "G/P corta", "Aporte por roll", "Saldo antes del llamado",
"Reposición", "Retiro", "Saldo final"), digits = 0) |>
formatStyle("Llamado", target = "row",
backgroundColor = styleEqual(c("SÍ", "No"), c("#fdecea", "white")))trim <- contrato_de[pp]
res8 <- data.frame(
Contrato = cod(unique(trim)),
`G/P posición corta` = usd(tapply(pnl_corta[i_rep, pp], trim, sum)),
`Llamados al margen` = tapply(mg$llamado[1, ], trim, sum),
`Reposiciones` = usd(tapply(mg$repo[1, ], trim, sum)),
`Retiros` = usd(tapply(mg$retiro[1, ], trim, sum)),
`Saldo al cierre` = usd(tapply(mg$fin[1, ], trim, function(x) tail(x, 1))), check.names = FALSE)
tabla(res8, "Flujos trimestrales de la posición en futuros (trayectoria representativa)")| Contrato | G/P posición corta | Llamados al margen | Reposiciones | Retiros | Saldo al cierre | |
|---|---|---|---|---|---|---|
| 3 | NQZ26 | $3,020,839 | 0 | $0 | $3,020,839 | $1,584,000 |
| 6 | NQH27 | -$3,799,639 | 2 | $3,835,197 | $35,557 | $1,617,000 |
| 9 | NQM27 | -$290,339 | 2 | $3,036,397 | $2,746,058 | $1,683,000 |
| 12 | NQU27 | -$296,022 | 1 | $436,693 | $220,703 | $1,602,967 |
| 15 | NQZ27 | $903,307 | 1 | $2,296,804 | $3,219,078 | $1,584,000 |
| 18 | NQH28 | -$720,913 | 2 | $2,365,315 | $1,644,402 | $1,617,000 |
| 21 | NQM28 | -$848,451 | 1 | $1,567,466 | $719,014 | $1,650,000 |
| 24 | NQU28 | $725,780 | 0 | $0 | $923,780 | $1,452,000 |
| 27 | NQZ28 | -$3,374,456 | 2 | $6,626,985 | $3,252,529 | $1,617,000 |
| 30 | NQH29 | -$3,649,805 | 1 | $4,295,476 | $645,671 | $1,683,000 |
| 33 | NQM29 | $4,533,138 | 0 | $0 | $4,533,138 | $1,914,000 |
| 36 | NQU29 | -$6,443,347 | 2 | $6,443,347 | $0 | $2,046,000 |
| 39 | NQZ29 | -$10,056,645 | 3 | $9,957,645 | $0 | $1,947,000 |
| 42 | NQH30 | -$1,857,375 | 2 | $4,011,199 | $2,186,824 | $1,914,000 |
| 45 | NQM30 | $479,777 | 1 | $2,899,116 | $3,378,893 | $2,046,000 |
| 48 | NQU30 | -$2,558,328 | 2 | $7,829,079 | $5,270,751 | $2,046,000 |
| 51 | NQZ30 | -$3,382,490 | 1 | $3,382,490 | $0 | $2,343,000 |
plot_ly(x = fechas[pp], y = mg$fin[1, ], type = "scatter", mode = "lines", name = "Saldo de margen") |>
add_lines(y = N_ten[i_rep, pp] * MM, name = "Margen de mantenimiento", line = list(dash = "dash")) |>
add_lines(y = N_ten[i_rep, pp] * IM, name = "Margen inicial", line = list(dash = "dot")) |>
add_markers(x = fechas[pp][mg$llamado[1, ] == 1], y = mg$pre[1, mg$llamado[1, ] == 1],
name = "Llamado al margen", marker = list(color = "red", size = 10)) |>
layout(title = "Cuenta de margen", yaxis = list(title = "USD"), xaxis = list(title = ""))En el conjunto de 10,000 simulaciones, la probabilidad de recibir al menos un llamado al margen en los 4 años es 100.0%, con un promedio de 26.5 llamados y reposiciones totales esperadas de $69,229,606, retiros esperados de $38,590,287 y un aporte neto esperado de $32,051,311. El gerente debe reservar liquidez para estos flujos: la cobertura no cuesta prima, pero sí exige caja cuando el mercado sube.
Al vencer cada contrato se cierra la posición (liquida al precio del índice) y se abre una posición corta en el siguiente vencimiento trimestral, recalculando el número de contratos con el valor actualizado del portafolio.
pos_r <- c(which(es_venc), nmes + 1)
ini_r <- c(1, head(pos_r, -1))
tab9 <- data.frame(
Fecha = format(fechas[pos_r], "%b-%Y"),
`Contrato cerrado` = cod(contrato_de[pos_r]),
`Precio de apertura` = num(F_ape[i_rep, ini_r]), `Precio de cierre` = num(F_liq[i_rep, pos_r]),
Contratos = N_ten[i_rep, pos_r],
`G/P realizada` = usd(sapply(seq_along(pos_r), function(k) sum(pnl_corta[i_rep, (ini_r[k] + 1):pos_r[k]]))),
`Contrato abierto` = ifelse(pos_r <= nmes, cod(venc_sig[pos_r]), "— (fin)"),
`Precio nuevo contrato` = ifelse(pos_r <= nmes, num(F_ape[i_rep, pos_r]), "—"),
`Nuevos contratos` = ifelse(pos_r <= nmes, N_ten[i_rep, pmin(pos_r + 1, nmes + 1)], NA),
`G/P acumulada` = usd(G_fut[i_rep, pos_r]),
`Valor acciones` = usd(V[i_rep, pos_r]), `Valor cubierto` = usd(V_cub[i_rep, pos_r]),
check.names = FALSE)
datatable(tab9, rownames = FALSE, options = list(pageLength = 17, scrollX = TRUE, dom = "t"),
caption = "Cierre y apertura de posiciones en cada roll-over")r_sin <- (V[, nmes + 1] / capital)^(1 / horizonte) - 1
r_con <- (pmax(V_cub[, nmes + 1], 0) / capital)^(1 / horizonte) - 1 # V_cub <= 0 es ruina (-100%), evita NaN
n_ruina <- sum(V_cub[, nmes + 1] <= 0)
n_ruina_alguna <- sum(apply(V_cub, 1, min) <= 0)
efecto <- data.frame(
Indicador = c("Rendimiento anual promedio", "Desviación del rendimiento anual",
"Percentil 5% del rendimiento anual", "Probabilidad de pérdida a 4 años",
"Rendimiento anual trayectoria representativa"),
`Sin cobertura` = c(pct(mean(r_sin)), pct(sd(r_sin)), pct(quantile(r_sin, .05)),
pct(mean(V[, nmes + 1] < capital)), pct(r_sin[i_rep])),
`Con cobertura y roll-over` = c(pct(mean(r_con)), pct(sd(r_con)), pct(quantile(r_con, .05)),
pct(mean(V_cub[, nmes + 1] < capital)), pct(r_con[i_rep])),
check.names = FALSE)
tabla(efecto, "Efecto del roll-over sobre la rentabilidad (todas las simulaciones)")| Indicador | Sin cobertura | Con cobertura y roll-over |
|---|---|---|
| Rendimiento anual promedio | 27.68% | 15.76% |
| Desviación del rendimiento anual | 14.30% | 11.88% |
| Percentil 5% del rendimiento anual | 6.06% | -1.96% |
| Probabilidad de pérdida a 4 años | 1.16% | 6.97% |
| Rendimiento anual trayectoria representativa | 26.48% | 14.77% |
plot_ly(alpha = 0.55) |>
add_histogram(x = r_sin, name = "Sin cobertura") |>
add_histogram(x = r_con, name = "Con cobertura") |>
layout(barmode = "overlay", title = "Distribución del rendimiento anual a 4 años",
xaxis = list(title = "Rendimiento anual", tickformat = ".0%"))Cuando vence un contrato, la posición se cierra al precio del índice y se vende el siguiente vencimiento a F = S e^{cτ}. Como c es positivo, el futuro nuevo se vende por encima del spot (contango), y esa diferencia se va cerrando hasta el vencimiento. Esa convergencia es ganancia para el que vende, trimestre tras trimestre. Por eso el portafolio cubierto no rinde cero, sino más o menos la tasa libre de riesgo más el alfa de las acciones. Lo que se sacrifica es la prima de riesgo del mercado.
Los números lo muestran. El rendimiento anual promedio baja de 27.72% a 15.80% y la desviación de 14.31% a 11.92%. Pero no todo mejora. El percentil 5% pasa de 5.99% a −2.30%, y la probabilidad de perder plata en cuatro años sube de 1.57% a 7.44%. Con la deriva histórica el índice sube en la mayoría de las trayectorias, y cuando sube la posición corta resta. Es decir, la cobertura reduce la dispersión pero le quita al portafolio el empuje de un mercado alcista. La trayectoria representativa lo deja claro: el Nasdaq-100 pasa de 28,385 a cerca de 58,161 puntos y la posición corta acumula una pérdida de USD 28.4 millones.
Hubo 2 trayectorias, de 10,000, en las que el valor cubierto llegó a cero o menos. A esas les asignamos pérdida total (−100%).
tabla(data.frame(
Referencia = c("^TNX — Tesoro 10 años (indicada en el taller)", "^FVX — Tesoro 5 años",
"^IRX — Letra 13 semanas"),
`Tasa al 30-oct-2026` = pct(c(rf_tnx, rf_fvx, rf_irx), 3), check.names = FALSE),
"Tasas libres de riesgo observadas")| Referencia | Tasa al 30-oct-2026 |
|---|---|
| ^TNX — Tesoro 10 años (indicada en el taller) | 4.929% |
| ^FVX — Tesoro 5 años | 4.721% |
| ^IRX — Letra 13 semanas | 3.973% |
El taller describe ^TNX como el índice del Tesoro de 1 a 3 años, pero en Yahoo Finance ^TNX corresponde al rendimiento del bono del Tesoro a 10 años. Se usa ^TNX como indica el docente (4.929%); como el horizonte es de 4 años, ^FVX (5 años) sería la referencia de plazo más cercana y se reporta para comparar. El carry, la capitalización de los flujos de los futuros y el descuento de su valor presente usan la tasa corta ^IRX (3.973%), porque la liquidación y el financiamiento de un futuro son de corto plazo.
En teoría, un portafolio con beta cubierta a cero no tiene riesgo sistemático y debe rendir la tasa libre de riesgo: \(E[V_t^{cub}]\approx V_0\,e^{r_f t}\).
q_pos <- 1 + 3 * (1:(nmes / 3)); t_q <- (q_pos - 1) / 12
tab10 <- data.frame(
Trimestre = 1:length(q_pos), Fecha = format(fechas[q_pos], "%b-%Y"),
`E[V] sin cobertura` = usd(colMeans(V[, q_pos])),
`E[V] cubierto (simulado)` = usd(colMeans(V_cub[, q_pos])),
`V0·e^(rf·t) teórico` = usd(capital * exp(rf * t_q)),
`E[G/P futuros]` = usd(colMeans(G_fut[, q_pos])),
`VP de E[G/P futuros]` = usd(colMeans(G_fut[, q_pos]) * exp(-rf_c * t_q)),
`Desv. sin cobertura` = usd(apply(V[, q_pos], 2, sd)),
`Desv. cubierto` = usd(apply(V_cub[, q_pos], 2, sd)), check.names = FALSE)
datatable(tab10, rownames = FALSE, options = list(pageLength = 16, scrollX = TRUE, dom = "t"),
caption = "Valor esperado trimestral: portafolio sin cobertura vs. cubierto")banda <- function(M, nombre, col) {
list(x = fechas, m = colMeans(M), lo = apply(M, 2, quantile, .05),
hi = apply(M, 2, quantile, .95), n = nombre, c = col)
}
b1 <- banda(V, "Sin cobertura", "rgba(31,119,180,0.2)")
b2 <- banda(V_cub, "Cubierto", "rgba(214,39,40,0.2)")
fig10 <- plot_ly()
for (b in list(b1, b2)) {
fig10 <- fig10 |>
add_ribbons(x = b$x, ymin = b$lo, ymax = b$hi, name = paste(b$n, "(5%–95%)"),
fillcolor = b$c, line = list(width = 0)) |>
add_lines(x = b$x, y = b$m, name = paste(b$n, "— media"))
}
fig10 |> add_lines(x = fechas, y = capital * exp(rf * (0:nmes) / 12), name = "V0·e^(rf·t)",
line = list(dash = "dash", color = "black")) |>
layout(title = "Valor esperado y bandas de confianza", yaxis = list(title = "USD"))A 4 años el valor esperado sin cobertura es $100,329,934 y el cubierto $66,863,814, frente a $42,627,861 que daría la tasa libre de riesgo. La desviación estándar del valor final baja de $48,775,640 a $28,514,117. Conclusión: la cobertura reduce el riesgo y también la rentabilidad esperada, porque se cede la prima de riesgo del mercado a cambio de estabilidad.
r1_sin <- V[, 2] / capital - 1; r1_con <- V_cub[, 2] / capital - 1
rm_sin <- (mean(V[, nmes + 1]) / capital)^(1 / nmes) - 1
rm_con <- (mean(V_cub[, nmes + 1]) / capital)^(1 / nmes) - 1
tab11 <- data.frame(
Enfoque = c("1. Sin cobertura (esperado)", "2. Cubierto con futuros (esperado)",
"3a. Cuantil 5% del retorno — sin cobertura", "3b. Cuantil 1% del retorno — sin cobertura",
"3c. Cuantil 5% del retorno — cubierto", "3d. Cuantil 1% del retorno — cubierto"),
`Rendimiento mensual` = pct(c(mean(r1_sin), mean(r1_con), quantile(r1_sin, c(.05, .01)),
quantile(r1_con, c(.05, .01)))),
`Valor a 1 mes` = usd(capital * (1 + c(mean(r1_sin), mean(r1_con), quantile(r1_sin, c(.05, .01)),
quantile(r1_con, c(.05, .01))))),
`Rend. mensual equivalente 4 años` = c(pct(rm_sin), pct(rm_con), rep("—", 4)),
`Valor a 4 años` = usd(c(mean(V[, nmes + 1]), mean(V_cub[, nmes + 1]),
quantile(V[, nmes + 1], c(.05, .01)), quantile(V_cub[, nmes + 1], c(.05, .01)))),
check.names = FALSE)
tabla(tab11, "Rendimiento y valor esperado del portafolio")| Enfoque | Rendimiento mensual | Valor a 1 mes | Rend. mensual equivalente 4 años | Valor a 4 años |
|---|---|---|---|---|
|
2.17% | $35,760,790 | 2.22% | $100,329,934 |
|
1.11% | $35,388,042 | 1.36% | $66,863,814 |
| 3a. Cuantil 5% del retorno — sin cobertura | -7.93% | $32,224,240 | — | $44,285,495 |
| 3b. Cuantil 1% del retorno — sin cobertura | -11.85% | $30,852,992 | — | $34,279,569 |
| 3c. Cuantil 5% del retorno — cubierto | -6.21% | $32,824,951 | — | $32,332,842 |
| 3d. Cuantil 1% del retorno — cubierto | -8.95% | $31,866,933 | — | $22,888,517 |
divs <- sapply(tickers, function(tk) {
d <- tryCatch(getDividends(tk, from = f_inicio - 365, to = f_inicio, auto.assign = FALSE),
error = function(e) NULL)
if (is.null(d) || NROW(d) == 0) 0 else sum(d)
})
yld <- divs / P0c
anios <- as.numeric(f_inicio - as.Date(index(precios)[1])) / 365.25
r_tot <- (as.numeric(last(precios[, tickers])) / as.numeric(precios[1, tickers]))^(1 / anios) - 1
r_pre <- (as.numeric(last(cierres)) / as.numeric(cierres[1, ]))^(1 / anios) - 1
tabla(data.frame(Acción = tickers, `Dividendo 12 m por acción` = usd(divs, 2),
`Rendimiento por dividendo` = pct(yld), `Dividendos anuales en el portafolio` = usd(capital * w * yld),
`Rend. anual solo precio (10 a.)` = pct(r_pre), `Rend. anual total (10 a.)` = pct(r_tot),
`Aporte del dividendo` = pct(r_tot - r_pre), check.names = FALSE),
"Dividendos y su efecto en el rendimiento total")| Acción | Dividendo 12 m por acción | Rendimiento por dividendo | Dividendos anuales en el portafolio | Rend. anual solo precio (10 a.) | Rend. anual total (10 a.) | Aporte del dividendo | |
|---|---|---|---|---|---|---|---|
| AAPL | AAPL | $1.06 | 0.31% | $74,818 | 28.37% | 29.52% | 1.15% |
| AMGN | AMGN | $9.94 | 2.13% | $227,698 | 12.69% | 16.10% | 3.41% |
| PEP | PEP | $5.81 | 4.23% | $0 | 2.51% | 5.74% | 3.23% |
Las tres empresas pagan dividendos, por eso usamos precios ajustados: el retorno total incluye la variación del precio más el dividendo reinvertido. Con los pesos que quedaron, el portafolio recibe unos USD 302,516 al año (0.86% del capital), que es como un 0.072% mensual adicional. La que más paga en proporción es PEP (4.23%), pero como no entró, casi todo el dividendo viene de AMGN (USD 227,698).
Con la cobertura, el dueño de las acciones sigue recibiendo sus dividendos. El futuro sobre el índice ya tiene descontado el dividendo esperado del NDX en su precio, \(F=S\,e^{(r-q)T}\): entre más alto sea q, más barato el futuro y menos se gana por convergencia con la posición corta.
bets <- c(0.6, beta_p, 1.5)
Nb <- round(bets * capital / (F0 * mult))
tab12 <- data.frame(
Beta = c("0.60 (hipotética)", paste0(num(beta_p, 2), " (portafolio)"), "1.50 (hipotética)"),
`N* exacto` = num(bets * capital / (F0 * mult), 2), `Contratos` = Nb,
`Exposición sistemática` = usd(bets * capital),
`Cambio por 1% del NDX` = usd(bets * capital * 0.01),
`Cambio por punto del NDX` = usd(bets * capital / S0),
`Compensa la cobertura por punto` = usd(Nb * mult),
`Beta residual` = num(bets - Nb * F0 * mult / capital, 4), check.names = FALSE)
tabla(tab12, "Número de contratos según la beta")| Beta | N* exacto | Contratos | Exposición sistemática | Cambio por 1% del NDX | Cambio por punto del NDX | Compensa la cobertura por punto | Beta residual |
|---|---|---|---|---|---|---|---|
| 0.60 (hipotética) | 36.80 | 37 | $21,000,000 | $210,000 | $740 | $740 | -0.0033 |
| 0.78 (portafolio) | 47.79 | 48 | $27,272,329 | $272,723 | $961 | $960 | -0.0034 |
| 1.50 (hipotética) | 92.00 | 92 | $52,500,000 | $525,000 | $1,850 | $1,840 | -0.0001 |
m <- seq(-0.25, 0.25, 0.01)
fig12 <- plot_ly()
for (k in 1:3) {
fig12 <- fig12 |>
add_lines(x = m, y = bets[k] * m * capital, name = paste0("β=", num(bets[k], 2), " sin cobertura"),
line = list(dash = "dot")) |>
add_lines(x = m, y = bets[k] * m * capital - Nb[k] * mult * F0 * m,
name = paste0("β=", num(bets[k], 2), " cubierto"))
}
layout(fig12, title = "G/P del portafolio frente a movimientos del índice",
xaxis = list(title = "Movimiento del Nasdaq-100", tickformat = ".0%"), yaxis = list(title = "USD"))1. El portafolio de máximo Sharpe quedó con 69.5% en AAPL y 30.5% en AMGN, sin PEP. La baja correlación entre AAPL y AMGN (0.21) es lo que permite llegar a un Sharpe de 0.83.
2. El VaR mensual es USD 2,752,653 al 95% y USD 4,070,157 al 99%. Cerca del 49% de la varianza es sistemática, y esa parte es la que se puede cubrir con futuros.
3. Con β = 0.78 vendemos 48 contratos NQ. Con el roll-over trimestral, la desviación del valor final baja de USD 48.1 millones a USD 28.5 millones. El costo es que el rendimiento esperado cae de 27.7% a 15.8% anual y la probabilidad de pérdida a cuatro años sube de 1.6% a 7.4%.
4. La cobertura no cobra prima, pero pide mucha liquidez. Hubo llamados al margen en todas las simulaciones, y el aporte neto esperado ronda los USD 32 millones en cuatro años.
5. Entre más beta, más contratos y más capital para el margen.
Limitaciones: Suponemos retornos normales e independientes, lo que subestima las caídas fuertes, y parámetros históricos que no cambian. La base del futuro se modela con un costo de acarreo fijo, liquidamos cada mes y no cada día, y los vencimientos quedan al cierre de mes. Lo de la liquidación mensual se nota en la cuenta de margen. En la trayectoria representativa hay meses en que el saldo queda negativo antes del llamado (−USD 2,626,918 en octubre de 2027), algo que con liquidación diaria no pasaría porque el llamado llegaría antes.