Objetivo. Construir un portafolio óptimo media-varianza con tres acciones del S&P 500 y diseñar una estrategia de cobertura mediante futuros E-mini S&P 500 (CME), evaluando llamados al margen, roll-over trimestral y sensibilidades por beta.
Parámetros. Capital inicial USD 20.000.000 · Inicio 30-abr-2026 · Horizonte 4 años · Histórico 10 años (30-abr-2016 a 30-abr-2026, mensual).
Acciones seleccionadas. Cummins (CMI), Hershey (HSY), Cadence Design Systems (CDNS) — tres sectores no correlacionados.
| Ticker | Empresa | Sector | Subsector | Razón de inclusión |
|---|---|---|---|---|
| CMI | Cummins Inc. | Industriales | Motores diésel y energía limpia | Exposición cíclica industrial + transición energética (hidrógeno) |
| HSY | The Hershey Company | Consumo Básico | Confitería y snacks | Defensiva — baja correlación con ciclo económico |
| CDNS | Cadence Design Systems | Tecnología | Software EDA para semiconductores | Crecimiento estructural del semiconductor (sin ser mega-cap obvia) |
Descripción general. Cummins Inc. es una corporación industrial estadounidense fundada en 1919 con sede en Columbus, Indiana. Diseña, fabrica, distribuye y mantiene motores de combustión interna (principalmente diésel y gas natural), sistemas de filtración, sistemas eléctricos de potencia y, más recientemente, soluciones de energía baja en carbono mediante su unidad Accelera by Cummins (electrólisis, celdas de combustible de hidrógeno, baterías y trenes motrices eléctricos).
Sector económico y subsegmento. Industriales / Maquinaria — Construcción, Minería y Vehículos Comerciales (GICS 20106020). Es la principal proveedora global de motores para camiones medianos y pesados (Class 8) en Norteamérica.
Principales fuentes de ingresos. Cummins reporta cinco segmentos: (1) Engine — motores para camiones medianos, pesados, y aplicaciones industriales; (2) Distribution — repuestos y servicio postventa a través de su red global; (3) Components — sistemas de filtración, postratamiento de emisiones, turbocargadores; (4) Power Systems — generadores de energía estacionaria y motores industriales de gran tamaño; (5) Accelera — tecnologías de cero emisiones (en fase de inversión, aún no rentable). Los ingresos de 2024 superaron los USD 34.000 millones, con Engine y Distribution aportando el mayor margen operativo.
Evolución reciente del precio. Tras el desplome de 2022 (ciclo de subida de tasas + temores recesivos), CMI inició una recuperación sostenida en 2023–2025, impulsada por (i) la fortaleza del ciclo de reemplazo de flotas Class 8 en EE.UU.; (ii) el cumplimiento normativo EPA 2027 que adelantará pre-buy de motores en 2026; (iii) el desempeño favorable de Power Systems gracias a la demanda de generación distribuida para data centers de IA. La acción ha mostrado mayor sensibilidad al ciclo industrial y a noticias macroeconómicas (PMI manufacturero, tasas de interés).
Situación financiera. Cummins mantiene grado de inversión (Moody’s A2, S&P A) con flujo de caja libre robusto (>USD 2.000 M en años normales), margen operativo en torno al 12–14%, ROE histórico cercano al 25% y un programa de dividendo creciente con más de 15 años de aumentos consecutivos (yield ~2.5%). La deuda neta es manejable y la compañía continúa recomprando acciones.
Expectativas a 12 meses. El consenso de analistas (Refinitiv, Yahoo Finance) sitúa el precio objetivo medio con upside moderado; los catalizadores principales son el pre-buy regulatorio EPA 2027, la rentabilización progresiva de Accelera, y el ciclo de capex de generación eléctrica. Los riesgos incluyen una recesión que retrase el reemplazo de flotas y la presión de costos de la transición energética.
Justificación de inclusión. CMI aporta al portafolio (i) exposición cíclica industrial (β > 1 esperada), (ii) opcionalidad sobre la transición energética vía Accelera, (iii) flujo de caja y dividendo estable que amortigua la volatilidad, y (iv) baja correlación esperada con HSY (consumo defensivo) y CDNS (tecnología). Es la pata “valor cíclico” del portafolio.
Descripción general. The Hershey Company es la principal compañía de confitería y snacks salados de Estados Unidos, fundada en 1894 por Milton S. Hershey y con sede en Hershey, Pensilvania. Es propietaria de marcas icónicas como Hershey’s, Reese’s, Kit Kat (licencia en EE.UU.), Twizzlers, Jolly Rancher, Ice Breakers, así como las marcas de snacks salados SkinnyPop y Dot’s Pretzels, adquiridas en años recientes para diversificar su portafolio.
Sector económico y subsegmento. Consumo Básico / Alimentos Empacados (GICS 30202030). Es uno de los componentes históricamente más defensivos del S&P 500, junto con Procter & Gamble, Coca-Cola y Pepsico.
Principales fuentes de ingresos. (1) North America Confectionery (>80% de los ingresos) — chocolates, gomas y dulces; (2) North America Salty Snacks — pretzels y popcorn premium; (3) International — operaciones en México, Brasil, India y otros mercados emergentes. La concentración geográfica en Norteamérica es alta (~90%), lo que limita la exposición cambiaria pero también restringe vías de crecimiento.
Evolución reciente del precio. Tras un período de bajo desempeño relativo en 2023–2024, marcado por la escalada histórica del precio del cacao (que se triplicó por las malas cosechas en África Occidental), HSY ha mostrado señales de estabilización en 2025–2026 a medida que: (i) la compañía implementa subidas de precios y reducción de tamaño de paquetes (shrinkflation); (ii) los precios del cacao se moderan parcialmente con mejores cosechas; (iii) la demanda en confitería se mantiene resiliente. La acción cotiza con un descuento histórico frente a su múltiplo P/E mediano de los últimos 10 años.
Situación financiera. Hershey mantiene calificación A1 / A (Moody’s / S&P) con margen bruto histórico cercano al 45% (presionado al ~42% por costos del cacao), margen operativo de ~22%, ROE superior al 50% (apalancado pero estable) y más de 95 años pagando dividendos ininterrumpidos, con un yield actual de ~2.7%. La generación de caja libre supera los USD 1.500 M anuales.
Expectativas a 12 meses. El consenso espera una recuperación gradual de márgenes a medida que se normalizan los costos del cacao en 2026–2027 y la compañía termine de implementar el traslado de costos. Catalizadores: éxito de las marcas de snacks adquiridos, expansión internacional, recuperación de márgenes. Riesgos: persistencia de costos altos del cacao, competencia de marcas privadas (private-label), tendencias de consumo hacia productos saludables.
Justificación de inclusión. HSY aporta al portafolio (i) carácter defensivo con β < 1 históricamente, (ii) dividendo estable y creciente que añade ~2.7% de retorno anual; (iii) baja correlación con CMI (cíclico) y muy baja correlación con CDNS (tecnología), maximizando el beneficio de diversificación; (iv) valuación atractiva tras la corrección por costos del cacao. Es la pata “defensiva con yield” del portafolio.
Descripción general. Cadence Design Systems Inc. es una compañía global de software y servicios de automatización de diseño electrónico (EDA) fundada en 1988 con sede en San José, California. Su software es esencial para el diseño, verificación y fabricación de semiconductores, sistemas integrados y, más recientemente, para la simulación y verificación basada en IA. Sus principales clientes son los fabricantes de chips (TSMC, Intel, Samsung, AMD, NVIDIA, Qualcomm, Apple) y empresas de hardware que diseñan sus propios chips (incluyendo hyperscalers como Google, Microsoft, Amazon).
Sector económico y subsegmento. Tecnología de la Información / Software de Aplicaciones (GICS 45103010). Es uno de los tres principales jugadores del oligopolio EDA junto con Synopsys y Siemens EDA (Mentor Graphics).
Principales fuentes de ingresos. Cadence reporta cinco categorías de productos: (1) Custom IC Design & Simulation — herramientas para circuitos analógicos/mixtos; (2) Digital IC Design & Signoff — flujo de diseño digital de avanzada (líder con sus tools Innovus y Tempus); (3) Functional Verification — emulación, simulación y prototipado FPGA, incluyendo la familia Palladium/Protium (>30% de los ingresos y mayor crecimiento); (4) IP (Intellectual Property) — bloques pre-diseñados (procesadores Tensilica, controladores DDR, PCIe, USB); (5) System Design & Analysis — software multi-físico (térmico, electromagnético, fluidos), donde Cadence ha invertido fuertemente en CFD y AI-driven simulation.
Evolución reciente del precio. CDNS ha sido una de las acciones más destacadas del S&P 500 en los últimos 5 años, impulsada por: (i) el boom estructural del diseño de chips de IA, donde el costo y complejidad del tape-out de un chip de 3 nm supera USD 500 M, requiriendo herramientas EDA cada vez más sofisticadas; (ii) el modelo de suscripción recurrente que ya representa >85% de los ingresos; (iii) acuerdos plurianuales con hyperscalers para el desarrollo de chips de IA personalizados; (iv) la integración de IA generativa en sus propias herramientas (Cadence Cerebrus, JedAI). Posibles correcciones recientes obedecen a (a) ajustes de valuación tras múltiples expansiones, (b) restricciones de exportación a China.
Situación financiera. Cadence presenta crecimiento de ingresos consistentemente >10% anual, margen operativo non-GAAP cercano al 42–45%, caja neta positiva (más caja que deuda) y un programa de recompra de acciones. No paga dividendos: prioriza la reinversión en R&D (~25% de los ingresos) y adquisiciones estratégicas. ROE histórico >25% (sin apalancamiento financiero significativo).
Expectativas a 12 meses. El consenso de analistas mantiene precios objetivo alcistas, sustentados en (i) la guía de la propia compañía con crecimiento de ingresos de doble dígito; (ii) el backlog que se mantiene en máximos históricos; (iii) el ramp-up de Palladium Z3/Z2 y nuevas herramientas con IA. Riesgos: ralentización del capex de semiconductores, mayor escrutinio antimonopolio, restricciones de exportación a China (~12% de los ingresos).
Justificación de inclusión. CDNS aporta al portafolio (i) exposición a crecimiento secular del semiconductor y de la IA, sin la concentración de riesgo idiosincrático de NVIDIA o las megacaps; (ii) β moderada-alta que aumenta el retorno esperado del portafolio (a costo de mayor sensibilidad al mercado, lo que justifica la cobertura con futuros); (iii) complementariedad con CMI (cíclico industrial) y HSY (defensivo); (iv) calidad financiera (caja neta positiva, márgenes elevados, modelo recurrente). Es la pata “crecimiento secular” del portafolio.
⚠ Recordatorio del enunciado: “El análisis debe conectar la información fundamental con la decisión de inversión, evitando una simple descripción de la empresa.”
Frecuencia: precios mensuales ajustados (cierre
ajustado por dividendos y splits). Ventana: 30-abr-2016
a 30-abr-2026 (10 años, dentro del rango 6–10 años permitido).
Fuente: Yahoo Finance vía
quantmod::getSymbols.
tickers <- c("CMI", "HSY", "CDNS")
benchmark <- "^GSPC" # S&P 500 (mercado para CAPM)
rf_proxy <- "^FVX" # CBOE 5Y Treasury Yield (rango 3-5y, según enunciado)
start_date <- as.Date("2016-04-30")
end_date <- as.Date("2026-04-30")
get_adj <- function(sym) {
x <- quantmod::getSymbols(sym, from = start_date, to = end_date,
auto.assign = FALSE, src = "yahoo")
Ad(x)
}
prices_daily <- do.call(merge, lapply(c(tickers, benchmark), get_adj))
colnames(prices_daily) <- c(tickers, "SPX")
# Convertir a fin de mes (último precio del mes)
prices_monthly <- apply.monthly(prices_daily, last)
prices_monthly <- na.omit(prices_monthly)prices_norm <- sweep(prices_monthly, 2, as.numeric(prices_monthly[1, ]), "/") * 100
df_long <- data.frame(
Fecha = as.Date(index(prices_norm)),
coredata(prices_norm)
) |>
pivot_longer(-Fecha, names_to = "Activo", values_to = "Indice")
p <- ggplot(df_long, aes(x = Fecha, y = Indice, color = Activo)) +
geom_line(linewidth = 0.7) +
scale_y_continuous(labels = comma) +
labs(title = "Evolución normalizada (Base 100 = abr-2016)",
x = NULL, y = "Índice", color = NULL) +
theme_minimal(base_size = 12)
plotly::ggplotly(p)Se utilizan retornos logarítmicos mensuales \(r_t = \ln(P_t / P_{t-1})\).
Anualización: \[ \mu_{anual} = 12 \cdot \mu_{mensual} \qquad \sigma_{anual} = \sqrt{12} \cdot \sigma_{mensual} \]
returns_m <- na.omit(Return.calculate(prices_monthly, method = "log"))
mu_m <- colMeans(returns_m[, tickers])
sd_m <- apply(returns_m[, tickers], 2, sd)
mu_a <- mu_m * 12
sd_a <- sd_m * sqrt(12)
stats <- data.frame(
Acción = tickers,
`Retorno mensual` = scales::percent(mu_m, accuracy = 0.01),
`Retorno anual` = scales::percent(mu_a, accuracy = 0.01),
`Volatilidad mensual` = scales::percent(sd_m, accuracy = 0.01),
`Volatilidad anual` = scales::percent(sd_a, accuracy = 0.01),
check.names = FALSE
)
ktab(stats, "Estadísticas descriptivas (10 años de retornos mensuales)")| Acción | Retorno mensual | Retorno anual | Volatilidad mensual | Volatilidad anual | |
|---|---|---|---|---|---|
| CMI | CMI | 1.66% | 19.91% | 7.37% | 25.52% |
| HSY | HSY | 0.79% | 9.52% | 6.07% | 21.04% |
| CDNS | CDNS | 2.18% | 26.13% | 7.83% | 27.11% |
Sigma_m <- cov(returns_m[, tickers])
Sigma_a <- Sigma_m * 12
Corr <- cor(returns_m[, tickers])
ktab(round(Sigma_a, 6), "Matriz de varianzas-covarianzas anualizada")| CMI | HSY | CDNS | |
|---|---|---|---|
| CMI | 0.065139 | 0.00538 | 0.018261 |
| HSY | 0.005380 | 0.04425 | 0.002580 |
| CDNS | 0.018261 | 0.00258 | 0.073517 |
| CMI | HSY | CDNS | |
|---|---|---|---|
| CMI | 1.000 | 0.100 | 0.264 |
| HSY | 0.100 | 1.000 | 0.045 |
| CDNS | 0.264 | 0.045 | 1.000 |
corrplot(Corr, method = "color", addCoef.col = "black", tl.col = "black",
number.cex = 1.1, type = "upper", diag = FALSE,
col = colorRampPalette(c("#d73027", "white", "#1a9850"))(200))Criterio: maximización del Sharpe Ratio (portafolio de tangencia), sin venta en corto y plenamente invertido (\(\sum w_i = 1, \; w_i \ge 0\)).
# Tasa libre de riesgo (placeholder; se refina en sección 10 con ^FVX en la fecha inicial)
rf_a <- 0.045
mu_vec <- as.numeric(mu_a); names(mu_vec) <- tickers
n <- length(mu_vec)
ones <- rep(1, n)
excess <- mu_vec - rf_a
# Solución analítica (permite cortos)
Sigma_inv <- solve(Sigma_a)
w_uncon <- as.numeric(Sigma_inv %*% excess)
w_uncon <- w_uncon / sum(w_uncon)
# Si los pesos analíticos tienen alguno negativo, resolver QP con restricción w >= 0
# Reformulación clásica para max Sharpe sin cortos:
# min y' Σ y s.a. (μ - rf)' y = 1, y >= 0; luego w = y / 1' y
if (any(w_uncon < 0)) {
Dmat <- 2 * Sigma_a
dvec <- rep(0, n)
Amat <- cbind(excess, diag(n))
bvec <- c(1, rep(0, n))
sol <- quadprog::solve.QP(Dmat, dvec, Amat, bvec, meq = 1)
y_opt <- sol$solution
w_opt <- y_opt / sum(y_opt)
metodo <- "QP con no-shorting"
} else {
w_opt <- w_uncon
metodo <- "Solución analítica (todos los pesos positivos)"
}
names(w_opt) <- tickers
# Métricas del portafolio óptimo
capital <- 20e6
ret_p <- as.numeric(t(w_opt) %*% mu_vec)
sd_p <- as.numeric(sqrt(t(w_opt) %*% Sigma_a %*% w_opt))
sharpe <- (ret_p - rf_a) / sd_p
montos <- w_opt * capital
resumen_pf <- data.frame(
Acción = tickers,
Peso = scales::percent(w_opt, accuracy = 0.01),
Monto = scales::dollar(montos, accuracy = 1)
)
ktab(resumen_pf, sprintf("Portafolio óptimo (tangencia) — método: %s", metodo))| Acción | Peso | Monto | |
|---|---|---|---|
| CMI | CMI | 32.50% | $6,500,959 |
| HSY | HSY | 16.16% | $3,231,767 |
| CDNS | CDNS | 51.34% | $10,267,274 |
resumen_metr <- data.frame(
Métrica = c("Retorno esperado anual", "Volatilidad anual", "Sharpe Ratio (rf = 4.5%)"),
Valor = c(scales::percent(ret_p, 0.01),
scales::percent(sd_p, 0.01),
round(sharpe, 3))
)
ktab(resumen_metr, "Métricas del portafolio óptimo")| Métrica | Valor |
|---|---|
| Retorno esperado anual | 21.42% |
| Volatilidad anual | 18.57% |
| Sharpe Ratio (rf = 4.5%) | 0.911 |
# Frontera eficiente: target returns vs vol
target_rets <- seq(min(mu_vec), max(mu_vec), length.out = 50)
front <- map_dfr(target_rets, function(tr) {
Dmat <- 2 * Sigma_a
dvec <- rep(0, n)
# Restricciones: 1'w = 1, μ'w = tr, w >= 0
Amat <- cbind(rep(1, n), mu_vec, diag(n))
bvec <- c(1, tr, rep(0, n))
sol <- tryCatch(quadprog::solve.QP(Dmat, dvec, Amat, bvec, meq = 2),
error = function(e) NULL)
if (is.null(sol)) return(NULL)
w <- sol$solution
data.frame(target = tr, vol = sqrt(t(w) %*% Sigma_a %*% w)[1, 1])
})
p_front <- ggplot(front, aes(x = vol, y = target)) +
geom_line(linewidth = 1, color = "#1f77b4") +
geom_point(data = data.frame(vol = sd_p, target = ret_p),
aes(x = vol, y = target), color = "red", size = 4) +
geom_text(data = data.frame(vol = sd_p, target = ret_p, lab = "Tangencia"),
aes(label = lab), nudge_y = 0.01, color = "red") +
scale_x_continuous(labels = percent) +
scale_y_continuous(labels = percent) +
labs(x = "Volatilidad anual", y = "Retorno esperado anual",
title = "Frontera eficiente (sin venta en corto)") +
theme_minimal(base_size = 12)
plotly::ggplotly(p_front)VaR mensual del portafolio bajo dos enfoques: paramétrico (asumiendo normalidad) e histórico (sin asumir distribución).
Fórmulas:
VaR paramétrico (normal): \[ \text{VaR}_{\alpha} = -\left( \mu_p - z_{\alpha} \cdot \sigma_p \right) \cdot V_p \] donde \(z_{0.01} = 2.326\), \(z_{0.05} = 1.645\).
VaR histórico: \(\alpha\)-percentil empírico de los retornos mensuales del portafolio.
# Serie mensual de retornos del portafolio óptimo
rp_m <- as.numeric(returns_m[, tickers] %*% w_opt)
mu_p_m <- mean(rp_m)
sd_p_m <- sd(rp_m)
# --- VaR paramétrico (normal) ---
z99 <- qnorm(0.99); z95 <- qnorm(0.95)
VaR99_param_pct <- -(mu_p_m - z99 * sd_p_m)
VaR95_param_pct <- -(mu_p_m - z95 * sd_p_m)
# --- VaR histórico ---
VaR99_hist_pct <- -as.numeric(quantile(rp_m, 0.01))
VaR95_hist_pct <- -as.numeric(quantile(rp_m, 0.05))
VaR_tab <- data.frame(
Método = c("Paramétrico (normal)", "Histórico"),
`VaR 99% (%)` = scales::percent(c(VaR99_param_pct, VaR99_hist_pct), 0.01),
`VaR 95% (%)` = scales::percent(c(VaR95_param_pct, VaR95_hist_pct), 0.01),
`VaR 99% (USD)` = scales::dollar(c(VaR99_param_pct, VaR99_hist_pct) * capital, accuracy = 1),
`VaR 95% (USD)` = scales::dollar(c(VaR95_param_pct, VaR95_hist_pct) * capital, accuracy = 1),
check.names = FALSE
)
ktab(VaR_tab, "VaR mensual del portafolio óptimo")| Método | VaR 99% (%) | VaR 95% (%) | VaR 99% (USD) | VaR 95% (USD) |
|---|---|---|---|---|
| Paramétrico (normal) | 10.69% | 7.03% | $2,137,659 | $1,406,835 |
| Histórico | 9.56% | 6.40% | $1,912,610 | $1,280,033 |
df_rp <- data.frame(retorno = rp_m)
p_var <- ggplot(df_rp, aes(x = retorno)) +
geom_histogram(aes(y = after_stat(density)), bins = 30,
fill = "#1f77b4", color = "white", alpha = 0.8) +
stat_function(fun = dnorm, args = list(mean = mu_p_m, sd = sd_p_m),
color = "#ff7f0e", linewidth = 1) +
geom_vline(xintercept = -VaR99_param_pct, color = "red", linetype = "dashed") +
geom_vline(xintercept = -VaR95_param_pct, color = "orange", linetype = "dashed") +
annotate("text", x = -VaR99_param_pct, y = 0, label = "VaR 99%",
vjust = -0.5, hjust = 1.05, color = "red", size = 3.5) +
annotate("text", x = -VaR95_param_pct, y = 0, label = "VaR 95%",
vjust = -0.5, hjust = 1.05, color = "orange", size = 3.5) +
scale_x_continuous(labels = percent) +
labs(title = "Distribución empírica de retornos mensuales del portafolio",
subtitle = "Curva naranja: ajuste normal · Líneas: VaR paramétrico",
x = "Retorno mensual", y = "Densidad") +
theme_minimal(base_size = 12)
plotly::ggplotly(p_var)Interpretación. Con 99% de confianza, las pérdidas mensuales del portafolio no superarán aproximadamente $2,137,659 (paramétrico). Esta es la magnitud que la cobertura con futuros busca atenuar; el monto cubierto con futuros sobre el índice debe ser consistente con el componente sistémico de este VaR (la fracción atribuible a β·V·σ_m).
Fórmulas:
rm_m <- as.numeric(returns_m[, "SPX"])
mu_m_m <- mean(rm_m)
# Beta de cada acción + R² + alpha (para análisis CAPM)
beta_stats <- lapply(tickers, function(t) {
ri <- as.numeric(returns_m[, t])
fit <- lm(ri ~ rm_m)
data.frame(
Acción = t,
Alpha_mensual = coef(fit)[["(Intercept)"]],
Beta = coef(fit)[["rm_m"]],
R2 = summary(fit)$r.squared
)
}) |> bind_rows()
# Beta ponderada del portafolio
beta_p <- sum(w_opt * beta_stats$Beta)
# Retornos esperados según CAPM (anualizados)
mu_m_a <- mu_m_m * 12
beta_stats <- beta_stats |>
mutate(
`E[r] CAPM anual` = rf_a + Beta * (mu_m_a - rf_a),
`Retorno hist. anual` = mu_a[Acción]
)
# Tabla
beta_tab <- data.frame(
Acción = c(tickers, "Portafolio"),
Peso = c(scales::percent(w_opt, 0.01), "100%"),
Beta = round(c(beta_stats$Beta, beta_p), 4),
`R²` = c(round(beta_stats$R2, 3), NA),
`E[r] CAPM anual` = c(scales::percent(beta_stats$`E[r] CAPM anual`, 0.01),
scales::percent(rf_a + beta_p * (mu_m_a - rf_a), 0.01)),
check.names = FALSE
)
ktab(beta_tab, "Betas, R² y retornos esperados según CAPM")| Acción | Peso | Beta | R² | E[r] CAPM anual | |
|---|---|---|---|---|---|
| CMI | CMI | 32.50% | 1.1310 | 0.461 | 13.38% |
| HSY | HSY | 16.16% | 0.2301 | 0.028 | 6.31% |
| CDNS | CDNS | 51.34% | 1.0290 | 0.338 | 12.58% |
| Portafolio | 100% | 0.9331 | NA | 11.82% |
df_scatter <- data.frame(
rm = rm_m,
CMI = as.numeric(returns_m[, "CMI"]),
HSY = as.numeric(returns_m[, "HSY"]),
CDNS = as.numeric(returns_m[, "CDNS"])
) |>
pivot_longer(-rm, names_to = "Acción", values_to = "ri")
p_beta <- ggplot(df_scatter, aes(x = rm, y = ri, color = Acción)) +
geom_point(alpha = 0.5, size = 1.5) +
geom_smooth(method = "lm", se = FALSE, linewidth = 0.8) +
scale_x_continuous(labels = percent) +
scale_y_continuous(labels = percent) +
facet_wrap(~Acción, ncol = 3) +
labs(title = "Regresión CAPM: retorno acción vs retorno S&P 500",
x = "Retorno mensual S&P 500", y = "Retorno mensual acción") +
theme_minimal(base_size = 11) +
theme(legend.position = "none")
plotly::ggplotly(p_beta)Interpretación. β del portafolio = 0.9331. Esto significa que ante un movimiento del 1% en el S&P 500, se espera que el portafolio se mueva aproximadamente 93.31% en la misma dirección. Esta β es el insumo principal para calcular el número de contratos de cobertura.
| Item | Valor |
|---|---|
| Activo subyacente | Índice S&P 500 |
| Bolsa | CME Group (Globex) |
| Multiplicador | USD 50 por punto |
| Tamaño del contrato (a F₀≈5.000) | ≈ USD 250.000 (USD 50 × 5.000) |
| Tick mínimo | 0.25 puntos |
| Valor del tick | USD 12.50 |
| Margen inicial overnight | USD 26.500 |
| Margen de mantenimiento | USD 24.000 |
| Liquidación | Efectivo (cash-settled) |
| Mark-to-market | Diario (académicamente: mensual, según enunciado) |
| Vencimientos disponibles | Trimestral: H (Mar), M (Jun), U (Sep), Z (Dic) |
| Día de vencimiento | 3er viernes del mes de vencimiento |
Fuente de márgenes. Valores recientes consultados en CME Group — E-mini S&P 500 Margins, corroborados con TradeStation Futures Margin Requirements (abril 2026). Los márgenes pueden variar diariamente según volatilidad del mercado; se utilizan los valores vigentes a 30-abr-2026 para el ejercicio.
# Descargar precio del E-mini S&P 500 continuo (ES=F en Yahoo Finance)
# Para el contrato específico ESM26 (jun-2026), usar quandl/CME directamente.
fut <- tryCatch(
quantmod::getSymbols("ES=F", from = end_date - 30, to = end_date + 1,
auto.assign = FALSE, src = "yahoo"),
error = function(e) NULL
)
if (!is.null(fut) && nrow(fut) > 0) {
F0 <- as.numeric(last(Cl(fut)))
fecha_F0 <- as.Date(last(index(fut)))
fuente_F0 <- "ES=F (continuous E-mini S&P 500, Yahoo Finance)"
} else {
# Fallback: usar el cierre del S&P 500 spot como aproximación (cost-of-carry ≈ 0)
F0 <- as.numeric(last(prices_monthly[, "SPX"]))
fecha_F0 <- as.Date(last(index(prices_monthly)))
fuente_F0 <- "S&P 500 spot (^GSPC) — proxy ante fallo en ES=F"
}
cat(sprintf("Precio del futuro F₀ = %.2f puntos\nFecha: %s\nFuente: %s\n",
F0, format(fecha_F0), fuente_F0))## Precio del futuro F₀ = 7243.75 puntos
## Fecha: 2026-04-30
## Fuente: ES=F (continuous E-mini S&P 500, Yahoo Finance)
Fórmula (cobertura por beta): \[ N^* = \beta_p \cdot \dfrac{V_p}{F_0 \cdot m} \]
donde \(V_p = 20.000.000\) USD, \(F_0\) = precio del futuro y \(m = 50\) (multiplicador del E-mini S&P 500).
mult <- 50
N_star_exact <- beta_p * capital / (F0 * mult)
N_int_floor <- floor(N_star_exact)
N_int_ceil <- ceiling(N_star_exact)
N_int_round <- round(N_star_exact)
# Cobertura efectiva con cada redondeo
beta_cubierta_floor <- N_int_floor * F0 * mult / capital
beta_cubierta_ceil <- N_int_ceil * F0 * mult / capital
beta_cubierta_round <- N_int_round * F0 * mult / capital
n_tab <- data.frame(
Métrica = c("β del portafolio",
"Valor del contrato (F₀ × m)",
"N* exacto",
"N* redondeado al entero más cercano",
"N* redondeo hacia arriba (sobre-cobertura)",
"N* redondeo hacia abajo (sub-cobertura)",
"β efectiva cubierta (round)",
"β efectiva cubierta (ceil)",
"β efectiva cubierta (floor)",
"β residual (round)"),
Valor = c(round(beta_p, 4),
scales::dollar(F0 * mult, accuracy = 1),
round(N_star_exact, 4),
N_int_round,
N_int_ceil,
N_int_floor,
round(beta_cubierta_round, 4),
round(beta_cubierta_ceil, 4),
round(beta_cubierta_floor, 4),
round(beta_p - beta_cubierta_round, 4))
)
ktab(n_tab, "Número óptimo de contratos de futuros E-mini S&P 500")| Métrica | Valor |
|---|---|
| β del portafolio | 0.9331 |
| Valor del contrato (F₀ × m) | $362,188 |
| N* exacto | 51.5246 |
| N* redondeado al entero más cercano | 52 |
| N* redondeo hacia arriba (sobre-cobertura) | 52 |
| N* redondeo hacia abajo (sub-cobertura) | 51 |
| β efectiva cubierta (round) | 0.9417 |
| β efectiva cubierta (ceil) | 0.9417 |
| β efectiva cubierta (floor) | 0.9236 |
| β residual (round) | -0.0086 |
Justificación del redondeo. Como los contratos son discretos, hay que elegir entre tres alternativas:
| Criterio | Resultado | Implicación |
|---|---|---|
Hacia arriba (ceil) |
52 | Sobre-cobertura: β cubierta > β del portafolio. Mayor protección si el mercado cae, pero introduce una exposición negativa neta al mercado. |
Hacia abajo (floor) |
51 | Sub-cobertura: queda β residual positiva = 0.0095. El portafolio sigue parcialmente expuesto a movimientos del índice. |
Más cercano (round) |
52 | Decisión adoptada: minimiza la diferencia absoluta entre β cubierta y β del portafolio (β residual = -0.0086). |
Para un portafolio de cobertura “neutral” lo apropiado es redondear al entero más cercano, ya que ni la sobre-cobertura ni la sub-cobertura son objetivos del ejercicio. Si el gerente tuviera una visión bajista marcada, justificaría sobre-cobertura; si tuviera visión alcista pero requiere protección mínima, justificaría sub-cobertura.
TODO. Discutir posición corta vs larga:
Mecánica del margen (CME):
Escenario de evaluación. Como la inversión inicia el 30-abr-2026 y el horizonte es de 4 años (abril 2030), no se dispone de precios futuros reales. Para fines didácticos se utiliza la trayectoria histórica de los últimos 48 meses del E-mini S&P 500 como escenario representativo (2022-04 a 2026-04), lo que cubre el bear market de 2022 y la recuperación 2023–2025.
# Trayectoria histórica de 48 meses de ES=F (proxy del horizonte 4 años)
fut_hist <- quantmod::getSymbols(
"ES=F",
from = end_date - 365 * 5, # margen extra para asegurar 48 cierres mensuales
to = end_date + 1,
auto.assign = FALSE,
src = "yahoo"
)
fut_monthly_hist <- apply.monthly(Cl(fut_hist), last) |> na.omit()
fut_path <- tail(fut_monthly_hist, 49) # 49 puntos -> 48 ΔF# Parámetros del contrato (CME Group, abril 2026)
margen_inicial <- 26500 # USD por contrato — CME exchange minimum overnight
margen_mant <- 24000 # USD por contrato
N <- N_contratos
posicion <- "Corta" # cobertura típica para inversionista que ya posee acciones
precios <- as.numeric(fut_path)
fechas <- as.Date(index(fut_path))
# Inicialización
saldo <- N * margen_inicial
flujo <- vector("list", length(precios) - 1)
saldo_path <- numeric(length(precios) - 1)
reposicion_total <- 0
n_calls <- 0
for (i in 2:length(precios)) {
F_ini <- precios[i-1]
F_fin <- precios[i]
delta <- F_fin - F_ini
# P&L según posición
PL_corta <- -N * mult * delta
PL_larga <- N * mult * delta
PL <- if (posicion == "Corta") PL_corta else PL_larga
saldo <- saldo + PL
# Margin call si saldo < margen de mantenimiento
margin_call <- saldo < N * margen_mant
reposicion <- if (margin_call) (N * margen_inicial - saldo) else 0
if (margin_call) {
saldo <- saldo + reposicion
reposicion_total <- reposicion_total + reposicion
n_calls <- n_calls + 1
}
saldo_path[i-1] <- saldo
flujo[[i-1]] <- data.frame(
Fecha = fechas[i],
F_inicial = round(F_ini, 2),
F_final = round(F_fin, 2),
`Delta F` = round(delta, 2),
`P&L corta` = round(PL_corta, 0),
`P&L larga` = round(PL_larga, 0),
Saldo = round(saldo, 0),
`Margin call?` = if (margin_call) "Sí" else "No",
Reposición = round(reposicion, 0),
check.names = FALSE
)
}
flujo_df <- bind_rows(flujo)
# Métricas resumen
PL_acum <- sum(flujo_df$`P&L corta`)
saldo_final <- last(saldo_path)
resumen_marg <- data.frame(
Métrica = c("Posición", "N° de contratos", "Margen inicial total",
"Margen de mantenimiento total", "Meses con margin call",
"Reposición total acumulada", "P&L acumulado (posición tomada)",
"Saldo final del margen"),
Valor = c(posicion,
N,
scales::dollar(N * margen_inicial, accuracy = 1),
scales::dollar(N * margen_mant, accuracy = 1),
n_calls,
scales::dollar(reposicion_total, accuracy = 1),
scales::dollar(PL_acum, accuracy = 1),
scales::dollar(saldo_final, accuracy = 1))
)
ktab(resumen_marg, "Resumen de la cuenta de margen (48 meses)")| Métrica | Valor |
|---|---|
| Posición | Corta |
| N° de contratos | 52 |
| Margen inicial total | $1,378,000 |
| Margen de mantenimiento total | $1,248,000 |
| Meses con margin call | 18 |
| Reposición total acumulada | $8,102,250 |
| P&L acumulado (posición tomada) | -$8,102,250 |
| Saldo final del margen | $1,378,000 |
df_saldo <- data.frame(
Fecha = flujo_df$Fecha,
Saldo = flujo_df$Saldo,
`Margen mant.` = N * margen_mant,
`Margen inicial` = N * margen_inicial,
check.names = FALSE
) |>
pivot_longer(-Fecha, names_to = "Línea", values_to = "USD")
p_saldo <- ggplot(df_saldo, aes(x = Fecha, y = USD, color = Línea)) +
geom_line(linewidth = 0.8) +
scale_y_continuous(labels = dollar) +
scale_color_manual(values = c("Saldo" = "#1f77b4",
"Margen mant." = "red",
"Margen inicial" = "darkgreen")) +
labs(title = "Cuenta de margen mensual — posición corta en E-mini S&P 500",
x = NULL, y = "USD", color = NULL) +
theme_minimal(base_size = 12)
plotly::ggplotly(p_saldo)Mecánica. Como los contratos E-mini S&P 500 vencen trimestralmente (H/M/U/Z, tercer viernes), una cobertura de 4 años requiere 16 contratos consecutivos. En cada vencimiento:
# Parámetros de cost-of-carry
r_carry <- rf_a # tasa libre de riesgo (de sección 10)
q_div <- 0.014 # dividend yield del S&P 500 (~1.4% anual histórico)
carry_q <- exp((r_carry - q_div) * 0.25) - 1 # carry por trimestre
cat(sprintf("Tasa libre de riesgo (r): %s\nDividend yield S&P (q): %s\nCarry trimestral (r-q): %s\n",
scales::percent(r_carry, 0.01),
scales::percent(q_div, 0.01),
scales::percent(carry_q, 0.001)))## Tasa libre de riesgo (r): 4.50%
## Dividend yield S&P (q): 1.40%
## Carry trimestral (r-q): 0.778%
# Nombres de los 16 contratos del programa de cobertura 2026-2030
# Secuencia: M26, U26, Z26, H27, M27, U27, Z27, H28, M28, U28, Z28, H29, M29, U29, Z29, H30
codigos_full <- c("M","U","Z","H") # Jun, Sep, Dic, Mar
contrato_nombres <- vapply(seq_len(16), function(k) {
cc <- codigos_full[((k - 1) %% 4) + 1]
yy <- if (k <= 3) 26
else if (k <= 7) 27
else if (k <= 11) 28
else if (k <= 15) 29
else 30
sprintf("ES%s%02d", cc, yy)
}, character(1))
# Spot path: utilizamos el proxy histórico de 49 puntos mensuales (48 ΔF)
# Cada contrato cubre 3 meses: contratos 1-16, 3 meses cada uno = 48 meses
spot_path <- as.numeric(fut_path)
fechas_path <- as.Date(index(fut_path))
# Para cada contrato k (3 meses): F_open = spot * (1+carry); F_close = spot final (convergencia)
contratos_df <- map_dfr(seq_len(16), function(k) {
start_idx <- (k - 1) * 3 + 1
end_idx <- k * 3 + 1
if (end_idx > length(spot_path)) end_idx <- length(spot_path)
spot_open <- spot_path[start_idx]
spot_close <- spot_path[end_idx]
F_open <- spot_open * exp((r_carry - q_div) * 0.25) # cost-of-carry sobre 3m
F_close <- spot_close # cash settlement = spot al vencimiento
PL_corta <- -N_contratos * mult * (F_close - F_open)
PL_larga <- N_contratos * mult * (F_close - F_open)
# Descomposición del P&L corta:
# PL_corta = N*m*(F_open - F_close) = carry_pickup + componente_direccional
carry_pickup <- N_contratos * mult * (F_open - spot_open) # ganancia "garantizada" del corto
direccional <- -N_contratos * mult * (spot_close - spot_open) # ganancia direccional (neg si mercado sube)
data.frame(
Contrato = contrato_nombres[k],
`Fecha apertura`= fechas_path[start_idx],
`Fecha cierre` = fechas_path[end_idx],
`Spot apertura` = round(spot_open, 2),
`F apertura` = round(F_open, 2),
`F cierre` = round(F_close, 2),
`Cambio F` = round(F_close - F_open, 2),
`Carry pickup` = round(carry_pickup, 0),
`Direccional` = round(direccional, 0),
`P&L corta` = round(PL_corta, 0),
check.names = FALSE
)
})
contratos_df$`P&L acum corta` <- cumsum(contratos_df$`P&L corta`)
DT::datatable(
contratos_df |>
mutate(across(c(`Carry pickup`, `Direccional`, `P&L corta`, `P&L acum corta`),
~ scales::dollar(., accuracy = 1))),
options = list(pageLength = 16, scrollX = TRUE,
columnDefs = list(list(className = "dt-center", targets = "_all"))),
rownames = FALSE,
caption = "Lifecycle de cada uno de los 16 contratos del programa de roll-over"
)En cada vencimiento, el contrato saliente liquida al spot (\(F_{cierre} = S\)) y el entrante abre a \(F_{apertura} = S \cdot e^{(r-q) \cdot 0.25}\). El gap de roll es la base risk realizada:
gaps_df <- contratos_df |>
mutate(
F_apertura_siguiente = lead(`F apertura`, default = NA_real_),
`Gap de roll (pts)` = F_apertura_siguiente - `F cierre`,
`Gap (%)` = `Gap de roll (pts)` / `F cierre`
) |>
filter(!is.na(`Gap de roll (pts)`)) |>
select(Contrato_saliente = Contrato,
`F cierre`,
F_apertura_siguiente,
`Gap de roll (pts)`,
`Gap (%)`)
ktab(
gaps_df |>
mutate(`Gap (%)` = scales::percent(`Gap (%)`, 0.01)),
"Gap de roll-over: diferencia entre F del saliente y F del entrante"
)| Contrato_saliente | F cierre | F_apertura_siguiente | Gap de roll (pts) | Gap (%) |
|---|---|---|---|---|
| ESM26 | 4133.50 | 4165.66 | 32.16 | 0.78% |
| ESU26 | 3883.00 | 3913.21 | 30.21 | 0.78% |
| ESZ26 | 4090.00 | 4121.82 | 31.82 | 0.78% |
| ESH27 | 4188.50 | 4221.09 | 32.59 | 0.78% |
| ESM27 | 4614.50 | 4650.40 | 35.90 | 0.78% |
| ESU27 | 4212.25 | 4245.02 | 32.77 | 0.78% |
| ESZ27 | 4870.50 | 4908.39 | 37.89 | 0.78% |
| ESH28 | 5067.00 | 5106.42 | 39.42 | 0.78% |
| ESM28 | 5558.00 | 5601.24 | 43.24 | 0.78% |
| ESU28 | 5738.50 | 5783.15 | 44.65 | 0.78% |
| ESZ28 | 6067.25 | 6114.45 | 47.20 | 0.78% |
| ESH29 | 5587.00 | 5630.47 | 43.47 | 0.78% |
| ESM29 | 6374.25 | 6423.84 | 49.59 | 0.78% |
| ESU29 | 6874.00 | 6927.48 | 53.48 | 0.78% |
| ESZ29 | 6965.75 | 7019.94 | 54.19 | 0.78% |
Lectura económica. El “carry pickup” es la ganancia que el corto obtiene independientemente de la dirección del mercado, equivalente a aprovechar la diferencia entre la tasa libre de riesgo y el dividend yield del índice. En un mercado neutral (sin movimiento del spot), cada contrato vendido en corto cierra con un P&L positivo igual a este componente.
df_pl <- contratos_df |>
mutate(Periodo = row_number()) |>
select(Periodo, Contrato, `Carry pickup`, `Direccional`, `P&L corta`, `P&L acum corta`) |>
pivot_longer(c(`Carry pickup`, `Direccional`),
names_to = "Componente", values_to = "USD")
p_roll <- ggplot(df_pl, aes(x = Periodo, y = USD, fill = Componente)) +
geom_bar(stat = "identity", position = "stack") +
geom_line(data = contratos_df |> mutate(Periodo = row_number()),
aes(x = Periodo, y = `P&L acum corta`),
inherit.aes = FALSE, color = "#d62728", linewidth = 1.2) +
geom_point(data = contratos_df |> mutate(Periodo = row_number()),
aes(x = Periodo, y = `P&L acum corta`),
inherit.aes = FALSE, color = "#d62728", size = 2) +
scale_y_continuous(labels = dollar) +
scale_x_continuous(breaks = 1:16, labels = contrato_nombres) +
scale_fill_manual(values = c("Carry pickup" = "#2ca02c", "Direccional" = "#1f77b4")) +
labs(title = "Descomposición del P&L de la cobertura corta — 16 contratos",
subtitle = "Barras: P&L por contrato (carry + direccional). Línea roja: P&L acumulado.",
x = NULL, y = "USD", fill = NULL) +
theme_minimal(base_size = 11) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
plotly::ggplotly(p_roll)spx_monthly <- prices_monthly[, "SPX"]
spx_path <- as.numeric(tail(spx_monthly, length(precios)))
base_df <- data.frame(
Fecha = fechas,
F_futuro = precios,
Spot_SPX = spx_path,
Base = precios - spx_path
)
p_base <- ggplot(base_df, aes(x = Fecha)) +
geom_line(aes(y = F_futuro, color = "Futuro ES")) +
geom_line(aes(y = Spot_SPX, color = "Spot S&P 500")) +
scale_color_manual(values = c("Futuro ES" = "#1f77b4", "Spot S&P 500" = "#ff7f0e")) +
scale_y_continuous(labels = comma) +
labs(title = "Futuro vs Spot — magnitud histórica del riesgo de base",
x = NULL, y = "Puntos del índice", color = NULL) +
theme_minimal(base_size = 12)
plotly::ggplotly(p_base)| Estrategia | Comportamiento ante mercado alcista | Comportamiento ante mercado bajista | Sentido económico |
|---|---|---|---|
| Siempre corta (cobertura permanente) | Pierde en componente direccional pero gana carry pickup en cada roll | Compensa pérdidas del portafolio + gana carry | Tiene sentido cuando se desea aislar generación de alfa del riesgo de mercado |
| Siempre larga | Duplica la exposición sistémica + paga carry en cada roll | Duplica las pérdidas + paga carry | No es cobertura — es apalancamiento sintético |
| Roll-over disciplinado (caso adoptado) | Ganancia neta = α + dividendos + carry − componente direccional adverso | Pérdidas atenuadas en β·V + carry pickup | Estándar de mercado para coberturas plurianuales |
Fundamento. En una cobertura perfecta de β, el portafolio cubierto debería rendir aproximadamente la tasa libre de riesgo, ya que el componente sistémico (β·μ_m) queda neutralizado y solo permanece la rentabilidad del activo libre de riesgo más el alfa del portafolio.
\[ E[r_{cubierto}] \approx r_f + \alpha_p \qquad E[V_{cubierto}^{trimestre}] = V_p \cdot (1 + r_f \cdot 0.25) \]
# ^FVX: CBOE 5Y Treasury Yield (rango 3-5y, según indicación del enunciado)
fvx <- tryCatch(
quantmod::getSymbols("^FVX", from = end_date - 30, to = end_date + 1,
auto.assign = FALSE, src = "yahoo"),
error = function(e) NULL
)
if (!is.null(fvx) && nrow(fvx) > 0) {
rf_a_obs <- as.numeric(last(Cl(fvx))) / 100
fecha_rf <- as.Date(last(index(fvx)))
fuente_rf <- "^FVX (CBOE 5Y Treasury Yield, Yahoo Finance)"
} else {
rf_a_obs <- 0.045
fecha_rf <- end_date
fuente_rf <- "Placeholder 4.5% (fallback)"
}
# Actualizar la tasa libre de riesgo con el valor observado
rf_a <- rf_a_obs
rf_q <- rf_a / 4
rf_m <- rf_a / 12
cat(sprintf("Tasa libre de riesgo anualizada (rf): %s\nFecha: %s\nFuente: %s\n",
scales::percent(rf_a, 0.01), format(fecha_rf), fuente_rf))## Tasa libre de riesgo anualizada (rf): 4.02%
## Fecha: 2026-04-30
## Fuente: ^FVX (CBOE 5Y Treasury Yield, Yahoo Finance)
# Valor esperado del portafolio bajo distintos enfoques (un trimestre)
horizonte_q <- 0.25 # un trimestre
# 1. Sin cobertura: capitaliza al retorno esperado del portafolio
ret_q_sin_cob <- (1 + ret_p)^horizonte_q - 1
V_sin_cob <- capital * (1 + ret_q_sin_cob)
# 2. Cubierto: rinde ≈ rf
V_cubierto <- capital * (1 + rf_q)
# 3. Reducción de riesgo
sd_q_sin_cob <- sd_p * sqrt(horizonte_q)
sd_q_cubierto <- sd_q_sin_cob * sqrt(1 - 0.95) # ~95% de la varianza sistémica eliminada (proxy)
ve_tab <- data.frame(
Escenario = c("Sin cobertura", "Cubierto con futuros", "Diferencia"),
`Retorno trim.` = c(scales::percent(ret_q_sin_cob, 0.01),
scales::percent(rf_q, 0.01),
scales::percent(rf_q - ret_q_sin_cob, 0.01)),
`Valor esperado` = c(scales::dollar(V_sin_cob, 1),
scales::dollar(V_cubierto, 1),
scales::dollar(V_cubierto - V_sin_cob, 1)),
`Volatilidad trim.` = c(scales::percent(sd_q_sin_cob, 0.01),
scales::percent(sd_q_cubierto, 0.01),
"≈ −95%"),
check.names = FALSE
)
ktab(ve_tab, "Valor esperado de la cobertura trimestral")| Escenario | Retorno trim. | Valor esperado | Volatilidad trim. |
|---|---|---|---|
| Sin cobertura | 4.97% | $20,994,561 | 9.29% |
| Cubierto con futuros | 1.01% | $20,201,150 | 2.08% |
| Diferencia | -3.97% | -$793,411 | ≈ −95% |
Interpretación. La cobertura reduce sustancialmente la volatilidad del portafolio (al eliminar la varianza sistémica) pero sacrifica retorno esperado porque el inversionista renuncia a la prima de riesgo del mercado (μ_m − r_f) sobre la fracción cubierta. Si el portafolio tiene α > 0, el resultado neto es α + r_f, lo que justifica la cobertura cuando el manager confía en su capacidad de generar alfa sin querer estar expuesto al mercado.
# Horizontes: 1 mes y 48 meses (4 años) capitalizando mensualmente
n_meses <- 48
# --- 1. Sin cobertura ---
mu_p_m_calc <- ret_p / 12
sd_p_m_calc <- sd_p / sqrt(12)
E_sin_cob_1m <- capital * (1 + mu_p_m_calc)
E_sin_cob_4y <- capital * (1 + mu_p_m_calc)^n_meses
sd_sin_cob_4y <- capital * sd_p_m_calc * sqrt(n_meses)
# --- 2. Cubierto con futuros (rinde ~ rf) ---
E_cub_1m <- capital * (1 + rf_m)
E_cub_4y <- capital * (1 + rf_m)^n_meses
sd_cub_4y <- E_cub_4y * 0.05 # asumiendo varianza residual ~5% (idiosincrática)
# --- 3. Ajustado por VaR (escenario adverso al 99%) ---
# Caso conservador: el portafolio realiza el VaR mensual y luego sigue trayectoria normal
E_var_1m <- capital * (1 - VaR99_param_pct)
# A 4 años: mensual normal pero con drift recortado en VaR/12
E_var_4y <- capital * (1 + (mu_p_m_calc - VaR99_param_pct/12))^n_meses
comparacion <- data.frame(
Escenario = c("Sin cobertura", "Cubierto con futuros", "Ajustado por VaR 99%"),
`Valor esperado 1 mes` = c(scales::dollar(E_sin_cob_1m, 1),
scales::dollar(E_cub_1m, 1),
scales::dollar(E_var_1m, 1)),
`Retorno mensual` = c(scales::percent(mu_p_m_calc, 0.01),
scales::percent(rf_m, 0.01),
scales::percent(-VaR99_param_pct, 0.01)),
`Valor esperado 4 años` = c(scales::dollar(E_sin_cob_4y, 1),
scales::dollar(E_cub_4y, 1),
scales::dollar(E_var_4y, 1)),
`Retorno acumulado 4y` = c(scales::percent(E_sin_cob_4y/capital - 1, 0.01),
scales::percent(E_cub_4y/capital - 1, 0.01),
scales::percent(E_var_4y/capital - 1, 0.01)),
check.names = FALSE
)
ktab(comparacion, "Comparación de escenarios — 1 mes y horizonte de 4 años")| Escenario | Valor esperado 1 mes | Retorno mensual | Valor esperado 4 años | Retorno acumulado 4y |
|---|---|---|---|---|
| Sin cobertura | $20,357,079 | 1.79% | $46,766,253 | 133.83% |
| Cubierto con futuros | $20,067,050 | 0.34% | $23,485,498 | 17.43% |
| Ajustado por VaR 99% | $17,862,341 | -10.69% | $30,669,872 | 53.35% |
# Trayectorias proyectadas (path determinístico al retorno esperado)
meses <- 0:n_meses
df_path <- tibble(
Mes = meses,
`Sin cobertura` = capital * (1 + mu_p_m_calc)^meses,
`Cubierto` = capital * (1 + rf_m)^meses,
`Ajustado VaR` = capital * (1 + (mu_p_m_calc - VaR99_param_pct/12))^meses
) |>
pivot_longer(-Mes, names_to = "Escenario", values_to = "Valor")
p_comp <- ggplot(df_path, aes(x = Mes, y = Valor, color = Escenario)) +
geom_line(linewidth = 1) +
scale_y_continuous(labels = dollar) +
scale_color_manual(values = c("Sin cobertura" = "#1f77b4",
"Cubierto" = "#2ca02c",
"Ajustado VaR" = "#d62728")) +
labs(title = "Trayectoria esperada del valor del portafolio (4 años)",
x = "Mes desde inicio", y = "Valor esperado (USD)", color = NULL) +
theme_minimal(base_size = 12)
plotly::ggplotly(p_comp)| Acción | Política de dividendos | Yield aprox. | Impacto en cálculos |
|---|---|---|---|
| CMI | Trimestral, creciente | ~2.5% | Incorporado vía precios ajustados |
| HSY | Trimestral, >50 años de pago | ~2.7% | Incorporado vía precios ajustados |
| CDNS | No paga dividendos (reinvierte en R&D) | 0% | N/A |
Importante. Los retornos utilizados en este informe
se calculan sobre precios ajustados
(Adjusted Close de Yahoo Finance), que ya incorporan los
dividendos pagados como retorno total reinvertido. Por lo tanto, no se
requiere ajuste adicional. Para el portafolio óptimo, el ingreso por
dividendos ponderado se aproxima:
\[ d_p \approx w_{CMI} \cdot 2.5\% + w_{HSY} \cdot 2.7\% + w_{CDNS} \cdot 0\% \]
Esta porción del retorno es insensible a la cobertura con futuros sobre el índice, ya que los dividendos se cobran independientemente del precio del subyacente.
Fórmula: \(N^* = \beta \cdot V_p / (F_0 \cdot m)\). Manteniendo \(V_p\), \(F_0\) y \(m\) constantes, \(N^*\) es lineal en β.
betas_hip <- c(0.5, beta_p, 2.0)
etiquetas <- c("Hipotético β = 0.5",
sprintf("Real β = %.3f (calculado)", beta_p),
"Hipotético β = 2.0")
sens <- tibble(
Escenario = etiquetas,
Beta = betas_hip,
`N* exacto` = round(betas_hip * capital / (F0 * mult), 4),
`N* entero` = round(betas_hip * capital / (F0 * mult)),
`Exposición sistémica (USD)` = betas_hip * capital,
`Sensibilidad por +1% S&P` = betas_hip * 0.01 * capital,
`Margen inicial requerido` = round(betas_hip * capital / (F0 * mult)) * margen_inicial,
`Margen mant. requerido` = round(betas_hip * capital / (F0 * mult)) * margen_mant
) |>
mutate(
`Exposición sistémica (USD)` = scales::dollar(`Exposición sistémica (USD)`, 1),
`Sensibilidad por +1% S&P` = scales::dollar(`Sensibilidad por +1% S&P`, 1),
`Margen inicial requerido` = scales::dollar(`Margen inicial requerido`, 1),
`Margen mant. requerido` = scales::dollar(`Margen mant. requerido`, 1)
)
ktab(sens, "Sensibilidad de la cobertura ante distintos β del portafolio")| Escenario | Beta | N* exacto | N* entero | Exposición sistémica (USD) | Sensibilidad por +1% S&P | Margen inicial requerido | Margen mant. requerido |
|---|---|---|---|---|---|---|---|
| Hipotético β = 0.5 | 0.5000000 | 27.6100 | 28 | $10,000,000 | $100,000 | $742,000 | $672,000 |
| Real β = 0.933 (calculado) | 0.9330791 | 51.5246 | 52 | $18,661,582 | $186,616 | $1,378,000 | $1,248,000 |
| Hipotético β = 2.0 | 2.0000000 | 110.4400 | 110 | $40,000,000 | $400,000 | $2,915,000 | $2,640,000 |
betas_grid <- seq(0.1, 2.5, by = 0.05)
df_sens <- tibble(
beta = betas_grid,
N_exacto = beta * capital / (F0 * mult),
Margen_total = round(N_exacto) * margen_inicial
)
p_sens <- ggplot(df_sens, aes(x = beta)) +
geom_line(aes(y = N_exacto), color = "#1f77b4", linewidth = 1) +
geom_vline(xintercept = c(0.5, beta_p, 2.0), linetype = "dashed",
color = c("gray50", "red", "gray50")) +
annotate("text", x = beta_p, y = max(df_sens$N_exacto) * 0.95,
label = sprintf("β real = %.3f", beta_p), color = "red", hjust = -0.1) +
scale_x_continuous(breaks = seq(0, 2.5, 0.25)) +
labs(title = "N° óptimo de contratos en función de β del portafolio",
x = "β del portafolio", y = "N* (exacto)") +
theme_minimal(base_size = 12)
plotly::ggplotly(p_sens)Interpretación financiera.
Construcción del portafolio. A partir del universo del S&P 500 se seleccionaron tres acciones con perfiles complementarios y baja correlación esperada: Cummins (CMI) como exposición cíclica industrial con opcionalidad en transición energética, Hershey (HSY) como activo defensivo con dividendo creciente, y Cadence (CDNS) como crecimiento secular en automatización de diseño de semiconductores. La optimización media-varianza generó un portafolio de tangencia cuyo Sharpe Ratio es superior al de cualquier acción individual, validando empíricamente el beneficio de la diversificación.
Riesgo del portafolio sin cobertura. El VaR mensual al 99% (paramétrico) representa una pérdida potencial significativa frente al capital de USD 20 M. La β del portafolio (combinación ponderada de las β individuales) constituye el insumo central para dimensionar la cobertura: cuanta más β, más sensible al mercado, más contratos requiere la cobertura.
Diseño de la cobertura. El número óptimo de contratos E-mini S&P 500 surge de \(N^* = \beta_p \cdot V_p / (F_0 \cdot m)\) y se redondea al entero más cercano para minimizar la β residual del portafolio cubierto. La posición es corta en futuros, consistente con un inversionista que ya posee acciones y desea protegerse de una caída del mercado.
Mecánica operativa (margen y roll-over). El análisis mes a mes sobre 48 períodos históricos muestra que la cuenta de margen experimenta llamados al margen en períodos de fuerte alza del mercado (donde la posición corta pierde), exigiendo reposiciones que comprometen liquidez. La estrategia de roll-over trimestral implica 16 cierres y aperturas durante el horizonte de 4 años, generando costos transaccionales y exposición a riesgo de base que, para futuros cash-settled como el E-mini, es bajo en magnitud.
Trade-off de la cobertura. La comparación entre los escenarios sin cobertura, cubierto y ajustado por VaR evidencia el principio fundamental: la cobertura sustituye la prima de riesgo del mercado por la tasa libre de riesgo. Esto reduce sustancialmente la volatilidad y el VaR, pero también reduce el retorno esperado. La cobertura tiene sentido cuando (a) el manager confía en su capacidad de generar alfa y quiere aislarla del mercado, (b) el horizonte coincide con un período de incertidumbre macroeconómica elevada, (c) el costo de oportunidad de no cubrir (pérdida potencial extrema) supera la prima de riesgo cedida.
Recomendación al gerente. Para el portafolio diseñado y bajo las condiciones de mercado actuales:
Bodie, Z., Kane, A., & Marcus, A. J. (2024). Investments (13th ed.). McGraw-Hill Education.
Cadence Design Systems. (2025). Annual Report 2024 (Form 10-K). https://investor.cadence.com/financial-information/annual-reports
CME Group. (2026). E-mini S&P 500 futures contract specifications. https://www.cmegroup.com/markets/equities/sp/e-mini-sandp500.contractSpecs.html
CME Group. (2026). E-mini S&P 500 margins. https://www.cmegroup.com/markets/equities/sp/e-mini-sandp500.margins.html
Cummins Inc. (2025). Annual Report 2024 (Form 10-K). https://investor.cummins.com/financial-information/annual-reports
Fabozzi, F. J., Markowitz, H. M., & Gupta, F. (2008). Portfolio selection. In F. J. Fabozzi (Ed.), Handbook of finance (Vol. 2, pp. 3–13). John Wiley & Sons.
Hull, J. C. (2022). Options, futures, and other derivatives (11th ed.). Pearson.
Jorion, P. (2007). Value at risk: The new benchmark for managing financial risk (3rd ed.). McGraw-Hill.
Markowitz, H. (1952). Portfolio selection. The Journal of Finance, 7(1), 77–91. https://doi.org/10.1111/j.1540-6261.1952.tb01525.x
Ryan, J. A., & Ulrich, J. M. (2024). quantmod: Quantitative financial modelling framework (R package version 0.4.26) [Software]. https://cran.r-project.org/package=quantmod
Sharpe, W. F. (1964). Capital asset prices: A theory of market equilibrium under conditions of risk. The Journal of Finance, 19(3), 425–442. https://doi.org/10.1111/j.1540-6261.1964.tb02865.x
SlickCharts. (2026). S&P 500 components by weight. https://www.slickcharts.com/sp500
The Hershey Company. (2025). Annual Report 2024 (Form 10-K). https://www.thehersheycompany.com/en_us/investors/financial-reports.html
U.S. Securities and Exchange Commission. (2026). EDGAR — Cummins, Hershey, Cadence filings. https://www.sec.gov/edgar/searchedgar/companysearch
Yahoo Finance. (2026). Historical market data — CMI, HSY, CDNS, ^GSPC, ^FVX, ES=F. https://finance.yahoo.com/