Introducción
El presente trabajo desarrolla una estrategia de optimización y
cobertura financiera utilizando acciones pertenecientes al índice
S&P 500.
Las acciones seleccionadas son:
PG (Procter & Gamble) → consumo defensivo SYK (Stryker) → sector
salud WM (Waste Management) → servicios ambientales
La diversificación sectorial permite reducir el riesgo específico del
portafolio y construir una estrategia de cobertura mediante contratos
futuros sobre el índice S&P 500.
library(quantmod)
library(PerformanceAnalytics)
library(PortfolioAnalytics)
library(ROI)
library(ROI.plugin.quadprog)
library(quadprog)
library(tidyverse)
library(knitr)
library(kableExtra)
library(scales)
library(corrplot)
library(plotly)
## =========================================================
## PARAMETROS INICIALES
## =========================================================
capital_inicial <- 20000000
fecha_inicio <- as.Date("2016-04-30")
fecha_fin <- as.Date("2026-04-29")
acciones <- c("PG","SYK","WM")
indice <- "^GSPC"
## =========================================================
## DESCARGA DE INFORMACION HISTORICA
## =========================================================
getSymbols(
c(acciones, indice),
src = "yahoo",
from = fecha_inicio,
to = fecha_fin
)
## [1] "PG" "SYK" "WM" "GSPC"
precios <- na.omit(
merge(
Ad(PG),
Ad(SYK),
Ad(WM),
Ad(GSPC)
)
)
colnames(precios) <- c(
"PG",
"SYK",
"WM",
"SP500"
)
head(precios)
## PG SYK WM SP500
## 2016-05-02 61.64122 98.37413 49.96724 2081.43
## 2016-05-03 61.74021 97.68515 49.85063 2063.37
## 2016-05-04 62.12082 97.16618 50.32548 2051.12
## 2016-05-05 61.89243 98.53521 50.23383 2050.63
## 2016-05-06 62.52431 99.51054 50.55873 2057.14
## 2016-05-09 62.51671 99.60898 50.98359 2058.69
## =========================================================
## EVOLUCION HISTORICA DE PRECIOS
## =========================================================
chartSeries(
precios$PG,
theme = chartTheme("white"),
name = "PG"
)

chartSeries(
precios$SYK,
theme = chartTheme("white"),
name = "SYK"
)

chartSeries(
precios$WM,
theme = chartTheme("white"),
name = "WM"
)

## =========================================================
## RENDIMIENTOS LOGARITMICOS
## =========================================================
rendimientos <- na.omit(
Return.calculate(
precios,
method = "log"
)
)
head(rendimientos)
## PG SYK WM SP500
## 2016-05-03 0.0016047023 -0.0070283124 -0.002336486 -0.0087144994
## 2016-05-04 0.0061458007 -0.0053268873 0.009480369 -0.0059545827
## 2016-05-05 -0.0036833775 0.0139912770 -0.001822768 -0.0002390367
## 2016-05-06 0.0101576515 0.0098496639 0.006446882 0.0031696106
## 2016-05-09 -0.0001216642 0.0009886972 0.008368223 0.0007532133
## 2016-05-10 0.0043739530 0.0038553192 0.002448022 0.0124063652
## =========================================================
## RETORNOS Y VOLATILIDADES ANUALIZADAS
## =========================================================
retornos_acciones <- rendimientos[,1:3]
retornos_esperados <- colMeans(
retornos_acciones
) * 252
volatilidades <- apply(
retornos_acciones,
2,
sd
) * sqrt(252)
tabla_estadisticas <- data.frame(
Accion = acciones,
Retorno_Anual = round(retornos_esperados,4),
Volatilidad_Anual = round(volatilidades,4)
)
kable(
tabla_estadisticas,
caption = "Retornos y Volatilidades Anualizadas"
) %>%
kable_styling(full_width = FALSE)
Retornos y Volatilidades Anualizadas
|
|
Accion
|
Retorno_Anual
|
Volatilidad_Anual
|
|
PG
|
PG
|
0.0887
|
0.1890
|
|
SYK
|
SYK
|
0.1188
|
0.2614
|
|
WM
|
WM
|
0.1521
|
0.1946
|
Explicación de Anualización
La anualización se realiza considerando 252 días hábiles bursátiles
por año:
Retornos promedio → multiplicados por 252 Volatilidades →
multiplicadas por √252 Covarianzas → multiplicadas por 252 Correlaciones
→ no requieren anualización
## =========================================================
## MATRIZ DE COVARIANZAS
## =========================================================
matriz_cov <- cov(
retornos_acciones
) * 252
kable(
round(matriz_cov,6),
caption = "Matriz de Covarianzas"
) %>%
kable_styling(full_width = FALSE)
Matriz de Covarianzas
|
|
PG
|
SYK
|
WM
|
|
PG
|
0.035716
|
0.019230
|
0.018355
|
|
SYK
|
0.019230
|
0.068346
|
0.023956
|
|
WM
|
0.018355
|
0.023956
|
0.037875
|
## =========================================================
## MATRIZ DE CORRELACIONES
## =========================================================
matriz_cor <- cor(retornos_acciones)
kable(
round(matriz_cor,4),
caption = "Matriz de Correlaciones"
) %>%
kable_styling(full_width = FALSE)
Matriz de Correlaciones
|
|
PG
|
SYK
|
WM
|
|
PG
|
1.0000
|
0.3892
|
0.4991
|
|
SYK
|
0.3892
|
1.0000
|
0.4709
|
|
WM
|
0.4991
|
0.4709
|
1.0000
|
## =========================================================
## HEATMAP DE CORRELACIONES
## =========================================================
corrplot(
matriz_cor,
method = "color",
type = "upper",
addCoef.col = "black",
tl.col = "black"
)

## =========================================================
## OPTIMIZACIÓN MEDIA-VARIANZA
## =========================================================
portafolio <- portfolio.spec(
assets = acciones
)
portafolio <- add.constraint(
portfolio = portafolio,
type = "full_investment"
)
portafolio <- add.constraint(
portfolio = portafolio,
type = "long_only"
)
portafolio <- add.objective(
portfolio = portafolio,
type = "risk",
name = "StdDev"
)
optimizacion <- optimize.portfolio(
R = retornos_acciones,
portfolio = portafolio,
optimize_method = "ROI"
)
pesos <- extractWeights(optimizacion)
pesos
## PG SYK WM
## 0.4826333 0.1162579 0.4011087
## =========================================================
## DISTRIBUCION OPTIMA DEL CAPITAL
## =========================================================
inversiones <- pesos * capital_inicial
tabla_pesos <- data.frame(
Accion = acciones,
Peso = percent(pesos),
Inversion_USD = dollar(inversiones)
)
kable(
tabla_pesos,
caption = "Distribución Óptima del Capital"
) %>%
kable_styling(full_width = FALSE)
Distribución Óptima del Capital
|
|
Accion
|
Peso
|
Inversion_USD
|
|
PG
|
PG
|
48.3%
|
$9,652,666
|
|
SYK
|
SYK
|
11.6%
|
$2,325,159
|
|
WM
|
WM
|
40.1%
|
$8,022,175
|
## =========================================================
## GRAFICO DE PESOS
## =========================================================
barplot(
pesos,
names.arg = acciones,
col = c("steelblue","darkgreen","orange"),
main = "Pesos Óptimos del Portafolio",
ylab = "Participación"
)

## =========================================================
## METRICAS DEL PORTAFOLIO
## =========================================================
retorno_portafolio <- sum(
pesos * retornos_esperados
)
volatilidad_portafolio <- sqrt(
t(pesos) %*%
matriz_cov %*%
pesos
)
rf <- 0.04
sharpe_ratio <- (
retorno_portafolio - rf
) / volatilidad_portafolio
tabla_metricas <- data.frame(
Indicador = c(
"Retorno Esperado",
"Volatilidad",
"Sharpe Ratio"
),
Valor = c(
round(retorno_portafolio,4),
round(volatilidad_portafolio,4),
round(sharpe_ratio,4)
)
)
kable(
tabla_metricas,
caption = "Métricas del Portafolio"
) %>%
kable_styling(full_width = FALSE)
Métricas del Portafolio
|
Indicador
|
Valor
|
|
Retorno Esperado
|
0.1176
|
|
Volatilidad
|
0.1638
|
|
Sharpe Ratio
|
0.4738
|
## =========================================================
## FRONTERA EFICIENTE
## =========================================================
frontera <- create.EfficientFrontier(
R = retornos_acciones,
portfolio = portafolio,
type = "mean-StdDev"
)
chart.EfficientFrontier(
frontera,
match.col = "StdDev",
chart.assets = TRUE,
main = "Frontera Eficiente"
)

## =========================================================
## VALUE AT RISK (VaR)
## =========================================================
mu_mensual <- retorno_portafolio / 12
sigma_mensual <- volatilidad_portafolio /
sqrt(12)
z_95 <- qnorm(0.95)
z_99 <- qnorm(0.99)
VaR_95 <- -(
mu_mensual -
z_95 * sigma_mensual
)
VaR_99 <- -(
mu_mensual -
z_99 * sigma_mensual
)
tabla_var <- data.frame(
Nivel = c("95%","99%"),
VaR_Porcentaje = c(
round(VaR_95,4),
round(VaR_99,4)
),
VaR_USD = c(
round(VaR_95 * capital_inicial,2),
round(VaR_99 * capital_inicial,2)
)
)
kable(
tabla_var,
caption = "Value at Risk Mensual"
) %>%
kable_styling(full_width = FALSE)
Value at Risk Mensual
|
Nivel
|
VaR_Porcentaje
|
VaR_USD
|
|
95%
|
0.0680
|
1359678
|
|
99%
|
0.1002
|
2004233
|
## =========================================================
## VALUE AT RISK (VaR)
## =========================================================
mercado <- rendimientos$SP500
betas <- c()
for(i in acciones){
beta <- cov(
rendimientos[,i],
mercado
) / var(mercado)
betas[i] <- beta
}
tabla_betas <- data.frame(
Accion = acciones,
Beta = round(betas,4)
)
kable(
tabla_betas,
caption = "Betas CAPM"
) %>%
kable_styling(full_width = FALSE)
Betas CAPM
|
|
Accion
|
Beta
|
|
PG
|
PG
|
0.4849
|
|
SYK
|
SYK
|
0.9701
|
|
WM
|
WM
|
0.5633
|
## =========================================================
## BETAS CAPM
## =========================================================
mercado <- rendimientos$SP500
betas <- c()
for(i in acciones){
beta <- cov(
rendimientos[,i],
mercado
) / var(mercado)
betas[i] <- beta
}
tabla_betas <- data.frame(
Accion = acciones,
Beta = round(betas,4)
)
kable(
tabla_betas,
caption = "Betas CAPM"
) %>%
kable_styling(full_width = FALSE)
Betas CAPM
|
|
Accion
|
Beta
|
|
PG
|
PG
|
0.4849
|
|
SYK
|
SYK
|
0.9701
|
|
WM
|
WM
|
0.5633
|
## =========================================================
## BETA DEL PORTAFOLIO
## =========================================================
beta_portafolio <- sum(
pesos * betas
)
beta_portafolio
## [1] 0.5727508
| Activo subyacente |
S&P 500 |
| Multiplicador |
50 USD |
| Precio Futuro |
5200 |
| Valor nocional |
260000 USD |
| Liquidación |
Mark-to-market |
## =========================================================
## NUMERO OPTIMO DE CONTRATOS
## =========================================================
precio_futuro <- 5200
multiplicador <- 50
valor_contrato <- precio_futuro *
multiplicador
contratos_teoricos <- (
beta_portafolio *
capital_inicial
) / valor_contrato
contratos_finales <- ceiling(
contratos_teoricos
)
tabla_futuros <- data.frame(
Concepto = c(
"Beta Portafolio",
"Valor Contrato",
"Contratos Teóricos",
"Contratos Finales"
),
Valor = c(
round(beta_portafolio,4),
valor_contrato,
round(contratos_teoricos,2),
contratos_finales
)
)
kable(
tabla_futuros,
caption = "Cobertura con Futuros"
) %>%
kable_styling(full_width = FALSE)
Cobertura con Futuros
|
Concepto
|
Valor
|
|
Beta Portafolio
|
5.728e-01
|
|
Valor Contrato
|
2.600e+05
|
|
Contratos Teóricos
|
4.406e+01
|
|
Contratos Finales
|
4.500e+01
|
## =========================================================
## INTERPRETACION DE COBERTURA
## =========================================================
if(beta_portafolio > 0){
posicion <- "Posición corta (SHORT)"
} else {
posicion <- "Posición larga (LONG)"
}
cat("
Interpretación:
El portafolio tiene beta positiva,
por lo tanto la cobertura adecuada
es una posición corta en futuros.
Si el mercado cae:
- El portafolio pierde valor
- Los futuros generan ganancias
Si el mercado sube:
- El portafolio gana valor
- Los futuros generan pérdidas
La cobertura reduce el riesgo sistemático.
")
##
## Interpretación:
##
## El portafolio tiene beta positiva,
## por lo tanto la cobertura adecuada
## es una posición corta en futuros.
##
## Si el mercado cae:
## - El portafolio pierde valor
## - Los futuros generan ganancias
##
## Si el mercado sube:
## - El portafolio gana valor
## - Los futuros generan pérdidas
##
## La cobertura reduce el riesgo sistemático.
cat("Posición recomendada:\n")
## Posición recomendada:
## Posición corta (SHORT)
## =========================================================
## SIMULACION MARK-TO-MARKET
## =========================================================
set.seed(123)
meses <- 12
cambios <- rnorm(
meses,
mean = 0,
sd = 0.03
)
precios_futuros <- c(precio_futuro)
for(i in 1:meses){
nuevo_precio <- precios_futuros[i] *
(1 + cambios[i])
precios_futuros <- c(
precios_futuros,
nuevo_precio
)
}
ganancias <- diff(precios_futuros) *
multiplicador *
contratos_finales
tabla_mtm <- data.frame(
Mes = 1:meses,
Precio_Futuro = round(
precios_futuros[-1],
2
),
Ganancia_Perdida = round(
ganancias,
2
)
)
kable(
tabla_mtm,
caption = "Mark-to-Market"
) %>%
kable_styling(full_width = FALSE)
Mark-to-Market
|
Mes
|
Precio_Futuro
|
Ganancia_Perdida
|
|
1
|
5112.57
|
-196726.95
|
|
2
|
5077.26
|
-79433.84
|
|
3
|
5314.68
|
534192.99
|
|
4
|
5325.92
|
25294.25
|
|
5
|
5346.58
|
46478.91
|
|
6
|
5621.67
|
618956.94
|
|
7
|
5699.41
|
174900.59
|
|
8
|
5483.10
|
-486681.56
|
|
9
|
5370.12
|
-254210.72
|
|
10
|
5298.32
|
-161544.94
|
|
11
|
5492.89
|
437776.66
|
|
12
|
5552.18
|
133408.20
|
## =========================================================
## MARGIN CALLS
## =========================================================
margen_inicial <- 15000
margen_mantenimiento <- 12000
tabla_mtm$Saldo_Margen <- (
margen_inicial *
contratos_finales
) + cumsum(
tabla_mtm$Ganancia_Perdida
)
tabla_mtm$Margin_Call <- ifelse(
tabla_mtm$Saldo_Margen <
margen_mantenimiento *
contratos_finales,
"SI",
"NO"
)
kable(
tabla_mtm,
caption = "Margin Calls"
) %>%
kable_styling(full_width = FALSE)
Margin Calls
|
Mes
|
Precio_Futuro
|
Ganancia_Perdida
|
Saldo_Margen
|
Margin_Call
|
|
1
|
5112.57
|
-196726.95
|
478273.0
|
SI
|
|
2
|
5077.26
|
-79433.84
|
398839.2
|
SI
|
|
3
|
5314.68
|
534192.99
|
933032.2
|
NO
|
|
4
|
5325.92
|
25294.25
|
958326.4
|
NO
|
|
5
|
5346.58
|
46478.91
|
1004805.4
|
NO
|
|
6
|
5621.67
|
618956.94
|
1623762.3
|
NO
|
|
7
|
5699.41
|
174900.59
|
1798662.9
|
NO
|
|
8
|
5483.10
|
-486681.56
|
1311981.3
|
NO
|
|
9
|
5370.12
|
-254210.72
|
1057770.6
|
NO
|
|
10
|
5298.32
|
-161544.94
|
896225.7
|
NO
|
|
11
|
5492.89
|
437776.66
|
1334002.3
|
NO
|
|
12
|
5552.18
|
133408.20
|
1467410.5
|
NO
|
## =========================================================
## ROLL-OVER TRIMESTRAL
## =========================================================
cat("
El roll-over consiste en cerrar el contrato
vigente antes del vencimiento y abrir uno nuevo.
Riesgos asociados:
- Riesgo de base
- Contango
- Backwardation
El roll-over permite mantener la cobertura
durante todo el horizonte de inversión.
")
##
## El roll-over consiste en cerrar el contrato
## vigente antes del vencimiento y abrir uno nuevo.
##
## Riesgos asociados:
## - Riesgo de base
## - Contango
## - Backwardation
##
## El roll-over permite mantener la cobertura
## durante todo el horizonte de inversión.
## =========================================================
## DRAWDOWN DEL PORTAFOLIO
## =========================================================
ret_port <- Return.portfolio(
R = retornos_acciones,
weights = pesos
)
chart.Drawdown(
ret_port,
main = "Drawdown del Portafolio"
)

## =========================================================
## RENDIMIENTO ACUMULADO
## =========================================================
chart.CumReturns(
ret_port,
main = "Rendimiento Acumulado"
)

## =========================================================
## COMPARACION VS MERCADO
## =========================================================
comparacion <- merge(
ret_port,
rendimientos$SP500
)
colnames(comparacion) <- c(
"Portafolio",
"SP500"
)
charts.PerformanceSummary(
comparacion,
main = "Portafolio vs Mercado"
)

## =========================================================
## TRACKING ERROR
## =========================================================
tracking_error <- sd(
ret_port - rendimientos$SP500
) * sqrt(252)
tracking_error
## [1] 0.1488173
cat("
Tracking Error:
Mide qué tanto se desvía el portafolio
respecto al índice S&P 500.
")
##
## Tracking Error:
## Mide qué tanto se desvía el portafolio
## respecto al índice S&P 500.
## =========================================================
## VISUALIZACION INTERACTIVA
## =========================================================
grafico <- ggplot(
tabla_estadisticas,
aes(
x = Accion,
y = Retorno_Anual,
fill = Accion
)
) +
geom_col() +
theme_minimal()
ggplotly(grafico)
Conclusiones 1.El portafolio óptimo fue construido utilizando el
modelo media-varianza de Markowitz. 2. Las acciones PG, SYK y WM
proporcionan diversificación sectorial y reducen el riesgo específico.
3. La beta del portafolio permitió determinar la exposición sistemática
al mercado. 4. La cobertura mediante futuros sobre el S&P 500 reduce
el riesgo sistemático del portafolio. 5. El cálculo del número óptimo de
contratos se realizó utilizando la beta del portafolio y el valor
nocional del futuro. 6. El análisis mark-to-market muestra cómo
evolucionan diariamente las ganancias y pérdidas de la cobertura.