# =================== 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). En este ejercicio se liquida mensualmente con fines académicos"
)
mult <- fut$multiplicador; IM <- fut$margen_inicial; MM <- fut$margen_mant
descargar <- 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"])

Resumen del ejercicio

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)")
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). En este ejercicio se liquida mensualmente con fines académicos

1. Análisis fundamental

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")
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)")
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")
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

La expectativa del modelo extrapola el comportamiento de los últimos 10 años; por eso se contrasta con el precio objetivo de consenso. Si el consenso es muy inferior al modelo, el retorno histórico probablemente sobreestima el futuro.

Apple (AAPL) — Tecnología / hardware y servicios

  • Negocio: diseña y vende iPhone, Mac, iPad, wearables (Apple Watch, AirPods) y servicios (App Store, iCloud, Apple Music, Apple Pay, publicidad, licencias).
  • Fuentes de ingreso: el iPhone aporta cerca de la mitad de los ingresos; Servicios es el segmento de mayor crecimiento y mayor margen, lo que vuelve más recurrente el flujo de caja.
  • Situación financiera: generación de caja muy alta, programa de recompra de acciones de gran tamaño y dividendo bajo pero creciente.
  • Por qué entra: es el motor de crecimiento del portafolio y la acción con mayor sensibilidad al índice (beta alta). Aporta rentabilidad esperada, pero concentra el riesgo sistemático que se cubrirá con futuros.

Amgen (AMGN) — Salud / biotecnología

  • Negocio: biofarmacéutica con medicamentos en hueso (Prolia, Evenity), cardiovascular (Repatha), inflamación (Enbrel, Tezspire), oncología y enfermedades raras (tras adquirir Horizon en 2023).
  • Riesgos y catalizadores: competencia de biosimilares en productos maduros frente a un pipeline relevante (incluido el candidato para obesidad MariTide).
  • Por qué entra: sector defensivo con demanda poco ligada al ciclo económico, beta baja, dividendo alto y baja correlación con Apple: reduce la varianza del portafolio.

PepsiCo (PEP) — Consumo básico / bebidas y snacks

  • Negocio: bebidas (Pepsi, Gatorade) y alimentos de conveniencia (Frito-Lay, Quaker), con fuerte peso de Norteamérica y presencia global.
  • Situación financiera: flujos estables, poder de fijación de precios y más de 50 años consecutivos aumentando el dividendo.
  • Por qué entra: acción de baja volatilidad y beta baja; amortigua las caídas del mercado y aporta ingreso por dividendos.

Lógica de la selección: una acción de crecimiento con beta alta (AAPL) combinada con dos defensivas de sectores distintos (salud y consumo básico) busca que el optimizador encuentre un portafolio con buena relación retorno-riesgo gracias a la diversificación, no solo por la rentabilidad individual.


2. Retornos, volatilidad, covarianzas y correlaciones

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")
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")
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
C <- cor(ret_m[, tickers])
tabla(round(C, 4), "Matriz de correlaciones")
Matriz de correlaciones
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")

3. Portafolio óptimo de media-varianza

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)))
Portafolios candidatos — rf = 4.93%
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")
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%"))

4. Valor en Riesgo mensual (95% y 99%)

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")
VaR mensual del portafolio
Método Confianza VaR mensual % VaR mensual USD
Paramétrico (normal) 95% (VaR 5%) 7.86% $2,752,653
Paramétrico (normal) 99% (VaR 1%) 11.63% $4,070,157
Histórico 95% (VaR 5%) 8.80% $3,078,400
Histórico 99% (VaR 1%) 10.69% $3,740,070
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: en un mes normal, con 95% de confianza la pérdida no debería superar $2,752,653; en 1 de cada 100 meses podría superar $4,070,157. El VaR es el monto de pérdida que el gerente quiere proteger. Sin embargo, los futuros sobre el índice solo cubren la parte sistemática del riesgo (la que se mueve con el Nasdaq-100); la parte específica de cada empresa no se elimina. En la pregunta 7 se calcula cuánto del VaR es cubrible.


5. Betas por CAPM

\[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")
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%.


6. Número óptimo de contratos

\[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")
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")
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

El futuro teórico y el carry usan la tasa corta (^IRX, 3.973%) y no la de 10 años, porque el costo de financiar un futuro es de corto plazo. Si NQ=F está disponible, el carry se toma implícito de la base observada, \(\ln(F_0/S_0)/T_0=\) 3.904%; si no, se usa \(r_c-q\).

Se toman 48 contratos: es el entero que deja la beta residual más cercana a cero (-0.0034). Redondear hacia arriba sobrecubre (el portafolio quedaría levemente “corto” en mercado) y hacia abajo subcubre. Si se quisiera afinar, la fracción restante (-0.21 contratos NQ) equivale a unos -2 contratos Micro E-mini (MNQ), cuyo multiplicador es USD 2.


7. Posición en futuros: corta

  • Posición corta: la usa quien ya tiene las acciones y teme una caída del mercado. Si el índice baja, las acciones pierden pero el futuro vendido gana.
  • Posición larga: la usa quien va a comprar acciones en el futuro (o está corto en ellas) y teme que el mercado suba antes de invertir.
  • Riesgo cubierto: el riesgo sistemático (movimientos generales del Nasdaq-100), no el riesgo propio de cada empresa.
  • Si el mercado sube: las acciones ganan y el futuro corto pierde (hay que reponer margen); el resultado neto queda cerca de la tasa libre de riesgo.
  • Si el mercado baja: las acciones pierden y el futuro corto gana; la pérdida se compensa.

Decisión: el gerente posee las acciones y quiere protegerse de una caída → VENDER 48 contratos NQ. Cada tick (0.25 puntos) mueve la posición en $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")
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,464 $5,478,528 -$5,478,528 $24,064
-10% -2,853.4 -11,414 -$2,727,232 $2,739,264 -$2,739,264 $12,032
-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,232 -$2,739,264 $2,739,264 -$12,032
20% 5,706.8 22,827 $5,454,464 -$5,478,528 $5,478,528 -$24,064
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_par

El 48.9% de la varianza mensual del portafolio es sistemática (explicada por el Nasdaq-100). Con la cobertura, el VaR 95% mensual pasaría de $2,752,653 a cerca de $2,111,679, y el VaR 99% de $4,070,157 a $3,078,228. Lo que queda es riesgo propio de AAPL, AMGN y PEP. La media mensual cubierta incorpora el efecto de la posición corta (incluido el carry), por eso es comparable con el VaR sin cobertura.


8. Liquidación mensual, cuenta de margen y llamados al margen

Supuestos de la simulación

  • Se simulan 10,000 trayectorias mensuales conjuntas de las 3 acciones y el Nasdaq-100 (normal multivariada sobre retornos log, con covarianzas históricas), 48 meses. La deriva simulada es historica: “historica” usa las medias históricas (incluye el alfa de las acciones); “neutral” usa \(\mu=r_c-q\) para el índice y \(\mu=r_c\) para las acciones, sin alfa.
  • El precio del futuro en cada fecha es \(F_t=S_t\,e^{c\,(T-t)}\) con carry \(c=\) 3.904% (implícito en la base observada de NQ=F; si no hay dato, \(c=r_c-q\) con \(q=\) 0.7%); al vencimiento converge al índice.
  • Con fines académicos los vencimientos se ubican al cierre de dic, mar, jun y sep (en la realidad, el tercer viernes).
  • La cuenta inicia con \(N\times\) margen inicial. Si el saldo cae por debajo de \(N\times\) margen de mantenimiento, hay llamado al margen y se repone hasta el margen inicial. Los excedentes sobre el margen inicial se retiran en cada liquidación.
  • En cada roll el número de contratos se recalcula con las betas individuales y los valores vigentes de cada acción. La ganancia o pérdida acumulada de los futuros se capitaliza (o financia) a la tasa corta: \(G_t=G_{t-1}\,e^{r_c/12}+\Pi_t\).
  • Para las tablas se usa la trayectoria representativa: la simulación cuyo Nasdaq-100 final es la mediana.
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)")
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 $12,085 1 $926,762 $938,847 $1,584,000
6 NQH27 -$502,142 2 $820,316 $318,173 $1,617,000
9 NQM27 $1,565,604 0 $0 $1,598,604 $1,584,000
12 NQU27 -$2,170,725 2 $3,133,736 $1,029,011 $1,518,000
15 NQZ27 -$2,922,777 1 $4,408,918 $1,486,142 $1,782,000
18 NQH28 -$382,536 1 $3,443,101 $3,093,565 $1,749,000
21 NQM28 $2,480,240 1 $1,725,828 $4,206,068 $1,914,000
24 NQU28 -$2,765,910 1 $3,121,046 $454,136 $1,815,000
27 NQZ28 $1,100,967 1 $2,052,439 $3,153,406 $1,947,000
30 NQH29 -$2,518,474 2 $4,334,080 $1,815,606 $2,112,000
33 NQM29 -$2,068,104 1 $5,742,599 $3,674,496 $2,244,000
36 NQU29 $2,617,049 0 $0 $2,617,049 $2,343,000
39 NQZ29 $1,685,542 2 $1,490,422 $3,175,964 $2,706,000
42 NQH30 -$5,836,599 2 $7,098,151 $1,756,551 $2,211,000
45 NQM30 -$8,270,422 3 $8,105,422 $0 $2,046,000
48 NQU30 -$1,684,043 1 $4,995,075 $3,574,048 $1,782,984
51 NQZ30 -$7,922,470 1 $7,987,486 $0 $1,848,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.6 llamados y reposiciones totales esperadas de $69,364,033, retiros esperados de $38,774,560 y un aporte neto esperado de $32,007,916. El gerente debe reservar liquidez para estos flujos: la cobertura no cuesta prima, pero sí exige caja cuando el mercado sube.


9. Roll-over trimestral (cobertura en corto)

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)")
Efecto del roll-over sobre la rentabilidad (todas las simulaciones)
Indicador Sin cobertura Con cobertura y roll-over
Rendimiento anual promedio 27.72% 15.80%
Desviación del rendimiento anual 14.31% 11.92%
Percentil 5% del rendimiento anual 5.99% -2.30%
Probabilidad de pérdida a 4 años 1.57% 7.44%
Rendimiento anual trayectoria representativa 20.61% 6.88%
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%"))

En cada roll la posición se cierra al precio del índice y se reabre a \(F=S\,e^{c\,\tau}\), con carry \(c=\) 3.904% y \(\tau\) la vida restante del contrato. Mientras \(c>0\), el futuro se vende con prima (contango) y esa prima converge a cero: el vendedor la gana trimestre a trimestre. Por eso el portafolio cubierto rinde aproximadamente la tasa libre de riesgo más el alfa de las acciones, y no cero. El costo de la cobertura es renunciar a la prima de riesgo del mercado. Trayectorias con valor cubierto \(\le 0\) al final de los 4 años: 2; en algún momento del período: 2 (se tratan como ruina, rendimiento −100%).


10. Valor esperado de la cobertura con la tasa libre de riesgo

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")
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,441,820 y el cubierto $66,990,393, frente a $42,627,861 que daría la tasa libre de riesgo. La desviación estándar del valor final baja de $48,066,154 a $28,475,362. 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.


11. Rendimiento esperado mensual bajo tres enfoques y dividendos

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")
Rendimiento y valor esperado del portafolio
Enfoque Rendimiento mensual Valor a 1 mes Rend. mensual equivalente 4 años Valor a 4 años
  1. Sin cobertura (esperado)
2.22% $35,775,429 2.22% $100,441,820
  1. Cubierto con futuros (esperado)
1.12% $35,392,891 1.36% $66,990,393
3a. Cuantil 5% del retorno — sin cobertura -7.72% $32,298,596 — $44,176,545
3b. Cuantil 1% del retorno — sin cobertura -11.28% $31,050,521 — $32,608,006
3c. Cuantil 5% del retorno — cubierto -6.05% $32,882,401 — $31,891,509
3d. Cuantil 1% del retorno — cubierto -8.81% $31,914,868 — $22,768,347

Dividendos

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")
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 los cálculos usan precios ajustados: el retorno total = variación del precio + dividendo reinvertido. El portafolio recibe cerca de **\(302,516** al año (**0.86%** del capital), equivalente a **0.072%** mensual adicional al rendimiento por precio. En la cobertura, el dueño de las acciones **conserva los dividendos**, mientras que el futuro sobre el índice ya descuenta el dividendo esperado del NDX en su precio (\)F=S,e^{(r-q)T}$): a mayor \(q\), menor precio del futuro y menor la ganancia por convergencia de la posición corta.


12. Sensibilidad a la beta: β = 0.6 y β = 1.5

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")
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,320 $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"))
  • β = 0.6: el portafolio solo traslada el 60% del movimiento del índice; requiere 37 contratos. Cubrirlo con más contratos lo dejaría sobrecubierto (ganaría si el mercado cae y perdería si sube).
  • β = 1.5: amplifica 1.5 veces los movimientos del índice; requiere 92 contratos, 2.5 veces más que con β = 0.6, y mayores márgenes y llamados al margen.
  • Por qué más beta exige más cobertura: el número de contratos es proporcional a la beta. Una caída del 10% del Nasdaq-100 le resta 15% a un portafolio con β = 1.5 pero solo 10% a cada contrato vendido, así que se necesitan más contratos para igualar la pérdida.

Conclusiones

  1. El portafolio óptimo (AAPL 69.5%, AMGN 30.5%, PEP 0.0%) combina crecimiento (AAPL) con dos defensivas de baja correlación, logrando un Sharpe de 0.83.
  2. Su VaR mensual al 95% es $2,752,653 y al 99% $4,070,157; cerca del 49% de ese riesgo es sistemático y cubrible con futuros.
  3. Con β = 0.78 se venden 48 contratos NQ. La cobertura con roll-over trimestral reduce la desviación del valor final de $48,066,154 a $28,475,362, a cambio de acercar el rendimiento esperado a la tasa libre de riesgo.
  4. La cobertura no tiene prima, pero exige liquidez para llamados al margen: hay que reservar caja para cuando el mercado sube.
  5. Cuanto mayor la beta, más contratos y más capital de margen se necesitan.

Limitaciones: retornos normales e independientes (subestiman colas gruesas), parámetros históricos constantes, base del futuro modelada con costo de acarreo constante, liquidación mensual en lugar de diaria y vencimientos al cierre de mes. Con deriva histórica, el rendimiento simulado incluye el alfa histórico de las acciones; la deriva “neutral” lo elimina.