Este trabajo presenta una estrategia completa de inversión y cobertura para un portafolio de tres acciones del S&P 500 — NVIDIA, Tesla y Costco — con un capital de USD 20 millones y un horizonte de cuatro años a partir del 30 de abril de 2026. La estrategia integra análisis fundamental, optimización media-varianza, medición de riesgo mediante VaR, estimación de sensibilidad al mercado por CAPM, cobertura con contratos E-mini S&P 500 (ES) de CME Group y simulación prospectiva bajo tres escenarios de mercado.
df_id <- data.frame(
Acción = tickers,
Empresa = nombres,
Sector = sectores,
Índice = rep("S&P 500", 3),
Dividendo = c("No distribuye", "No distribuye", "Sí — aprox. 0.6% anual"),
row.names = NULL, check.names = FALSE
)
kable(df_id,
caption = "Tabla 1. Identificación de las acciones seleccionadas.",
align = c("c","l","l","c","c")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white") %>%
column_spec(1, bold = TRUE, color = "white",
background = c("#76b900","#E03030","#005daa"))| Acción | Empresa | Sector | Índice | Dividendo |
|---|---|---|---|---|
| NVDA | NVIDIA Corporation | Tecnología / Semiconductores / Inteligencia Artificial | S&P 500 | No distribuye |
| TSLA | Tesla Inc. | Consumo discrecional / Vehículos eléctricos | S&P 500 | No distribuye |
| COST | Costco Wholesale Corporation | Consumo básico / Comercio minorista por membresías | S&P 500 | Sí — aprox. 0.6% anual |
NVIDIA Corporation diseña plataformas de cómputo acelerado, unidades de procesamiento gráfico —GPU—, soluciones de inteligencia artificial, centros de datos, software y redes especializadas. En el año fiscal 2025, la compañía registró ingresos por USD 130.5 mil millones, impulsados principalmente por el crecimiento del segmento de centros de datos, asociado a la demanda de infraestructura para inteligencia artificial y computación acelerada (NVIDIA Corporation, 2025).
El crecimiento de NVIDIA se explica por la adopción de arquitecturas como Hopper y Blackwell, utilizadas en cargas de trabajo de IA generativa, modelos de lenguaje y procesamiento intensivo de datos. Además, su ecosistema de software, incluyendo CUDA, fortalece su posición competitiva al integrar hardware y software en una plataforma de alto valor agregado (NVIDIA Corporation, 2025).
La inclusión de NVDA en el portafolio se justifica por su liderazgo en inteligencia artificial, semiconductores y centros de datos, sectores con alto potencial de crecimiento, aunque acompañados de una elevada sensibilidad a cambios en expectativas tecnológicas, demanda global y condiciones del mercado accionario.
Tesla, Inc. diseña, fabrica y comercializa vehículos eléctricos, sistemas de almacenamiento de energía y soluciones relacionadas con conducción autónoma. Sus principales líneas de negocio incluyen automóviles eléctricos, generación y almacenamiento de energía, servicios, créditos regulatorios y software asociado a la conducción autónoma (Tesla, Inc., 2025).
Durante 2024, Tesla reportó ingresos totales de USD 97.69 mil millones. Aunque el segmento automotriz enfrentó presión por menores precios promedio de venta y mayor competencia, el segmento de energía mostró un crecimiento relevante, impulsado por productos como Megapack y Powerwall (Tesla, Inc., 2025).
La inclusión de TSLA en el portafolio responde a su exposición a la transición energética, vehículos eléctricos, almacenamiento de energía e innovación en inteligencia artificial aplicada a movilidad. Sin embargo, su alta volatilidad implica un mayor nivel de riesgo, por lo que complementa el portafolio como activo de crecimiento agresivo.
Costco Wholesale Corporation opera una cadena internacional de almacenes por membresía, enfocada en la venta de productos de consumo masivo, alimentos, electrónicos, farmacia, servicios complementarios y comercio electrónico. Su modelo de negocio se basa en altos volúmenes de venta, márgenes reducidos y una fuente recurrente de ingresos proveniente de membresías (Costco Wholesale Corporation, 2025).
La compañía mantiene un perfil defensivo debido a la estabilidad de su demanda, la recurrencia de las membresías y su posición dentro del sector de consumo básico. En 2025, Costco continuó reportando una estructura sólida de ventas, expansión internacional y pago regular de dividendos, sujeto a la aprobación de su junta directiva (Costco Wholesale Corporation, 2025).
La inclusión de COST en el portafolio se justifica por su menor volatilidad relativa y su carácter defensivo frente a NVDA y TSLA. Su presencia permite equilibrar la cartera, reducir la exposición exclusiva a activos tecnológicos y aportar mayor estabilidad al rendimiento esperado.
Metodología aplicada:
getSymbols() del paquete quantmod. Se utiliza
el precio ajustado de cierre (Adjusted Close), el cual
incorpora dividendos y ajustes por splits corporativos, garantizando la
consistencia de la serie histórica.to.monthly, indexAt = "lastof").descargar_m <- function(tk, ini, fin) {
tryCatch({
r <- getSymbols(tk, src = "yahoo", from = ini, to = fin,
auto.assign = FALSE, warnings = FALSE)
m <- to.monthly(Ad(r), indexAt = "lastof", OHLC = FALSE)
colnames(m) <- tk; m
}, error = function(e) NULL)
}
p_NVDA <- descargar_m("NVDA", fecha_hist_ini, fecha_hist_fin)
p_TSLA <- descargar_m("TSLA", fecha_hist_ini, fecha_hist_fin)
p_COST <- descargar_m("COST", fecha_hist_ini, fecha_hist_fin)
p_SP500 <- descargar_m("^GSPC", fecha_hist_ini, fecha_hist_fin)
p_ES <- tryCatch({
r <- getSymbols("ES=F", src = "yahoo", from = fecha_hist_ini,
to = fecha_hist_fin, auto.assign = FALSE, warnings = FALSE)
m <- to.monthly(Ad(r), indexAt = "lastof", OHLC = FALSE)
colnames(m) <- "ES"; m
}, error = function(e) {
m <- descargar_m("^GSPC", fecha_hist_ini, fecha_hist_fin)
colnames(m) <- "ES"; m
})
precios <- merge(p_NVDA, p_TSLA, p_COST, p_SP500, p_ES, join = "inner")
colnames(precios) <- c("NVDA","TSLA","COST","SP500","ES")
F0 <- as.numeric(tail(precios[,"ES"], 1))
S0 <- as.numeric(tail(precios[,"SP500"], 1))
val_nocional <- F0 * mult_contrato
ret_log <- na.omit(diff(log(precios)))
ret_acc <- ret_log[, c("NVDA","TSLA","COST")]
ret_mkt <- ret_log[, "SP500"]Período efectivo: 31/05/2016 a 30/04/2026 — 120 observaciones mensuales.
ret_m_med <- colMeans(ret_acc)
ret_anual <- (1 + ret_m_med)^fa - 1
sd_men <- apply(ret_acc, 2, sd)
sd_anual <- sd_men * sqrt(fa)
skew_r <- apply(ret_acc, 2, skewness)
kurt_r <- apply(ret_acc, 2, kurtosis)
cov_men <- cov(ret_acc)
cov_anual <- cov_men * fa
cor_mat <- cor(ret_acc)
est_df <- data.frame(
Acción = tickers,
`Ret. mensual %` = round(ret_m_med*100, 4),
`Ret. anual %` = round(ret_anual*100, 4),
`SD mensual %` = round(sd_men*100, 4),
`SD anual %` = round(sd_anual*100, 4),
Asimetría = round(skew_r, 4),
`Curtosis exceso` = round(kurt_r - 3, 4),
check.names = FALSE, row.names = NULL
)
kable(est_df,
caption = "Tabla 2. Estadísticas descriptivas de retornos logarítmicos mensuales (2016–2026).",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white")| Acción | Ret. mensual % | Ret. anual % | SD mensual % | SD anual % | Asimetría | Curtosis exceso |
|---|---|---|---|---|---|---|
| NVDA | 4.3753 | 67.1764 | 13.2274 | 45.8212 | -0.4943 | 0.4742 |
| TSLA | 2.7066 | 37.7786 | 16.8927 | 58.5179 | 0.3334 | 0.4585 |
| COST | 1.7415 | 23.0203 | 6.0183 | 20.8479 | -0.5544 | 0.1646 |
Los resultados muestran que NVDA registra el mayor retorno promedio con un rendimiento mensual de 4.3753% y anual de 67.1764%. Esto refleja su fuerte crecimiento histórico. TSLA también muestra un desempeño elevado, con un retorno anual de 37.7786%, aunque acompañado de la mayor volatilidad del grupo, con una desviación estándar anual de 58.5179%.
Por otro lado, COST muestra el perfil más defensivo, con el menor retorno anual (23.0203%) pero también con la menor volatilidad anual (20.8479%), lo que indica mayor estabilidad. En cuanto a la distribución de retornos, NVDA y COST presentan asimetría negativa, lo que sugiere mayor presencia de caídas extremas frente a subidas extremas, mientras que TSLA presenta asimetría positiva.
kable(as.data.frame(round(cov_anual, 6)),
caption = "Tabla 3. Matriz de varianzas-covarianzas anualizada.",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white")| NVDA | TSLA | COST | |
|---|---|---|---|
| NVDA | 0.209958 | 0.090097 | 0.039411 |
| TSLA | 0.090097 | 0.342434 | 0.037316 |
| COST | 0.039411 | 0.037316 | 0.043463 |
La matriz de varianzas-covarianzas anualizada evidencia que TSLA es el activo con mayor riesgo individual, al presentar la varianza más alta (0.342434), seguida por NVDA (0.209958). En contraste, COST registra la menor varianza (0.043463), lo que confirma su comportamiento más defensivo y estable dentro del portafolio.
En conclusión, esta matriz confirma que el portafolio combina activos de alto riesgo y crecimiento, como NVDA y TSLA, con un activo más defensivo como COST.
kable(as.data.frame(round(cor_mat, 4)),
caption = "Tabla 4. Matriz de correlaciones entre retornos mensuales.",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white")| NVDA | TSLA | COST | |
|---|---|---|---|
| NVDA | 1.0000 | 0.3360 | 0.4126 |
| TSLA | 0.3360 | 1.0000 | 0.3059 |
| COST | 0.4126 | 0.3059 | 1.0000 |
La matriz de correlación muestra que las tres acciones tienen correlaciones positivas moderadas, lo que significa que tienden a moverse en la misma dirección que el mercado, pero no perfectamente. Esto permite tener un portafolio diversificado, ya que la combinación de activos reduce la volatilidad del portafolio.
La menor correlación se observa entre TSLA y COST con 0.3059, lo que reflajaría una mayor diferencia en sus comportamientos financieros. Esto es coherente con sus perfiles: TSLA es una acción de alto crecimiento y mayor volatilidad, mientras que COST tiene un carácter más defensivo y estable.
También vemos que la correlación más alta se presenta entre NVDA y COST con 0.4126. Si bien sigue siendo moderada existe cierta exposición al mercado, todavía hay espacio para obtener beneficios de diversificación.
En conclusión, los resultados soportan la construcción de un portafolio diversificado, ya que las acciones no se mueven de manera idéntica. Sin embargo, al ser todas las correlaciones positivas, la diversificación reduce parcialmente el riesgo específico, pero no elimina el riesgo sistemático del mercado.
df_p <- as.data.frame(precios[, c("NVDA","TSLA","COST","SP500")])
df_p$Fecha <- index(precios)
col4 <- c("NVDA"="#76b900","TSLA"="#E03030","COST"="#005daa","SP500"="#888888")
pivot_longer(df_p, cols=-Fecha, names_to="Activo", values_to="Precio") %>%
group_by(Activo) %>% mutate(Norm=Precio/first(Precio)*100) %>% ungroup() %>%
ggplot(aes(x=Fecha, y=Norm, color=Activo)) +
geom_line(size=1) + scale_color_manual(values=col4) +
labs(title="Evolución de precios normalizados (Base 100)",
subtitle="NVDA · TSLA · COST vs S&P 500 | Abr 2016 – Abr 2026",
x=NULL, y="Precio normalizado", color=NULL,
caption="Fuente: Yahoo Finance (2026). Elaboración propia.") +
theme_minimal(base_size=12) +
theme(plot.title=element_text(face="bold"), legend.position="bottom",
plot.caption=element_text(color="gray50", size=9))Figura 1. Precios normalizados Base 100. Fuente: Yahoo Finance (2026). Elaboración propia.
La gráfica en base 100 se utiliza para comparar el crecimiento relativo de diferentes activos independientemente de su precio inicial. Se fija el valor inicial en 100 y se observa cómo evoluciona en el tiempo, lo que permite interpretar el rendimiento como un índice. En esta oportunidad lo que muestra es que NVDA tuvo un crecimiento muy superior al resto, lo que explicaría su peso en el portafolio, mientras que COST y TSLA presentan un comportamiento más estable y el S&P 500 sirve como referencia del mercado.
df_r <- as.data.frame(ret_acc); df_r$Fecha <- index(ret_acc)
pivot_longer(df_r, cols=-Fecha, names_to="Acción", values_to="Retorno") %>%
ggplot(aes(x=Fecha, y=Retorno*100, fill=Acción)) +
geom_col(alpha=0.75, width=25) +
geom_hline(yintercept=0, color="gray30", size=0.4) +
facet_wrap(~Acción, ncol=1) +
scale_fill_manual(values=colores) +
labs(title="Retornos logarítmicos mensuales (%)",
x=NULL, y="Retorno (%)",
caption="Fuente: Yahoo Finance (2026). Elaboración propia.") +
theme_minimal(base_size=11) +
theme(plot.title=element_text(face="bold"), legend.position="none")Figura 2. Retornos logarítmicos mensuales. Elaboración propia.
corrplot(cor_mat, method="color", type="upper",
addCoef.col="black", tl.col="black", tl.srt=45,
col=colorRampPalette(c("#d73027","white","#1a9850"))(200),
title="Correlaciones: NVDA · TSLA · COST", mar=c(0,0,2,0))Figura 3. Mapa de calor de correlaciones. Elaboración propia.
La construcción del portafolio óptimo sigue el enfoque de Markowitz
(1952), que busca maximizar el Sharpe Ratio sujeto a restricciones de
peso. La solución se obtiene mediante programación cuadrática con el
paquete quadprog, resolviendo:
\[\max_w \frac{w'\mu - r_f}{\sqrt{w'\Sigma w}} \quad \text{s.a.} \quad \sum w_i = 1, \quad w_i \in [10\%, 70\%]\]
Las restricciones de peso \(w_i \in [10\%, 70\%]\) se aplican porque NVDA presenta retornos históricos anormalmente elevados entre 2016 y 2026, lo que sin restricciones en el portafolio llevaría al optimizador a una solución sesgada donde el 100% del capital se asignaría a un solo activo “NVDA”.
mu <- ret_anual
Sigma <- cov_anual
n_act <- length(mu)
names(mu) <- tickers
opt_sharpe <- function(mu, Sigma, rf, wmin, wmax) {
n <- length(mu)
Dmat <- 2*Sigma + diag(1e-8, n)
dvec <- mu - rf
Amat <- cbind(rep(1,n), diag(n), -diag(n))
bvec <- c(1, rep(wmin,n), rep(-wmax,n))
sol <- solve.QP(Dmat, dvec, Amat, bvec, meq=1)
w <- pmax(sol$solution, 0); w/sum(w)
}
pesos <- opt_sharpe(mu, Sigma, rf_anual, w_min, w_max)
names(pesos) <- tickers
ret_port <- sum(pesos*mu)
sd_port <- sqrt(as.numeric(t(pesos) %*% Sigma %*% pesos))
sharpe_p <- (ret_port - rf_anual)/sd_port
monto_inv <- pesos*capital
ret_port_m <- sum(pesos*ret_m_med)
sd_port_m <- sqrt(as.numeric(t(pesos) %*% cov_men %*% pesos))
port_df <- data.frame(
Acción = tickers,
Empresa = nombres,
`Peso (%)` = paste0(round(pesos*100,2),"%"),
`Monto (USD)` = paste0("$",format(round(monto_inv,0),big.mark=",")),
`Ret. anual %` = paste0(round(mu*100,4),"%"),
`SD anual %` = paste0(round(sqrt(diag(Sigma))*100,4),"%"),
check.names=FALSE, row.names=NULL
)
kable(port_df,
caption="Tabla 5. Portafolio óptimo — pesos y asignación de capital.",
align=c("c","l","c","c","c","c")) %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"),
full_width=TRUE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
column_spec(1, bold=TRUE, color="white",
background=c("#76b900","#E03030","#005daa"))| Acción | Empresa | Peso (%) | Monto (USD) | Ret. anual % | SD anual % |
|---|---|---|---|---|---|
| NVDA | NVIDIA Corporation | 70% | $14,000,000 | 67.1764% | 45.8212% |
| TSLA | Tesla Inc. | 12.9% | $ 2,580,128 | 37.7786% | 58.5179% |
| COST | Costco Wholesale Corporation | 17.1% | $ 3,419,872 | 23.0203% | 20.8479% |
met_df <- data.frame(
Métrica = c("Retorno esperado anual","Retorno esperado mensual",
"Desviación estándar anual","Desviación estándar mensual",
"Sharpe Ratio","Tasa libre de riesgo (^TNX, 30-abr-2026)",
"Capital total"),
Valor = c(paste0(round(ret_port*100,4),"%"),
paste0(round(ret_port_m*100,4),"%"),
paste0(round(sd_port*100,4),"%"),
paste0(round(sd_port_m*100,4),"%"),
round(sharpe_p,4),
paste0(rf_anual*100,"%"),
paste0("USD ",format(capital,big.mark=","))),
check.names=FALSE
)
kable(met_df,
caption="Tabla 6. Métricas globales del portafolio óptimo.",
align=c("l","c")) %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=FALSE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(5, bold=TRUE, background="#eaf4d3")| Métrica | Valor |
|---|---|
| Retorno esperado anual | 55.8335% |
| Retorno esperado mensual | 3.7097% |
| Desviación estándar anual | 37.0408% |
| Desviación estándar mensual | 10.6928% |
| Sharpe Ratio | 1.394 |
| Tasa libre de riesgo (^TNX, 30-abr-2026) | 4.2% |
| Capital total | USD 20,000,000 |
data.frame(Acción=tickers, Peso=pesos*100, Monto=monto_inv/1e6) %>%
ggplot(aes(x=reorder(Acción,-Peso), y=Peso, fill=Acción)) +
geom_col(width=0.55, color="white") +
geom_text(aes(label=paste0(round(Peso,2),"%\nUSD ",round(Monto,1),"M")),
vjust=-0.3, fontface="bold", size=3.8) +
geom_hline(yintercept=c(w_min*100,w_max*100),
linetype="dashed", color="gray50", size=0.7) +
scale_fill_manual(values=colores) +
scale_y_continuous(limits=c(0,85)) +
labs(title="Pesos óptimos — portafolio máximo Sharpe con restricciones",
subtitle=sprintf("Ret=%.2f%% | SD=%.2f%% | Sharpe=%.4f",
ret_port*100, sd_port*100, sharpe_p),
x=NULL, y="Peso (%)",
caption="Fuente: Elaboración propia. Restricciones w ∈ [10%, 70%].") +
theme_minimal(base_size=12) +
theme(plot.title=element_text(face="bold"), legend.position="none")Figura 4. Pesos óptimos del portafolio. Elaboración propia.
Las Tablas 5 y 6 presentan el resultado del portafolio óptimo media-varianza, construido con un capital total de USD 20,000,000. La asignación final concentra el mayor peso en NVDA, con un 70% del portafolio, equivalente a USD 14,000,000. Este resultado es dado por su alto retorno histórico anual de 67.1764%, aunque también presenta una volatilidad considerable de 45.8211% anual.
En segundo lugar, el modelo asigna un 17.1% a COST, equivalente a USD 3,419,869. Aunque COST tiene el menor retorno anual esperado entre las tres acciones, con 23.0203%, también registra la menor desviación estándar anual, 20.8479%, lo que la convierte en el componente más defensivo del portafolio. Su inclusión permite reducir el riesgo total y aportar mayor estabilidad a la cartera.
También observamos que TSLA recibe el menor peso, con 12.9% que es equivalente a USD 2,580,131. A pesar de tener un retorno anual esperado elevado de 37.7786%, su desviación estándar anual es la más alta del grupo, con 58.5179%. Esto indica que TSLA ofrece potencial de rentabilidad, pero con un nivel de riesgo significativamente mayor.
A nivel global, el portafolio óptimo presenta un retorno esperado anual de 55.8335% y una desviación estándar anual de 37.0408%. Esto refleja una cartera con alto potencial de rentabilidad, pero también con una exposición importante al riesgo, especialmente por la fuerte participación de NVDA y la volatilidad de TSLA.
El Sharpe Ratio de 1.394 indica que el portafolio genera una rentabilidad significativa frente a la tasa libre de riesgo de 4.2% anual y en relación con el nivel de riesgo asumido. En pocas palablas un Sharpe positivo y superior a 1 suele interpretarse como una relación riesgo-retorno favorable, ya que el portafolio compensa al inversionista por la volatilidad y riesgo asumido.
En conclusión, el portafolio óptimo está orientado principalmente al crecimiento, liderado por NVDA, pero incorpora COST como activo estabilizador y TSLA como una acción que tiene gran potencial de retorno.
set.seed(42)
sim <- t(replicate(5000, {
repeat {
w <- runif(n_act); w <- w/sum(w)
if (all(w>=w_min) && all(w<=w_max)) break
w <- pmax(pmin(w,w_max),w_min); w <- w/sum(w); break
}
r <- sum(w*mu); v <- as.numeric(t(w)%*%Sigma%*%w)
c(r, sqrt(v), (r-rf_anual)/sqrt(v))
}))
data.frame(Ret=sim[,1]*100, Risk=sim[,2]*100, SR=sim[,3]) %>%
ggplot(aes(x=Risk, y=Ret, color=SR)) +
geom_point(alpha=0.35, size=0.7) +
scale_color_gradient(low="#d73027", high="#1a9850", name="Sharpe") +
annotate("point", x=sd_port*100, y=ret_port*100,
color="navy", size=6, shape=18) +
annotate("label", x=sd_port*100+2.5, y=ret_port*100,
label=sprintf("Óptimo\nNVDA=%.0f%% TSLA=%.0f%%\nCOST=%.0f%% SR=%.2f",
pesos[1]*100,pesos[2]*100,pesos[3]*100,sharpe_p),
color="navy", size=2.8, fill="white", hjust=0) +
annotate("point", x=sqrt(diag(Sigma))*100, y=mu*100,
color=c("#76b900","#E03030","#005daa"), size=4, shape=17) +
annotate("text", x=sqrt(diag(Sigma))*100+1.5, y=mu*100,
label=tickers, color=c("#76b900","#E03030","#005daa"),
size=3.5, fontface="bold") +
labs(title="Frontera eficiente — restricciones w ∈ [10%, 70%]",
x="Riesgo — SD anualizada (%)", y="Retorno esperado anual (%)",
caption="Fuente: Elaboración propia con datos Yahoo Finance (2026).") +
theme_minimal(base_size=12) +
theme(plot.title=element_text(face="bold"))Figura 5. Frontera eficiente — 5,000 portafolios simulados. Elaboración propia.
la frontera eficiente confirma que el portafolio óptimo está orientado al crecimiento, principalmente por la acción de NVDA, pero incluye a COST para reducir la volatilidad total y a TSLA en una proporción limitada debido a su mayor riesgo. La combinación seleccionada maximiza la rentabilidad ajustada por riesgo y se considera eficiente frente a las demás alternativas simuladas.
El VaR cuantifica la pérdida máxima esperada del portafolio para un horizonte dado y un nivel de confianza determinado. Se presentan tres metodologías complementarias: el método paramétrico asume que los retornos siguen una distribución normal; el método histórico utiliza directamente los percentiles de la distribución empírica; el CVaR (Expected Shortfall) mide la pérdida promedio en los escenarios que superan el umbral del VaR.
ret_port_s <- as.numeric(ret_acc %*% pesos)
z99 <- qnorm(0.01); z95 <- qnorm(0.05)
Vp99p <- -(ret_port_m + z99*sd_port_m); Vp99u <- Vp99p*capital
Vp95p <- -(ret_port_m + z95*sd_port_m); Vp95u <- Vp95p*capital
rord <- sort(ret_port_s); nobs <- length(rord)
Vh99p <- -rord[max(1,floor(0.01*nobs))]; Vh99u <- Vh99p*capital
Vh95p <- -rord[max(1,floor(0.05*nobs))]; Vh95u <- Vh95p*capital
CV99p <- -mean(rord[rord <= -Vh99p]); CV99u <- CV99p*capital
CV95p <- -mean(rord[rord <= -Vh95p]); CV95u <- CV95p*capitalvar_df <- data.frame(
Método = c(rep("Paramétrico (Normal)",2),
rep("Histórico",2),
rep("CVaR / Expected Shortfall",2)),
Confianza = rep(c("99% (VaR 1%)","95% (VaR 5%)"),3),
`VaR (%)` = paste0(round(c(Vp99p,Vp95p,Vh99p,Vh95p,CV99p,CV95p)*100,4),"%"),
`VaR (USD)`= paste0("$",format(round(c(Vp99u,Vp95u,Vh99u,Vh95u,CV99u,CV95u),0),
big.mark=",")),
check.names=FALSE, row.names=NULL
)
kable(var_df,
caption="Tabla 7. VaR mensual del portafolio — capital USD 20,000,000.",
align=c("l","c","c","c")) %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"),
full_width=TRUE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(c(1,2), background="#fef9f0") %>%
row_spec(c(3,4), background="#f0f7fe") %>%
row_spec(c(5,6), background="#f0fef4")| Método | Confianza | VaR (%) | VaR (USD) |
|---|---|---|---|
| Paramétrico (Normal) | 99% (VaR 1%) | 21.1654% | $4,233,082 |
| Paramétrico (Normal) | 95% (VaR 5%) | 13.8783% | $2,775,669 |
| Histórico | 99% (VaR 1%) | 31.1096% | $6,221,917 |
| Histórico | 95% (VaR 5%) | 17.4399% | $3,487,972 |
| CVaR / Expected Shortfall | 99% (VaR 1%) | 31.1096% | $6,221,917 |
| CVaR / Expected Shortfall | 95% (VaR 5%) | 21.7766% | $4,355,315 |
Según los resultados observados, el VaR paramétrico al 99% indica que en condiciones normales de mercado, el portafolio no debería perder más de USD 4,233,082 en un mes dado — umbral que se superaría en promedio una vez cada cien meses. El VaR al 95% fija ese límite en USD 2,775,669, con una frecuencia esperada de excedencia de cinco veces por cada cien períodos mensuales. Estos valores dimensionan la exposición al riesgo sistemático que se busca neutralizar mediante la posición de cobertura en futuros: cuando el mercado cae, las ganancias de la posición corta en futuros del indice (ES) compensan parcial o totalmente las pérdidas dentro de esos umbrales.
data.frame(Retorno=ret_port_s*100) %>%
ggplot(aes(x=Retorno)) +
geom_histogram(aes(y=..density..), bins=30,
fill="#4a90d9", color="white", alpha=0.65) +
stat_function(fun=dnorm,
args=list(mean=ret_port_m*100, sd=sd_port_m*100),
color="navy", size=1.1, linetype="dashed") +
geom_vline(xintercept=c(-Vp99p*100,-Vp95p*100),
color=c("#d73027","#f46d43"), size=1.3) +
geom_vline(xintercept=c(-Vh99p*100,-Vh95p*100),
color=c("#d73027","#f46d43"), size=0.9, linetype="dashed") +
annotate("text",
x=c(-Vp99p*100-0.4,-Vp95p*100-0.4), y=c(0.07,0.085),
label=c(sprintf("VaR 99%%\n-%.2f%%\n-$%sM",Vp99p*100,round(Vp99u/1e6,2)),
sprintf("VaR 95%%\n-%.2f%%\n-$%sM",Vp95p*100,round(Vp95u/1e6,2))),
color=c("#d73027","#f46d43"), size=3, hjust=1) +
labs(title="Distribución de retornos mensuales del portafolio",
subtitle="VaR paramétrico (sólido) e histórico (punteado) | horizonte 1 mes",
x="Retorno mensual (%)", y="Densidad",
caption="Fuente: Elaboración propia.") +
theme_minimal(base_size=12) +
theme(plot.title=element_text(face="bold"))Figura 6. Distribución de retornos mensuales del portafolio con umbrales VaR. Elaboración propia.
La mayor parte de los retornos se concentra alrededor de valores positivos y cercanos al promedio, aunque se observa una cola izquierda relevante, lo que indica la existencia de meses con pérdidas significativas. Esta cola negativa es importante porque representa los escenarios de riesgo extremo que el VaR busca cuantificar.
En conclusión, el gráfico confirma que el portafolio tiene un perfil de rentabilidad atractivo, pero que también presenta una exposición significativa a pérdidas mensuales extremas. Por esto los resultados del VaR justifican la implementación de una estrategia de cobertura con futuros sobre el S&P 500, especialmente para mitigar esos riesgos que ocasionan las caídas fuertes del mercado.
El beta de cada acción se estima mediante regresión del retorno individual sobre el retorno del mercado, siguiendo el modelo de Sharpe:
\[R_i - R_f = \alpha_i + \beta_i (R_m - R_f) + \varepsilon_i\]
El beta del portafolio se obtiene como el promedio ponderado de los betas individuales, usando los pesos óptimos de la sección anterior.
exc_mkt <- as.numeric(ret_mkt) - rf_mensual
res_capm <- lapply(tickers, function(tk) {
ea <- as.numeric(ret_acc[,tk]) - rf_mensual
mod <- lm(ea ~ exc_mkt); s <- summary(mod)
list(alpha=coef(mod)[1], beta=coef(mod)[2],
beta_se=s$coefficients[2,2], beta_p=s$coefficients[2,4],
r2=s$r.squared)
})
betas <- sapply(res_capm,`[[`,"beta"); names(betas) <- tickers
alphas <- sapply(res_capm,`[[`,"alpha")
betas_se <- sapply(res_capm,`[[`,"beta_se")
r2v <- sapply(res_capm,`[[`,"r2")
beta_pv <- sapply(res_capm,`[[`,"beta_p")
beta_port <- sum(pesos*betas)
capm_df <- data.frame(
Acción = tickers,
`Peso (%)` = paste0(round(pesos*100,2),"%"),
`α mensual (%)` = round(alphas*100,5),
`β` = round(betas,4),
`SE(β)` = round(betas_se,4),
`p-valor β` = round(beta_pv,5),
`R²` = round(r2v,4),
`Contribución β` = round(pesos*betas,4),
check.names=FALSE, row.names=NULL
)
kable(capm_df,
caption="Tabla 8. Estimación CAPM por acción — regresión mensual sobre el S&P 500.",
align="c") %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"),
full_width=TRUE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white")| Acción | Peso (%) | α mensual (%) | β | SE(β) | p-valor β | R² | Contribución β |
|---|---|---|---|---|---|---|---|
| NVDA | 70% | 2.75728 | 1.8672 | 0.2162 | 0 | 0.3894 | 1.3070 |
| TSLA | 12.9% | 1.16840 | 1.7496 | 0.3141 | 0 | 0.2096 | 0.2257 |
| COST | 17.1% | 0.85798 | 0.7856 | 0.1028 | 0 | 0.3330 | 0.1343 |
bp_df <- data.frame(
Componente = c(tickers,"β Portafolio"),
`Peso (w)` = c(paste0(round(pesos*100,2),"%"),"100%"),
`Beta (β)` = round(c(betas,beta_port),4),
`w × β` = round(c(pesos*betas,beta_port),4),
check.names=FALSE, row.names=NULL
)
kable(bp_df,
caption="Tabla 9. Beta del portafolio como promedio ponderado (β_p = Σ wᵢ·βᵢ).",
align="c") %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=FALSE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(4, bold=TRUE, background="#eaf4d3")| Componente | Peso (w) | Beta (β) | w × β |
|---|---|---|---|
| NVDA | 70% | 1.8672 | 1.3070 |
| TSLA | 12.9% | 1.7496 | 0.2257 |
| COST | 17.1% | 0.7856 | 0.1343 |
| β Portafolio | 100% | 1.6671 | 1.6671 |
El beta del portafolio es β_p = 1.6671. Esto indica que por cada punto porcentual de variación en el S&P 500, el portafolio se mueve en la misma dirección aproximadamente 1.67 puntos porcentuales. NVDA y TSLA aportan la mayor sensibilidad al mercado por el comportamiento en sus rendimientos, mientras que COST, con un beta cercana a 0.7 se regula el efecto en el portafolio. El resultado neto es un portafolio más agresivo y con mayor riesgo que el mercado, lo que justifica una cobertura activa mediante futuros para proteger el capital en escenarios bajistas.
ret_mkt_a <- (1+mean(as.numeric(ret_mkt)))^fa - 1
prima_mkt <- ret_mkt_a - rf_anual
sml_x <- seq(0, max(betas)*1.25, length.out=200)
sml_df <- data.frame(Beta=sml_x, Ret=(rf_anual+sml_x*prima_mkt)*100)
pts_df <- data.frame(Acción=tickers, Beta=betas, Ret=ret_anual*100)
ggplot() +
geom_line(data=sml_df, aes(x=Beta,y=Ret), color="navy", size=1) +
geom_point(data=pts_df, aes(x=Beta,y=Ret,color=Acción), size=5) +
geom_text(data=pts_df, aes(x=Beta+0.07,y=Ret,label=Acción,color=Acción),
fontface="bold", size=4) +
annotate("point", x=beta_port, y=ret_port*100, color="black", size=5, shape=18) +
annotate("text", x=beta_port+0.07, y=ret_port*100,
label=sprintf("Portafolio\nβ=%.2f",beta_port), size=3.2) +
scale_color_manual(values=colores) +
labs(title="Security Market Line (SML) — Modelo CAPM",
subtitle=sprintf("Rf=%.2f%% | Prima de mercado=%.2f%%",
rf_anual*100, prima_mkt*100),
x="Beta (β)", y="Retorno esperado anual (%)",
caption="Fuente: Sharpe (1964). Datos: Yahoo Finance (2026). Elaboración propia.") +
theme_minimal(base_size=12) +
theme(plot.title=element_text(face="bold"), legend.position="none")Figura 7. Security Market Line (SML). Fuente: Sharpe (1964). Datos: Yahoo Finance (2026).
cont_df <- data.frame(
Especificación = c("Denominación","Ticker CME Globex","Activo subyacente",
"Plataforma","Multiplicador","Vencimientos",
"Precio F0 (30-abr-2026)","Valor nocional por contrato",
"Margen inicial (10% más del mrg de mtto)","Margen de mantenimiento",
"Tick mínimo","Liquidación","Mark-to-market"),
Valor = c("E-mini S&P 500","ES","S&P 500 Index","CME Globex",
"$50 por punto del índice",
"Trimestral: mar / jun / sep / dic",
paste0("USD ",format(round(F0,2),big.mark=",")),
paste0("USD ",format(round(val_nocional,0),big.mark=",")),
paste0("USD ",format(margen_ini,big.mark=",")),
paste0("USD ",format(margen_mant,big.mark=",")),
"0.25 puntos = USD 12.50",
"Cash-settled (efectivo)","Diario (académico: mensual)"),
`Fuente (APA)` = c(rep("CME Group, 2026",6),
"Yahoo Finance, 2026","Cálculo propio",
"Cálculo propio","CME Group, 2026",
rep("CME Group, 2026",3)),
check.names=FALSE, row.names=NULL
)
kable(cont_df,
caption="Tabla 10. Especificaciones del contrato E-mini S&P 500 (ES).",
align=c("l","l","l")) %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"),
full_width=TRUE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(c(7,8,9,10), background="#fef9f0", bold=TRUE)| Especificación | Valor | Fuente (APA) |
|---|---|---|
| Denominación | E-mini S&P 500 | CME Group, 2026 |
| Ticker CME Globex | ES | CME Group, 2026 |
| Activo subyacente | S&P 500 Index | CME Group, 2026 |
| Plataforma | CME Globex | CME Group, 2026 |
| Multiplicador | $50 por punto del índice | CME Group, 2026 |
| Vencimientos | Trimestral: mar / jun / sep / dic | CME Group, 2026 |
| Precio F0 (30-abr-2026) | USD 7,168 | Yahoo Finance, 2026 |
| Valor nocional por contrato | USD 358,400 | Cálculo propio |
| Margen inicial (10% más del mrg de mtto) | USD 26,447 | Cálculo propio |
| Margen de mantenimiento | USD 24,043 | CME Group, 2026 |
| Tick mínimo | 0.25 puntos = USD 12.50 | CME Group, 2026 |
| Liquidación | Cash-settled (efectivo) | CME Group, 2026 |
| Mark-to-market | Diario (académico: mensual) | CME Group, 2026 |
\[N^* = \beta_p \times \frac{V_p}{F_0 \times Q_F}\]
Vp <- capital; QF <- mult_contrato
N_exact <- beta_port * Vp / (F0 * QF)
N_floor <- floor(N_exact)
N_ceil <- ceiling(N_exact)
cob_fl <- N_floor * F0 * QF / Vp * 100
cob_ce <- N_ceil * F0 * QF / Vp * 100
N_usado <- N_ceil
margen_total <- N_usado * margen_ini
nc_df <- data.frame(
Parámetro = c("Beta portafolio (β_p)","Valor portafolio (Vp)",
"Precio futuro F0","Multiplicador (QF)",
"Valor nocional (F0 × QF)",
"N* exacto",
"N* redondeado hacia abajo","N* redondeado hacia arriba",
"Margen inicial total (N↑)",
"Cobertura efectiva (N↓)","Cobertura efectiva (N↑)"),
Valor = c(round(beta_port,4),
paste0("USD ",format(Vp,big.mark=",")),
paste0("USD ",format(round(F0,2),big.mark=",")),
paste0("$",QF," por punto"),
paste0("USD ",format(round(F0*QF,0),big.mark=",")),
round(N_exact,4), N_floor, N_ceil,
paste0("USD ",format(margen_total,big.mark=",")),
paste0(round(cob_fl,2),"%"),
paste0(round(cob_ce,2),"%")),
check.names=FALSE, row.names=NULL
)
kable(nc_df,
caption="Tabla 11. Cálculo del número óptimo de contratos de futuros ES.",
align=c("l","c")) %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=FALSE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(6, bold=TRUE, background="#fff3cd") %>%
row_spec(c(7,8), bold=TRUE, background="#eaf4d3")| Parámetro | Valor |
|---|---|
| Beta portafolio (β_p) | 1.6671 |
| Valor portafolio (Vp) | USD 20,000,000 |
| Precio futuro F0 | USD 7,168 |
| Multiplicador (QF) | $50 por punto |
| Valor nocional (F0 × QF) | USD 358,400 |
| N* exacto | 93.029 |
| N* redondeado hacia abajo | 93 |
| N* redondeado hacia arriba | 94 |
| Margen inicial total (N↑) | USD 2,486,018 |
| Cobertura efectiva (N↓) | 166.66% |
| Cobertura efectiva (N↑) | 168.45% |
para el portafolio a gestionar se toma la desición de considerar 94 contratos (redondeo hacia arriba). Debido a que la diferencia entre 93 y 94 contratos representa menos del 1% del valor nocional total, por lo que el costo de una mayor cobertura marginal es mínimo frente al beneficio de protección completa. En gestión de riesgo, cuando el objetivo es cubrir una posición larga en acciones frente a caídas del mercado, se prefiere una cobertura ligeramente superior a una menor cobertura que deje capital expuesto.
Un inversionista que desee realizar inversiones sobre acciones y quiere protegerse frente a caídas del mercado toma una posición corta en futuros sobre el índice. La lógica es directa: si el mercado baja, el portafolio de acciones pierde valor, pero la posición corta en futuros genera una ganancia que compensa esa pérdida. Si el mercado sube, ocurre lo contrario — las acciones se valorizan, pero la posición corta genera una pérdida que reduce la ganancia neta.
pos_df <- data.frame(
Situación = c("Mercado sube","Mercado baja",
"Mercado sube","Mercado baja"),
`Posición en futuros` = c("Corta","Corta","Larga","Larga"),
`Efecto sobre portafolio acciones` = c("Gana","Pierde","Pierde","Gana"),
`Efecto sobre posición futuros` = c("Pierde","Gana","Gana","Pierde"),
`Resultado neto` = c("Parcialmente cubierto",
"Pérdida compensada",
"Ganancia reducida",
"Pérdida amplificada"),
check.names=FALSE, row.names=NULL
)
kable(pos_df,
caption="Tabla 12. Análisis de escenarios: posición corta vs. larga en futuros.",
align=c("l","c","c","c","l")) %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"),
full_width=TRUE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(c(1,3), background="#f0fef4") %>%
row_spec(c(2,4), background="#fef0f0")| Situación | Posición en futuros | Efecto sobre portafolio acciones | Efecto sobre posición futuros | Resultado neto |
|---|---|---|---|---|
| Mercado sube | Corta | Gana | Pierde | Parcialmente cubierto |
| Mercado baja | Corta | Pierde | Gana | Pérdida compensada |
| Mercado sube | Larga | Pierde | Gana | Ganancia reducida |
| Mercado baja | Larga | Gana | Pierde | Pérdida amplificada |
Para este portafolio se adopta posición corta en 94 contratos ES. ya que el riesgo que se cubre es el riesgo sistemático del mercado, medido por la beta del portafolio (1.6671). El riesgo de cada acción no puede cubrirse con futuros sobre índice porque no está correlacionado con el movimiento del S&P 500.
La posición larga en futuros se usaría en el caso contrario: un gestor que estima recibir capital en el futuro y quiere asegurar hoy el precio de compra de acciones, o un fondo que quiere incrementar su exposición al mercado sin comprar acciones directamente. En el contexto de este ejercicio, donde ya se poseen las acciones, la posición sugerida es la corta.
# ── Paso 1: intentar descargar precios reales del futuro ES 2026-2030 ─────────
p_fut_raw <- tryCatch({
r <- getSymbols("ES=F", src = "yahoo",
from = fecha_inv_ini,
to = fecha_inv_ini + years(4),
auto.assign = FALSE, warnings = FALSE)
m <- to.monthly(Ad(r), indexAt = "lastof", OHLC = FALSE)
colnames(m) <- "ES"
m <- na.omit(m)
if (nrow(m) < 6) stop("pocos datos reales")
m
}, error = function(e) NULL)
# ── Paso 2: si no hay datos reales suficientes, simular precios del futuro ────
# usando MBG calibrado con la volatilidad histórica del S&P 500 y una
# tendencia moderada (10% anual) — escenario base para la cobertura.
# Esto garantiza variación realista del saldo de margen con llamados al margen.
set.seed(42)
n_meses_cob <- 48
fechas_cob <- seq(fecha_inv_ini %m+% months(1),
by = "month", length.out = n_meses_cob)
if (!is.null(p_fut_raw) && nrow(p_fut_raw) >= 6) {
# Usar datos reales disponibles y completar con simulación el resto
precios_fut_vec <- as.numeric(p_fut_raw[, 1])
fechas_reales <- index(p_fut_raw)
n_real <- length(precios_fut_vec)
cat(sprintf("Datos reales ES: %d meses. Resto simulado.\n", n_real))
# Completar con simulación desde el último precio real
if (n_real < n_meses_cob) {
S_last <- tail(precios_fut_vec, 1)
sigma_m <- sd(diff(log(precios_fut_vec))) * sqrt(12) # vol anual
mu_sim <- 0.10
dt <- 1/12
n_faltan <- n_meses_cob - n_real
precios_sim <- numeric(n_faltan)
S_cur <- S_last
for (k in seq_len(n_faltan)) {
S_cur <- S_cur * exp((mu_sim - 0.5*sigma_m^2)*dt +
sigma_m*sqrt(dt)*rnorm(1))
precios_sim[k] <- S_cur
}
precios_fut_vec <- c(precios_fut_vec, precios_sim)
} else {
precios_fut_vec <- precios_fut_vec[seq_len(n_meses_cob)]
}
} else {
# Simulación completa: empezar desde F0 con tendencia moderada (10% anual)
# y volatilidad histórica del S&P 500 (~18% anual), representativa del
# comportamiento esperado del índice en el horizonte de inversión.
cat("Usando simulación completa del precio del futuro ES (2026-2030).\n")
sigma_sp <- as.numeric(sd_anual["COST"]) * 0.6 # proxy vol índice ~18% anual
sigma_sp <- max(sigma_sp, 0.15); sigma_sp <- min(sigma_sp, 0.25)
mu_fut <- 0.10
dt <- 1/12
precios_fut_vec <- numeric(n_meses_cob)
S_cur <- F0
for (k in seq_len(n_meses_cob)) {
S_cur <- S_cur * exp((mu_fut - 0.5*sigma_sp^2)*dt +
sigma_sp*sqrt(dt)*rnorm(1))
precios_fut_vec[k] <- S_cur
}
}## Usando simulación completa del precio del futuro ES (2026-2030).
# ── Paso 3: construir tabla_fut con precio inicial y final por mes ────────────
tabla_fut <- data.frame(
Mes_num = seq_len(n_meses_cob),
Fecha = fechas_cob,
Precio_fin_mes = round(precios_fut_vec, 2)
) %>%
mutate(
Precio_ini_mes = c(F0, head(Precio_fin_mes, -1)),
Variacion_pts = Precio_fin_mes - Precio_ini_mes,
GP_largo_cont = Variacion_pts * QF,
GP_corto_cont = -GP_largo_cont,
GP_largo_total = GP_largo_cont * N_usado,
GP_corto_total = GP_corto_cont * N_usado
)
# ── Paso 4: simular cuenta de margen ─────────────────────────────────────────
margen_ini_total <- N_usado * margen_ini
margen_mant_total <- N_usado * margen_mant
saldo_antes <- numeric(n_meses_cob)
saldo_despues <- numeric(n_meses_cob)
llamado <- logical(n_meses_cob)
repos <- numeric(n_meses_cob)
saldo_act <- margen_ini_total
for (i in seq_len(n_meses_cob)) {
saldo_act <- saldo_act + tabla_fut$GP_corto_total[i]
saldo_antes[i] <- saldo_act # variación real ANTES de reponer
if (saldo_act < margen_mant_total) {
llamado[i] <- TRUE
repos[i] <- margen_ini_total - saldo_act
saldo_act <- margen_ini_total # se restituye al nivel inicial
}
saldo_despues[i] <- saldo_act
}
tabla_fut <- tabla_fut %>%
mutate(
Saldo_antes_repos = saldo_antes,
Saldo_margen = saldo_despues,
Llamado_margen = llamado,
Reposicion_USD = repos
)
n_llamados_total <- sum(llamado)
cat(sprintf("Llamados al margen identificados: %d de %d meses\n",
n_llamados_total, n_meses_cob))## Llamados al margen identificados: 7 de 48 meses
# ── Tabla completa con TODOS los meses del horizonte ─────────────────────────
filas_llamado <- which(tabla_fut$Llamado_margen)
disp_df <- tabla_fut %>%
mutate(
Mes_label = format(Fecha, "%b-%Y"),
Precio_ini_mes = formatC(Precio_ini_mes, format="f", digits=2, big.mark=","),
Precio_fin_mes = formatC(Precio_fin_mes, format="f", digits=2, big.mark=","),
Variacion_pts = formatC(Variacion_pts, format="f", digits=2, big.mark=","),
GP_corto_label = paste0(ifelse(GP_corto_total >= 0, "+", ""),
formatC(round(GP_corto_total,0),
format="d", big.mark=",")),
Saldo_antes_label = paste0("$", formatC(round(Saldo_antes_repos,0),
format="d", big.mark=",")),
Saldo_final_label = paste0("$", formatC(round(Saldo_margen,0),
format="d", big.mark=",")),
Llamado_label = ifelse(Llamado_margen, "SI", "No"),
Repos_label = ifelse(Reposicion_USD > 0,
paste0("$", formatC(round(Reposicion_USD,0),
format="d", big.mark=",")),
"-")
) %>%
select(Mes_label, Precio_ini_mes, Precio_fin_mes, Variacion_pts,
GP_corto_label, Saldo_antes_label, Saldo_final_label,
Llamado_label, Repos_label)
kable(disp_df,
caption = paste0("Tabla 13. Flujos mensuales completos de la posicion corta: ",
N_usado, " contratos ES | ", n_meses_cob,
" meses | ", n_llamados_total, " llamados al margen."),
align = c("l","r","r","r","r","r","r","c","r")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 11) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white") %>%
row_spec(filas_llamado, background = "#ffe0e0", bold = TRUE) %>%
column_spec(8, bold = TRUE,
color = ifelse(tabla_fut$Llamado_margen, "#a93226", "gray40")) %>%
scroll_box(height = "480px", width = "100%")| Mes_label | Precio_ini_mes | Precio_fin_mes | Variacion_pts | GP_corto_label | Saldo_antes_label | Saldo_final_label | Llamado_label | Repos_label |
|---|---|---|---|---|---|---|---|---|
| Jun-2026 | 7,168.00 | 7,662.87 | 494.87 | -2,325,889 | $160,129 | $2,486,018 | SI | $2,325,889 |
| Jul-2026 | 7,662.87 | 7,533.28 | -129.59 | +609,073 | $3,095,091 | $3,095,091 | No |
|
| Aug-2026 | 7,533.28 | 7,709.48 | 176.20 | -828,140 | $2,266,951 | $2,266,951 | No |
|
| Sep-2026 | 7,709.48 | 7,982.49 | 273.01 | -1,283,147 | $983,804 | $2,486,018 | SI | $1,502,214 |
| Oct-2026 | 7,982.49 | 8,183.75 | 201.26 | -945,922 | $1,540,096 | $2,486,018 | SI | $945,922 |
| Nov-2026 | 8,183.75 | 8,206.70 | 22.95 | -107,865 | $2,378,153 | $2,378,153 | No |
|
| Dec-2026 | 8,206.70 | 8,826.85 | 620.15 | -2,914,705 | $-536,552 | $2,486,018 | SI | $3,022,570 |
| Jan-2027 | 8,826.85 | 8,856.00 | 29.15 | -137,005 | $2,349,013 | $2,349,013 | No |
|
| Feb-2027 | 8,856.00 | 9,736.59 | 880.59 | -4,138,773 | $-1,789,760 | $2,486,018 | SI | $4,275,778 |
| Mar-2027 | 9,736.59 | 9,782.27 | 45.68 | -214,696 | $2,271,322 | $2,271,322 | No |
|
| Apr-2027 | 9,782.27 | 10,427.74 | 645.47 | -3,033,709 | $-762,387 | $2,486,018 | SI | $3,248,405 |
| May-2027 | 10,427.74 | 11,598.55 | 1,170.81 | -5,502,807 | $-3,016,789 | $2,486,018 | SI | $5,502,807 |
| Jun-2027 | 11,598.55 | 11,002.66 | -595.89 | +2,800,683 | $5,286,701 | $5,286,701 | No |
|
| Jul-2027 | 11,002.66 | 10,951.33 | -51.33 | +241,251 | $5,527,952 | $5,527,952 | No |
|
| Aug-2027 | 10,951.33 | 10,969.11 | 17.78 | -83,566 | $5,444,386 | $5,444,386 | No |
|
| Sep-2027 | 10,969.11 | 11,359.07 | 389.96 | -1,832,812 | $3,611,574 | $3,611,574 | No |
|
| Oct-2027 | 11,359.07 | 11,303.41 | -55.66 | +261,602 | $3,873,176 | $3,873,176 | No |
|
| Nov-2027 | 11,303.41 | 10,149.98 | -1,153.43 | +5,421,121 | $9,294,297 | $9,294,297 | No |
|
| Dec-2027 | 10,149.98 | 9,199.90 | -950.08 | +4,465,376 | $13,759,673 | $13,759,673 | No |
|
| Jan-2028 | 9,199.90 | 9,813.42 | 613.52 | -2,883,544 | $10,876,129 | $10,876,129 | No |
|
| Feb-2028 | 9,813.42 | 9,755.87 | -57.55 | +270,485 | $11,146,614 | $11,146,614 | No |
|
| Mar-2028 | 9,755.87 | 9,098.70 | -657.17 | +3,088,699 | $14,235,313 | $14,235,313 | No |
|
| Apr-2028 | 9,098.70 | 9,098.26 | -0.44 | +2,068 | $14,237,381 | $14,237,381 | No |
|
| May-2028 | 9,098.26 | 9,660.80 | 562.54 | -2,643,938 | $11,593,443 | $11,593,443 | No |
|
| Jun-2028 | 9,660.80 | 10,564.89 | 904.09 | -4,249,223 | $7,344,220 | $7,344,220 | No |
|
| Jul-2028 | 10,564.89 | 10,446.76 | -118.13 | +555,211 | $7,899,431 | $7,899,431 | No |
|
| Aug-2028 | 10,446.76 | 10,407.72 | -39.04 | +183,488 | $8,082,919 | $8,082,919 | No |
|
| Sep-2028 | 10,407.72 | 9,714.28 | -693.44 | +3,259,168 | $11,342,087 | $11,342,087 | No |
|
| Oct-2028 | 9,714.28 | 9,983.32 | 269.04 | -1,264,488 | $10,077,599 | $10,077,599 | No |
|
| Nov-2028 | 9,983.32 | 9,782.53 | -200.79 | +943,713 | $11,021,312 | $11,021,312 | No |
|
| Dec-2028 | 9,782.53 | 10,051.44 | 268.91 | -1,263,877 | $9,757,435 | $9,757,435 | No |
|
| Jan-2029 | 10,051.44 | 10,439.87 | 388.43 | -1,825,621 | $7,931,814 | $7,931,814 | No |
|
| Feb-2029 | 10,439.87 | 10,999.49 | 559.62 | -2,630,214 | $5,301,600 | $5,301,600 | No |
|
| Mar-2029 | 10,999.49 | 10,792.78 | -206.71 | +971,537 | $6,273,137 | $6,273,137 | No |
|
| Apr-2029 | 10,792.78 | 11,113.26 | 320.48 | -1,506,256 | $4,766,881 | $4,766,881 | No |
|
| May-2029 | 11,113.26 | 10,393.56 | -719.70 | +3,382,590 | $8,149,471 | $8,149,471 | No |
|
| Jun-2029 | 10,393.56 | 10,121.01 | -272.55 | +1,280,985 | $9,430,456 | $9,430,456 | No |
|
| Jul-2029 | 10,121.01 | 9,827.30 | -293.71 | +1,380,437 | $10,810,893 | $10,810,893 | No |
|
| Aug-2029 | 9,827.30 | 8,917.56 | -909.74 | +4,275,778 | $15,086,671 | $15,086,671 | No |
|
| Sep-2029 | 8,917.56 | 8,997.82 | 80.26 | -377,222 | $14,709,449 | $14,709,449 | No |
|
| Oct-2029 | 8,997.82 | 9,145.83 | 148.01 | -695,647 | $14,013,802 | $14,013,802 | No |
|
| Nov-2029 | 9,145.83 | 9,070.79 | -75.04 | +352,688 | $14,366,490 | $14,366,490 | No |
|
| Dec-2029 | 9,070.79 | 9,443.10 | 372.31 | -1,749,857 | $12,616,633 | $12,616,633 | No |
|
| Jan-2030 | 9,443.10 | 9,218.51 | -224.59 | +1,055,573 | $13,672,206 | $13,672,206 | No |
|
| Feb-2030 | 9,218.51 | 8,752.69 | -465.82 | +2,189,354 | $15,861,560 | $15,861,560 | No |
|
| Mar-2030 | 8,752.69 | 8,984.47 | 231.78 | -1,089,366 | $14,772,194 | $14,772,194 | No |
|
| Apr-2030 | 8,984.47 | 8,738.68 | -245.79 | +1,155,213 | $15,927,407 | $15,927,407 | No |
|
| May-2030 | 8,738.68 | 9,371.63 | 632.95 | -2,974,865 | $12,952,542 | $12,952,542 | No |
|
# ── Tabla 13b: detalle exacto de cada llamado al margen ──────────────────────
if (n_llamados_total > 0) {
df_llamados <- tabla_fut %>%
filter(Llamado_margen == TRUE) %>%
mutate(
Mes_exact = format(Fecha, "%B %Y"),
F_fin = round(Precio_fin_mes, 2),
Saldo_previo = paste0("$", formatC(round(Saldo_antes_repos,0),
format="d", big.mark=",")),
Repos_req = paste0("$", formatC(round(Reposicion_USD,0),
format="d", big.mark=",")),
Saldo_rest = paste0("$", formatC(margen_ini_total,
format="d", big.mark=","))
) %>%
select(Mes_exact, F_fin, Saldo_previo, Repos_req, Saldo_rest)
kable(df_llamados,
caption = paste0("Tabla 13b. Detalle de los ", n_llamados_total,
" llamados al margen — mes, saldo y monto de reposición."),
align = c("l","c","r","r","r"),
col.names = c("Mes del llamado","Precio futuro",
"Saldo antes de reponer","Monto reposición",
"Saldo tras reposición")) %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE) %>%
row_spec(0, bold = TRUE, background = "#a93226", color = "white") %>%
row_spec(seq_len(nrow(df_llamados)), background = "#ffe0e0", bold = TRUE)
} else {
cat(paste0("Durante el horizonte de 48 meses no se registraron llamados ",
"al margen. Esto ocurre cuando el mercado no genera movimientos ",
"mensuales superiores a la diferencia entre margen inicial y de ",
"mantenimiento (USD ",
formatC(margen_ini - margen_mant, format="d", big.mark=","),
" por contrato)."))
}| Mes del llamado | Precio futuro | Saldo antes de reponer | Monto reposición | Saldo tras reposición |
|---|---|---|---|---|
| June 2026 | 7662.87 | $160,129 | $2,325,889 | $2,486,018 |
| September 2026 | 7982.49 | $983,804 | $1,502,214 | $2,486,018 |
| October 2026 | 8183.75 | $1,540,096 | $945,922 | $2,486,018 |
| December 2026 | 8826.85 | $-536,552 | $3,022,570 | $2,486,018 |
| February 2027 | 9736.59 | $-1,789,760 | $4,275,778 | $2,486,018 |
| April 2027 | 10427.74 | $-762,387 | $3,248,405 | $2,486,018 |
| May 2027 | 11598.55 | $-3,016,789 | $5,502,807 | $2,486,018 |
# Construir etiquetas de eje X cada 6 meses
breaks_x <- seq(1, n_meses_cob, by = 6)
labels_x <- format(tabla_fut$Fecha[breaks_x], "%b-%y")
# Datos de llamados al margen para puntos y etiquetas
df_llamados_graf <- tabla_fut %>%
filter(Llamado_margen == TRUE)
ggplot(tabla_fut, aes(x = Mes_num)) +
# Área sombreada bajo la curva
geom_ribbon(aes(ymin = margen_mant_total / 1e6,
ymax = Saldo_antes_repos / 1e6),
fill = "#4a90d9", alpha = 0.12) +
# Línea principal del saldo
geom_line(aes(y = Saldo_antes_repos / 1e6),
color = "#0055aa", size = 0.9) +
# Línea margen de mantenimiento
geom_hline(yintercept = margen_mant_total / 1e6,
linetype = "dashed", color = "#d73027", size = 1.0) +
# Línea margen inicial
geom_hline(yintercept = margen_ini_total / 1e6,
linetype = "dotted", color = "#444444", size = 0.8) +
# Zona de peligro (entre mantenimiento y cero)
annotate("rect",
xmin = 1, xmax = n_meses_cob,
ymin = 0, ymax = margen_mant_total / 1e6,
fill = "#d73027", alpha = 0.04) +
# Puntos de llamado al margen
{if (nrow(df_llamados_graf) > 0)
geom_point(data = df_llamados_graf,
aes(x = Mes_num, y = Saldo_antes_repos / 1e6),
color = "#d73027", size = 5, shape = 25,
fill = "#d73027")
} +
# Etiquetas de llamados al margen
{if (nrow(df_llamados_graf) > 0)
geom_label(data = df_llamados_graf,
aes(x = Mes_num, y = Saldo_antes_repos / 1e6,
label = format(Fecha, "%b\n%y")),
color = "#a93226", size = 2.6,
vjust = 1.8, label.size = 0.2,
fill = "white", fontface = "bold")
} +
# Anotaciones de líneas de referencia (lado derecho)
annotate("text",
x = n_meses_cob - 1,
y = margen_mant_total / 1e6 * 1.03,
label = paste0("Marg. mant.: $",
formatC(round(margen_mant_total/1e3,0),
format="d", big.mark=","), "K"),
color = "#d73027", size = 3.0, hjust = 1, fontface = "bold") +
annotate("text",
x = n_meses_cob - 1,
y = margen_ini_total / 1e6 * 1.02,
label = paste0("Marg. inicial: $",
formatC(round(margen_ini_total/1e3,0),
format="d", big.mark=","), "K"),
color = "#444444", size = 3.0, hjust = 1) +
scale_x_continuous(breaks = breaks_x, labels = labels_x) +
scale_y_continuous(labels = function(x)
paste0("$", formatC(x, format="f", digits=1), "M")) +
labs(
title = "Cuenta de margen — posición corta en futuros ES (2026-2030)",
subtitle = paste0(
n_llamados_total, " llamado(s) al margen detectado(s) | ",
"Saldo mostrado ANTES de reposición | ",
"Banda azul = variación mensual del saldo"
),
x = NULL,
y = "Saldo (USD millones)",
caption = paste0(
"Fuente: Elaboración propia. Llamado al margen: saldo cae bajo $",
formatC(margen_mant_total, format="d", big.mark=","),
" (margen de mantenimiento total)."
)
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(color = "gray40", size = 9.5),
plot.caption = element_text(color = "gray50", size = 8.5),
axis.text.x = element_text(angle = 35, hjust = 1, size = 9),
panel.grid.minor = element_blank()
)Figura 8. Evolución del saldo de la cuenta de margen — posición corta (2026-2030). Elaboración propia.
Durante los primeros meses del horizonte se observan 7 llamados al margen, concentrados entre mayo de 2026 y marzo de 2027. Esto ocurre porque el saldo cae por debajo del margen de mantenimiento, lo que implica que la posición corta tuvo pérdidas por aumentos en el precio del futuro. En estos casos debemos reponer recursos hasta restablecer la cuenta al nivel del margen inicial.
A partir de 2027, el saldo de la cuenta aumenta considerablemente y se mantiene por encima de los niveles mínimos. Lo que indica que la posición corta empieza a generar ganancias acumuladas. En consecuencia, no se presentan nuevos llamados al margen durante la mayor parte del período. En conclusión, la gráfica evidencia que la cobertura con futuros requiere una gestión activa de liquidez, especialmente al inicio. Aunque la posición corta protege el portafolio ante caídas del mercado, también puede generar pérdidas temporales y llamados al margen cuando el mercado sube, por lo que es necesario mantener recursos disponibles para cumplir con las garantías exigidas.
Los contratos de futuros sobre índice tienen vencimientos trimestrales (marzo, junio, septiembre y diciembre). Para mantener la cobertura durante los cuatro años del horizonte de inversión, es necesario cerrar la posición al final de cada trimestre y abrir una nueva posición en el contrato del trimestre siguiente. Este proceso se denomina roll-over y genera ganancias o pérdidas realizadas en cada renovación.
# ── Roll-over: agrupar por trimestre sin backtick-names con caracteres especiales
tabla_roll_raw <- tabla_fut %>%
mutate(
anio = lubridate::year(Fecha),
trimestre = lubridate::quarter(Fecha),
etiqueta = paste0(anio, "-T", trimestre)
) %>%
group_by(etiqueta) %>%
summarise(
F_ini_trim = round(first(Precio_ini_mes), 2),
F_cie_trim = round(last(Precio_fin_mes), 2),
GP_corta_usd = round(sum(GP_corto_total), 2),
GP_larga_usd = round(sum(GP_largo_total), 2),
n_llamados = sum(Llamado_margen),
repos_usd = round(sum(Reposicion_USD), 2),
.groups = "drop"
) %>%
mutate(
# Riesgo de base: diferencia entre F cierre del trimestre anterior
# y F inicio del trimestre actual (precio del nuevo contrato)
riesgo_base = round(F_ini_trim - lag(F_cie_trim), 2)
)
# Renombrar para presentación limpia
tabla_roll <- tabla_roll_raw %>%
rename(
Trimestre = etiqueta,
`F inicio` = F_ini_trim,
`F cierre` = F_cie_trim,
`G/P corta (USD)` = GP_corta_usd,
`G/P larga (USD)` = GP_larga_usd,
`Llamados margen` = n_llamados,
`Reposicion (USD)` = repos_usd,
`Riesgo de base` = riesgo_base
)
# Filas con resultado negativo en posición corta (mercado subió ese trimestre)
filas_neg <- which(tabla_roll$`G/P corta (USD)` < 0)
filas_pos <- which(tabla_roll$`G/P corta (USD)` >= 0)
kable(tabla_roll,
caption = "Tabla 14. Roll-over trimestral — resultados de la posición corta.",
align = c("c","r","r","r","r","c","r","r")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white") %>%
row_spec(filas_pos, background = "#f0fef4") %>%
row_spec(filas_neg, background = "#fef0f0") %>%
column_spec(4, bold = TRUE,
color = ifelse(tabla_roll$`G/P corta (USD)` >= 0,
"#0a6640", "#a93226"))| Trimestre | F inicio | F cierre | G/P corta (USD) | G/P larga (USD) | Llamados margen | Reposicion (USD) | Riesgo de base |
|---|---|---|---|---|---|---|---|
| 2026-T2 | 7168.00 | 7662.87 | -2325889 | 2325889 | 1 | 2325889 | NA |
| 2026-T3 | 7662.87 | 7982.49 | -1502214 | 1502214 | 1 | 1502214 | 0 |
| 2026-T4 | 7982.49 | 8826.85 | -3968492 | 3968492 | 2 | 3968492 | 0 |
| 2027-T1 | 8826.85 | 9782.27 | -4490474 | 4490474 | 1 | 4275778 | 0 |
| 2027-T2 | 9782.27 | 11002.66 | -5735833 | 5735833 | 2 | 8751212 | 0 |
| 2027-T3 | 11002.66 | 11359.07 | -1675127 | 1675127 | 0 | 0 | 0 |
| 2027-T4 | 11359.07 | 9199.90 | 10148099 | -10148099 | 0 | 0 | 0 |
| 2028-T1 | 9199.90 | 9098.70 | 475640 | -475640 | 0 | 0 | 0 |
| 2028-T2 | 9098.70 | 10564.89 | -6891093 | 6891093 | 0 | 0 | 0 |
| 2028-T3 | 10564.89 | 9714.28 | 3997867 | -3997867 | 0 | 0 | 0 |
| 2028-T4 | 9714.28 | 10051.44 | -1584652 | 1584652 | 0 | 0 | 0 |
| 2029-T1 | 10051.44 | 10792.78 | -3484298 | 3484298 | 0 | 0 | 0 |
| 2029-T2 | 10792.78 | 10121.01 | 3157319 | -3157319 | 0 | 0 | 0 |
| 2029-T3 | 10121.01 | 8997.82 | 5278993 | -5278993 | 0 | 0 | 0 |
| 2029-T4 | 8997.82 | 9443.10 | -2092816 | 2092816 | 0 | 0 | 0 |
| 2030-T1 | 9443.10 | 8984.47 | 2155561 | -2155561 | 0 | 0 | 0 |
| 2030-T2 | 8984.47 | 9371.63 | -1819652 | 1819652 | 0 | 0 | 0 |
El riesgo de base surge porque el precio de cierre del contrato vencido y el precio de apertura del contrato entrante no siempre coinciden. Esa diferencia es la fuente de imperfección de la cobertura: si el basis se amplía en contra de la posición, el roll-over genera una pérdida adicional que no existiría si los contratos coincidieran exactamente con el horizonte de inversión.
Si el inversionista mantuviera siempre posición corta: cuando el mercado sube frecuentemente, acumularía pérdidas en la posición de futuros que reducirían la rentabilidad neta del portafolio por debajo del rendimiento de las acciones. Si mantuviera siempre posición larga: ante mercados alcistas ganaría en futuros y en acciones igualmente, pero en mercados bajistas las pérdidas se aumentarian en ambas posiciones y esto contradice el objetivo de cobertura.
tasa_libre_trim <- (1 + rf_anual)^(1/4) - 1
ret_ind_anual <- (1 + mean(as.numeric(ret_mkt)))^fa - 1
prima_mkt_anual <- ret_ind_anual - rf_anual
prima_mkt_trim <- (1 + prima_mkt_anual)^(1/4) - 1
ret_port_trim <- (1 + ret_port)^(1/4) - 1
ret_cub_trim <- ret_port_trim - beta_port*prima_mkt_trim + tasa_libre_trim
comp_df <- data.frame(
Enfoque = c("Sin cobertura","Con cobertura"),
`Retorno trim. esperado` = paste0(round(c(ret_port_trim,ret_cub_trim)*100,4),"%"),
`Valor esperado (USD)` = paste0("$",
format(round(capital*(1+c(ret_port_trim,ret_cub_trim)),0),big.mark=",")),
check.names=FALSE, row.names=NULL
)
kable(comp_df,
caption="Tabla 15. Valor esperado trimestral del portafolio con y sin cobertura.",
align=c("l","c","c")) %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=FALSE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(2, background="#eaf4d3", bold=TRUE)| Enfoque | Retorno trim. esperado | Valor esperado (USD) |
|---|---|---|
| Sin cobertura | 11.7288% | $22,345,763 |
| Con cobertura | 9.1817% | $21,836,336 |
Esta tabla compara el valor esperado trimestral del portafolio con y sin cobertura. El escenario sin cobertura presenta un retorno esperado mayor, de 11.7288%, equivalente a un valor proyectado de USD 22,345,763. En cambio, el portafolio con cobertura reduce el retorno esperado a 9.1817%, con un valor proyectado de USD 21,836,336.
Esta diferencia refleja el costo económico de cubrir el riesgo. la cobertura con futuros disminuye la exposición a caídas del mercado, pero también limita parte de las ganancias potenciales cuando el mercado sube. La tasa libre de riesgo utilizada corresponde al CBOE Interest Rate 3–5 Year Treasury Index (^TNX) al 30 de abril de 2026, de aproximadamente 4.2% anual(Yahoo Finance / CBOE, 2026). La cobertura no destruye valor sino que intercambia retorno esperado por reducción de riesgo, que es lo que se busca en esta estrategia.
En conclusión, la estrategia cubierta sacrifica aproximadamente 2.55 puntos porcentuales de retorno trimestral esperado a cambio de una mayor protección frente a escenarios adversos.
La tabla compara el valor esperado mensual del portafolio bajo cuatro enfoques de riesgo y cobertura. El portafolio sin cobertura presenta el mayor retorno esperado, con 3.7097% mensual, equivalente a USD 20,741,939, reflejando el rendimiento esperado sin reducir la exposición al mercado.
Al incorporar la cobertura con futuros, el retorno esperado disminuye a 2.921%, con un valor proyectado de USD 20,584,195. Esta reducción es consistente con el objetivo de la cobertura: proteger el portafolio frente a caídas del mercado, aunque sacrificando parte de la rentabilidad esperada.
ret_m_med_port <- mean(ret_port_s)
prima_mkt_m <- mean(as.numeric(ret_mkt)) - rf_mensual
tasa_libre_m_eff <- (1 + rf_anual)^(1/12) - 1
ret_cub_m <- ret_m_med_port - beta_port*prima_mkt_m + tasa_libre_m_eff
enf_df <- data.frame(
Enfoque = c("Sin cobertura",
"Cubierto con futuros",
"Ajustado por VaR 95%",
"Ajustado por VaR 99%"),
`Retorno mensual` = paste0(round(c(ret_m_med_port,
ret_cub_m,
ret_m_med_port - Vp95p,
ret_m_med_port - Vp99p)*100,4),"%"),
`Valor esperado (USD)` = paste0("$",
format(round(capital*(1+c(ret_m_med_port,
ret_cub_m,
ret_m_med_port - Vp95p,
ret_m_med_port - Vp99p)),0),
big.mark=",")),
check.names=FALSE, row.names=NULL
)
kable(enf_df,
caption="Tabla 16. Valor esperado mensual del portafolio bajo tres enfoques.",
align=c("l","c","c")) %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"),
full_width=TRUE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(1, background="#f0f7fe") %>%
row_spec(2, background="#eaf4d3") %>%
row_spec(c(3,4), background="#fef9f0")| Enfoque | Retorno mensual | Valor esperado (USD) |
|---|---|---|
| Sin cobertura | 3.7097% | $20,741,939 |
| Cubierto con futuros | 2.921% | $20,584,195 |
| Ajustado por VaR 95% | -10.1687% | $17,966,270 |
| Ajustado por VaR 99% | -17.4557% | $16,508,857 |
Efecto de dividendos: COST distribuye un dividendo regular de aproximadamente 0.6% anual, equivalente a unos USD 0.15 por acción cada trimestre. Los precios ajustados utilizados en los cálculos ya incorporan este rendimiento, de modo que los retornos representan el retorno total de la inversión (apreciación de precio más dividendos). Para NVDA y TSLA, que no distribuyen dividendos en el período analizado, el precio ajustado coincide con el precio de cierre. El dividendo de COST mejora marginalmente el retorno total del portafolio respecto a un portafolio compuesto exclusivamente por acciones de crecimiento.
Para complementar el análisis puntual, se simula la evolución del portafolio a lo largo del horizonte de inversión bajo tres escenarios de mercado mediante el modelo de Black-Scholes-Merton de movimiento browniano geométrico:
\[S_t = S_0 \exp\left[\left(\mu - \frac{\sigma^2}{2}\right)t + \sigma \sqrt{t}\, Z\right], \quad Z \sim N(0,1)\]
# ── Función de simulación MBG (movimiento browniano geométrico) ───────────────
simular_trayectorias <- function(S0, mu_a, sigma_a, meses = 48, n = 1000) {
set.seed(123)
dt <- 1 / 12
M <- matrix(NA, nrow = meses + 1, ncol = n)
M[1, ] <- S0
for (j in 1:n)
for (t in 2:(meses + 1))
M[t, j] <- M[t-1, j] * exp(
(mu_a - 0.5 * sigma_a^2) * dt + sigma_a * sqrt(dt) * rnorm(1)
)
M
}
# ── Precios iniciales (01-may-2026) ──────────────────────────────────────────
ultimos_p <- c(
NVDA = 198.45,
TSLA = 390.82,
COST = 1011.72
)
# Garantizar que el orden coincida con el vector tickers
ultimos_p <- ultimos_p[tickers]
# ── Volatilidades históricas por acción ──────────────────────────────────────
sigma_sim <- as.numeric(sd_anual)
names(sigma_sim) <- tickers
# ── Deriva de cada escenario ─────────────────────────────────────────────────
# Los escenarios se construyen de forma CONSISTENTE entre sí y con los datos
# históricos de mercado, sin depender del nivel de mu_historico de cada acción:
#
# Alcista : retorno del S&P 500 en su percentil 75% histórico anual ~ +20%
# para acciones growth (NVDA/TSLA) se permite hasta +25%
# para COST (defensiva) se permite hasta +18%
# Moderado : retorno esperado histórico del S&P 500 anualizado ~ +10%
# consistente con el largo plazo del índice 2016-2026
# Bajista : caída moderada, consistente con correcciones históricas ~ -8%
# sin llegar a escenario de crisis sistémica
#
# Este enfoque evita que el mu_historico de NVDA (>80% anual 2016-2026)
# distorsione la simulación hacia valores terminales irreales.
param_sim <- data.frame(
accion = tickers,
S0 = as.numeric(ultimos_p),
sigma = sigma_sim,
mu_base = as.numeric(ret_anual), # retorno hist. para referencia
stringsAsFactors = FALSE
) %>%
mutate(
# Alcista: mercado favorable, con tope razonable por acción
mu_alcista = case_when(
accion == "NVDA" ~ 0.25, # 25% anual — alto crecimiento IA
accion == "TSLA" ~ 0.22, # 22% anual — expansión EV
accion == "COST" ~ 0.14 # 14% anual — consumo sólido
),
# Moderado: retorno esperado de largo plazo del S&P 500
mu_moderado = case_when(
accion == "NVDA" ~ 0.12, # 12% — alineado con prima tecnológica
accion == "TSLA" ~ 0.10, # 10% — crecimiento normalizado
accion == "COST" ~ 0.10 # 10% — consumo básico estable
),
# Bajista: corrección de mercado sin crisis sistémica
mu_bajista = case_when(
accion == "NVDA" ~ -0.08, # -8% — corrección valuación IA
accion == "TSLA" ~ -0.12, # -12% — presión márgenes + competencia
accion == "COST" ~ -0.01 # -1% — Conservador
)
)
kable(
param_sim %>%
select(accion, S0, mu_alcista, mu_moderado, mu_bajista, sigma) %>%
mutate(across(c(mu_alcista,mu_moderado,mu_bajista,sigma),
~paste0(round(.x*100,1),"%")),
S0 = paste0("$",format(round(S0,2),big.mark=","))),
caption = "Tabla 17. Parámetros de simulación por escenario y acción.",
align = "c",
col.names = c("Acción","S₀ (precio inicial)","μ Alcista",
"μ Moderado","μ Bajista","σ Anual")
) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white") %>%
row_spec(1, background = "#f0fef4") %>%
row_spec(2, background = "#fef9f0") %>%
row_spec(3, background = "#eef4fd")| Acción | S₀ (precio inicial) | μ Alcista | μ Moderado | μ Bajista | σ Anual | |
|---|---|---|---|---|---|---|
| NVDA | NVDA | $ 198.45 | 25% | 12% | -8% | 45.8% |
| TSLA | TSLA | $ 390.82 | 22% | 10% | -12% | 58.5% |
| COST | COST | $1,011.72 | 14% | 10% | -1% | 20.8% |
sims <- lapply(tickers, function(tk) {
f <- param_sim[param_sim$accion == tk, ]
list(
alcista = simular_trayectorias(f$S0, f$mu_alcista, f$sigma),
moderado = simular_trayectorias(f$S0, f$mu_moderado, f$sigma),
bajista = simular_trayectorias(f$S0, f$mu_bajista, f$sigma)
)
})
names(sims) <- tickersescenarios_ord <- c("alcista","moderado","bajista")
tray_df <- map_dfr(tickers, function(tk) {
map_dfr(escenarios_ord, function(esc) {
data.frame(
mes = 0:48,
accion = tk,
escenario = esc,
precio = rowMeans(sims[[tk]][[esc]]),
p5 = apply(sims[[tk]][[esc]], 1, quantile, 0.05),
p95 = apply(sims[[tk]][[esc]], 1, quantile, 0.95)
)
})
}) %>%
mutate(
escenario = factor(escenario,
levels = escenarios_ord,
labels = c("Alcista","Moderado","Bajista"))
)
col_esc <- c("Alcista"="#1a9850","Moderado"="#f4a800","Bajista"="#d73027")
ggplot(tray_df, aes(x = mes, color = escenario, fill = escenario)) +
geom_ribbon(aes(ymin = p5, ymax = p95), alpha = 0.12, color = NA) +
geom_line(aes(y = precio), size = 1.0) +
facet_wrap(~ accion, scales = "free_y", ncol = 1) +
scale_color_manual(values = col_esc) +
scale_fill_manual(values = col_esc) +
scale_x_continuous(breaks = seq(0, 48, 6),
labels = c("Abr-26","Oct-26","Abr-27","Oct-27",
"Abr-28","Oct-28","Abr-29","Oct-29","Abr-30")) +
labs(title = "Trayectorias de precio promedio por escenario — 48 meses",
subtitle = "Línea = precio promedio | Banda = percentil 5% – 95%",
x = NULL, y = "Precio esperado (USD)",
color = "Escenario", fill = "Escenario",
caption = "Fuente: Elaboración propia. Modelo MBG (Hull, 2018).") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold"),
plot.subtitle = element_text(color = "gray40", size = 10),
legend.position= "bottom",
axis.text.x = element_text(angle = 30, hjust = 1, size = 8))Figura 9. Trayectorias de precio promedio por acción y escenario — horizonte 48 meses. Elaboración propia.
# Valor del portafolio a 48 meses por escenario
# Cada trayectoria es un vector de 1,000 valores terminales del portafolio
calc_val_port <- function(esc) {
vals <- lapply(tickers, function(tk) {
w_i <- pesos[tk]
S0_i <- param_sim$S0[param_sim$accion == tk]
pf <- sims[[tk]][[esc]][49, ] # fila 49 = mes 48 = abr-2030
capital * w_i * (pf / S0_i) # valor terminal ponderado
})
Reduce(`+`, vals)
}
vp_alc <- calc_val_port("alcista")
vp_mod <- calc_val_port("moderado")
vp_baj <- calc_val_port("bajista")
# Verificación de coherencia: mediana alcista > moderado > bajista
# y todos > 0 (no puede haber portafolio con valor negativo en MBG)
stopifnot(
median(vp_alc) > median(vp_mod),
median(vp_mod) > median(vp_baj),
all(vp_baj > 0)
)
fmt_usd <- function(x) paste0("$", format(round(x, 0), big.mark = ","))
res48 <- data.frame(
Escenario = c("Alcista","Moderado","Bajista"),
`Capital inicial` = fmt_usd(capital),
`Media` = fmt_usd(c(mean(vp_alc), mean(vp_mod), mean(vp_baj))),
`Mediana` = fmt_usd(c(median(vp_alc), median(vp_mod), median(vp_baj))),
`Perc. 5%` = fmt_usd(c(quantile(vp_alc,.05), quantile(vp_mod,.05),
quantile(vp_baj,.05))),
`Perc. 95%` = fmt_usd(c(quantile(vp_alc,.95), quantile(vp_mod,.95),
quantile(vp_baj,.95))),
`Retorno medio` = paste0(round(c(mean(vp_alc/capital)-1,
mean(vp_mod/capital)-1,
mean(vp_baj/capital)-1)*100, 1),"%"),
check.names = FALSE, row.names = NULL
)
kable(res48,
caption = "Tabla 18. Valor del portafolio a 48 meses — media, mediana y percentiles.",
align = c("l","c","c","c","c","c","c")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE) %>%
row_spec(0, bold = TRUE, background = "#2c3e50", color = "white") %>%
row_spec(1, background = "#f0fef4") %>%
row_spec(2, background = "#fef9f0") %>%
row_spec(3, background = "#fef0f0")| Escenario | Capital inicial | Media | Mediana | Perc. 5% | Perc. 95% | Retorno medio |
|---|---|---|---|---|---|---|
| Alcista | $20,000,000 | $47,592,935 | $33,848,733 | $9,553,539 | $126,527,572 | 138% |
| Moderado | $20,000,000 | $29,951,511 | $21,618,955 | $6,438,577 | $ 78,295,996 | 49.8% |
| Bajista | $20,000,000 | $14,313,078 | $10,559,911 | $3,362,444 | $ 36,493,175 | -28.4% |
La simulación a 48 meses utiliza el modelo de movimiento browniano geométrico para proyectar la evolución de NVDA, TSLA y COST bajo tres escenarios: alcista, moderado y bajista. Los parámetros muestran que NVDA y TSLA tienen mayores retornos esperados en escenarios favorables, pero también mayor exposición en escenarios bajistas, coherente con su perfil de crecimiento y alta volatilidad. Por otrom lado, COST presenta retornos esperados más moderados y una volatilidad anual menor, lo que refleja su papel conservador dentro del portafolio.
La tabla de valor del portafolio se evidencia que este tendría un desempeño significativamente distinto según el escenario. En el escenario alcista, el valor medio proyectado asciende a USD 49.2 millones, con un retorno medio de 146%, mientras que en el escenario moderado alcanza USD 30.9 millones, equivalente a 54.4%. En contraste, el escenario bajista reduce el valor medio a USD 15.1 millones, con un retorno medio de -24.4%. La diferencia entre media, mediana y percentiles muestra que existe alta dispersión en los resultados, especialmente por la volatilidad de NVDA y TSLA; por tanto, la simulación confirma que el portafolio tiene alto potencial de valorización, pero también una exposición importante a pérdidas en escenarios no tan buenos.
dist_df <- data.frame(
Valor = c(vp_alc, vp_mod, vp_baj) / 1e6,
Escenario = rep(c("Alcista","Moderado","Bajista"),
each = 1000)
) %>%
mutate(Escenario = factor(Escenario, levels = c("Alcista","Moderado","Bajista")))
ggplot(dist_df, aes(x = Valor, fill = Escenario, color = Escenario)) +
geom_density(alpha = 0.25, size = 0.9) +
geom_vline(data = dist_df %>%
group_by(Escenario) %>%
summarise(med = median(Valor), .groups="drop"),
aes(xintercept = med, color = Escenario),
linetype = "dashed", size = 1) +
scale_fill_manual(values = col_esc) +
scale_color_manual(values = col_esc) +
scale_x_continuous(labels = scales::dollar_format(suffix = "M", prefix = "$")) +
labs(title = "Distribución del valor terminal del portafolio (mes 48 — abr 2030)",
subtitle = "Líneas punteadas = mediana por escenario | 1,000 trayectorias simuladas",
x = "Valor del portafolio (USD millones)",
y = "Densidad",
caption = "Fuente: Elaboración propia. Modelo MBG (Hull, 2018).") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"),
plot.subtitle = element_text(color = "gray40", size = 10),
legend.position= "bottom")Figura 10. Distribución del valor terminal del portafolio a 48 meses por escenario. Elaboración propia.
La mediana resulta más representativa que la media para describir el valor terminal del portafolio, dado que las distribuciones log-normales del modelo MBG son asimétricas hacia la derecha — la media queda inflada por las trayectorias más extremas del lado positivo. Los percentiles 5% y 95% delimitan el rango que contiene el 90% de los resultados simulados y representan los escenarios pesimista y optimista razonables para cada hipótesis de mercado.
betas_hip <- c(0.5, 2.0)
hip_df <- data.frame(
`Beta hipotética` = betas_hip,
`Exposición sistemática (USD)` =
paste0("$",format(round(betas_hip*capital,0),big.mark=",")),
`N* exacto` = round(betas_hip*capital/(F0*QF),4),
`N* redondeado` = round(betas_hip*capital/(F0*QF)),
`Margen inicial (USD)` =
paste0("$",format(round(round(betas_hip*capital/(F0*QF))*margen_ini,0),
big.mark=",")),
`Sensib. 1% S&P500 (USD)` =
paste0("$",format(round(betas_hip*0.01*capital,0),big.mark=",")),
check.names=FALSE, row.names=NULL
)
kable(hip_df,
caption="Tabla 19. Sensibilidad de la cobertura con beta hipotética 0.5 y 2.",
align=c("c","c","c","c","c","c")) %>%
kable_styling(bootstrap_options=c("striped","hover"), full_width=TRUE) %>%
row_spec(0, bold=TRUE, background="#2c3e50", color="white") %>%
row_spec(1, background="#eaf4d3") %>%
row_spec(2, background="#fef0f0")| Beta hipotética | Exposición sistemática (USD) | N* exacto | N* redondeado | Margen inicial (USD) | Sensib. 1% S&P500 (USD) |
|---|---|---|---|---|---|
| 0.5 | $10,000,000 | 27.9018 | 28 | $ 740,516 | $100,000 |
| 2.0 | $40,000,000 | 111.6071 | 112 | $2,962,064 | $400,000 |
Con beta = 0.5, el portafolio es menos sensible al mercado que un índice pasivo: ante una caída del 10% en el S&P 500, se esperaría una pérdida de solo el 5% en el portafolio. En este caso se necesitan menos contratos de futuros para la cobertura, el margen requerido es menor y el costo de la estrategia disminuye. Este perfil corresponde a carteras defensivas o neutras al mercado.
Con beta = 2, el portafolio amplifica los movimientos del mercado en ambas direcciones: ante una caída del 10% en el índice, la pérdida esperada sería del 20%. Para cubrir esa exposición se necesita aproximadamente el doble de contratos respecto al caso beta = 1, lo que implica un mayor desembolso de margen y una gestión más activa de los llamados al margen cuando el mercado se mueve en sentido contrario a la posición. Portafolios con beta elevada son típicos de carteras concentradas en acciones de crecimiento o con apalancamiento implícito.
La relación es proporcional: aumentar la beta aumenta el número de contratos y la exposición al riesgo. Esto muestra por qué la beta del portafolio es el parámetro central de la estrategia de cobertura con futuros — cambios pequeños en beta tienen efectos directos sobre el costo y la efectividad de la cobertura.
Black, F., & Litterman, R. (1992). Global portfolio optimization. Financial Analysts Journal, 48(5), 28–43. https://doi.org/10.2469/faj.v48.n5.28
CME Group. (2026). E-mini S&P 500 margins. Chicago Mercantile Exchange. https://www.cmegroup.com/markets/equities/sp/e-mini-sandp500.margins.html
Costco Wholesale Corporation. (2025). Annual report / Form 10-K. https://investor.costco.com/financials/annual-reports-and-proxy-statements/default.aspx
Costco Wholesale Corporation. (2025). Annual report 2025. https://s201.q4cdn.com/287523651/files/doc_financials/2025/ar/COST-Annual-Report-2025.pdf
Markowitz, H. (1952). Portfolio selection. Journal of Finance, 7(1), 77–91. https://doi.org/10.2307/2975974
NVIDIA Corporation. (2025). Annual report 2025. https://investor.nvidia.com/financial-info/annual-reports-and-proxies/default.aspx
Sharpe, W. F. (1964). Capital asset prices: A theory of market equilibrium under conditions of risk. Journal of Finance, 19(3), 425–442. https://doi.org/10.2307/2977928
Tesla, Inc. (2025). Form 10-K for the fiscal year ended December 31, 2024. U.S. Securities and Exchange Commission. https://ir.tesla.com/_flysystem/s3/sec/000162828025003063/tsla-20241231-gen.pdf
Yahoo Finance. (2026). Datos históricos de precios — NVDA, TSLA, COST, ^GSPC, ES=F [Base de datos]. https://finance.yahoo.com
Yahoo Finance / CBOE. (2026). CBOE Interest Rate 3–5 Year Treasury Index (^TNX). https://finance.yahoo.com/quote/%5ETNX