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
Concepto Valor
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("\n")
cat("Posición recomendada:\n")
## Posición recomendada:
cat(posicion)
## 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.