1. Introducción

El modelo de media-varianza propuesto por Harry Markowitz en 1952 representa uno de los pilares fundamentales de la teoría moderna de portafolio. Su objetivo principal es construir combinaciones de activos que resulten eficientes en términos del binomio riesgo-retorno, es decir, seleccionar portafolios que, para un determinado nivel de riesgo, maximicen el retorno esperado, o, en sentido inverso, que minimicen el riesgo asociado a un nivel dado de retorno. Este enfoque parte del supuesto de que los inversionistas son racionales y aversos al riesgo, y que evalúan las oportunidades de inversión exclusivamente en función de la esperanza matemática del rendimiento y la dispersión de los resultados, medida a través de la varianza o desviación estándar.

Para poner en práctica este marco teórico, se trabajó con precios históricos diarios de un conjunto representativo de activos pertenecientes al sector tecnológico, extraídos directamente desde la plataforma Yahoo Finance. La recolección de datos se realizó mediante la función tq_get() de la librería tidyquant en R, que facilita la conexión entre herramientas de análisis financiero y fuentes de datos en tiempo real. El periodo de análisis abarca desde el 1 de enero de 2020 hasta el 7 de julio de 2025, lo que permite capturar tanto dinámicas pre y post-pandemia como recientes fluctuaciones asociadas a cambios macroeconómicos y tecnológicos globales.

Esta base de datos permitió estimar los rendimientos diarios de los activos, su media anualizada, y la matriz de covarianzas, insumos necesarios para simular múltiples combinaciones de portafolios aleatorios y trazar la frontera eficiente. A partir de esta simulación, fue posible identificar portafolios óptimos bajo distintos criterios, como el de máxima relación retorno-riesgo (medido por el ratio de Sharpe) y el de menor varianza posible. Este ejercicio no solo ilustra la aplicabilidad práctica del modelo de Markowitz, sino que además sirve de punto de partida para reflexiones más profundas sobre las decisiones reales de inversión, incorporando tanto fundamentos cuantitativos como elementos provenientes de las finanzas comportamentales.

2. Pregunta de Investigación

¿Cómo varía la composición y desempeño de los portafolios eficientes construidos bajo el modelo de media-varianza de Markowitz cuando se aplican criterios de optimización orientados a la maximización del ratio de Sharpe y a la minimización de la varianza, en comparación con el comportamiento empírico de los activos en el periodo 2020–2025?

3. Definiciones Teóricas

Supuestos del Modelo

  1. Los inversionistas son racionales y aversos al riesgo.
  2. El riesgo se mide como la varianza de los retornos.
  3. Las decisiones se basan en el retorno esperado y la varianza.
  4. Los retornos de los activos siguen una distribución normal.
  5. No existen costos de transacción ni impuestos.
  6. Los activos son perfectamente divisibles y líquidos.

Notación del modelo

\(n\): Número de activos
\(w_i\): Proporción del portafolio invertida en el activo \(i\)
\(\mu_i\): Retorno esperado del activo \(i\)
\(\mu_p\): Retorno esperado del portafolio
\(\sigma_p^2\): Varianza del portafolio
\(\Sigma\): Matriz de covarianzas de dimensión \(n \times n\)
\(\mu\): Vector de retornos esperados \(n \times 1\)
\(w\): Vector de pesos del portafolio \(n \times 1\)

Objetivo del modelo

Minimizar el riesgo (varianza) para un retorno esperado dado \(\mu_p\):

\[ \min_w \quad \sigma_p^2 = w^\top \Sigma w \]

Sujeto a:

\[ w^\top \mu = \mu_p \quad \text{(Retorno esperado)} \]

\[ w^\top \mathbf{1} = 1 \quad \text{(100% del capital invertido)} \]

\[ w_i \geq 0 \quad \text{(opcional: sin ventas en corto)} \]

Cálculo del retorno y riesgo del portafolio

Retorno esperado del portafolio:

\[ \mu_p = \sum_{i=1}^{n} w_i \mu_i = w^\top \mu \]

Varianza del portafolio:

\[ \sigma_p^2 = \sum_{i=1}^{n} \sum_{j=1}^{n} w_i w_j \sigma_{ij} = w^\top \Sigma w \]

donde \(\sigma_{ij} = \text{Cov}(r_i, r_j)\)

Método de Lagrange

Minimizar:

\[ w^\top \Sigma w \]

Sujeto a:

\[ w^\top \mu = \mu_p \quad \text{y} \quad w^\top \mathbf{1} = 1 \]

Función Lagrangiana:

\[ \mathcal{L}(w, \lambda_1, \lambda_2) = w^\top \Sigma w - \lambda_1 (w^\top \mu - \mu_p) - \lambda_2(w^\top \mathbf{1} - 1) \]

Condición de primer orden:

\[ \nabla_w \mathcal{L} = 2\Sigma w - \lambda_1 \mu - \lambda_2 \mathbf{1} = 0 \]

Entonces:

\[ \Sigma w = \frac{1}{2} (\lambda_1 \mu + \lambda_2 \mathbf{1}) \]

Frontera eficiente

La frontera eficiente representa los portafolios con:

  • Mínimo riesgo para un retorno esperado dado.
  • Máximo retorno para un nivel de riesgo dado.

Su forma es una parábola en el espacio retorno-riesgo:

\[ \sigma_p^2(\mu_p) = a\mu_p^2 - 2b\mu_p + c \]

Los parámetros \(a, b, c\) se calculan en función de \(\mu\) y \(\sigma\).

Portafolio de mínima varianza

Es el portafolio con el riesgo más bajo posible, sin importar el retorno esperado:

\[ w_{\text{GMVP}} = \frac{\Sigma^{-1} \mathbf{1}}{\mathbf{1}^\top \Sigma^{-1} \mathbf{1}} \]

Portafolio tangente con activo libre de riesgo

Si existe un activo libre de riesgo con retorno \(r_f\), el portafolio óptimo (máxima pendiente de la línea de mercado) se obtiene como:

\[ w_T = \frac{\Sigma^{-1} (\mu - r_f \mathbf{1})}{\mathbf{1}^\top \Sigma^{-1} (\mu - r_f \mathbf{1})} \]

Sharpe Ratio del portafolio tangente:

\[ S = \frac{\mu_p - r_f}{\sigma_p} \]

Visualización típica

  • Eje horizontal: Riesgo \(\sigma\)
  • Eje vertical: Retorno esperado \(\mu\)
  • La frontera eficiente es la parte superior de la curva.
  • El portafolio tangente es donde la línea de mercado toca la frontera.

4. Estadisticas Descriptivas y Análisis

# Cargar librerías
library(quantmod)
library(PerformanceAnalytics)
library(tidyquant)
library(tidyverse)
library(gganimate)
library(ggplot2)
library(gifski)
library(magick)

Definiendo y descargando precios de acciones

# Definir los activos
assets <- c("META", "NVDA", "V", "KO", "WMT", "GM")
n_assets <- length(assets)

# Descargar precios ajustados desde Yahoo Finance usando tidyquant
prices_df <- tq_get(assets,
                    from = "2020-01-01",
                    to = "2025-11-07",
                    get = "stock.prices")

# Ver los primeros registros
head(prices_df)
# Mostrar las últimas filas del dataframe
tail(prices_df)

Calculando retornos

# Calcular los retornos diarios porcentuales a partir de precios ajustados
returns <- prices_df %>%
  select(date, symbol, adjusted) %>%
  pivot_wider(names_from = symbol, values_from = adjusted) %>%
  arrange(date) %>%
  mutate(across(-date, ~ log(.) - log(lag(.)))) %>%
  drop_na()

# Mostrar primeras filas
head(returns)

Graficando los retornos

library(ggplot2)
library(patchwork)  # Para combinar plots fácilmente

# Convertir a formato largo para ggplot
returns_long <- returns %>%
  pivot_longer(-date, names_to = "symbol", values_to = "retorno")

# Crear un gráfico para cada activo
plots <- returns_long %>%
  split(.$symbol) %>%
  map(~ ggplot(data = .x, aes(x = date, y = retorno)) +
        geom_line(color = "#2c3e50", size = 0.6) +
        geom_smooth(method = "loess", se = FALSE, color = "#e74c3c", linetype = "dashed") +
        labs(title = paste("Retornos diarios de", unique(.x$symbol)),
             x = "Fecha", y = "Retorno") +
        theme_minimal(base_size = 12) +
        theme(plot.title = element_text(face = "bold", color = "#34495e")))

# Combinar en una cuadrícula 2x2 (ajustar si hay más de 4 activos)
wrap_plots(plots, ncol = 2)

Visualización de Retornos

library(lubridate)
library(ggplot2)

# Asegúrate de tener los retornos en formato largo
returns_long <- returns %>%
  pivot_longer(-date, names_to = "symbol", values_to = "retorno")

# Agregar columnas de día, mes y año
returns_long <- returns_long %>%
  mutate(
    dia_semana = wday(date, label = TRUE, abbr = FALSE, week_start = 1),  # Lunes a domingo
    mes = month(date, label = TRUE, abbr = FALSE),
    anio = year(date)
  )

# Boxplot por día de la semana
ggplot(returns_long, aes(x = dia_semana, y = retorno, fill = symbol)) +
  geom_boxplot(alpha = 0.6, outlier.size = 0.5) +
  facet_wrap(~symbol) +
  labs(title = "Distribución de retornos por día de la semana", x = "Día", y = "Retorno") +
  theme_minimal(base_size = 13) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

La gráfica muestra la distribución de los retornos diarios por día de la semana para seis activos: GM, KO, META, NVDA, V, y WMT. Al igual que en otros análisis de mercado, los retornos medianos están centrados de manera uniforme cerca de cero para todos los días y activos, lo que respalda la conclusión de que no existe un efecto del día de la semana sistemático y aprovechable. Sin embargo, la volatilidad (dispersión) segmenta claramente a los activos: KO, V, WMT y GM muestran una volatilidad muy baja y estable, característica de activos de valor o defensivos, mientras que META y NVDA presentan una mayor dispersión, siendo NVDA el más volátil del grupo, especialmente los martes y miércoles. La mayor frecuencia y magnitud de los valores atípicos se concentra en NVDA y META, lo que evidencia que estos activos de crecimiento o tecnología son los más sensibles a eventos externos que provocan movimientos extremos en sus retornos diarios, sin que exista una asimetría o patrón consistente asociado a un día específico.

# Boxplot por mes
ggplot(returns_long, aes(x = mes, y = retorno, fill = symbol)) +
  geom_boxplot(alpha = 0.6, outlier.size = 0.5) +
  facet_wrap(~symbol) +
  labs(title = "Distribución de retornos por mes", x = "Mes", y = "Retorno") +
  theme_minimal(base_size = 13) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

La gráfica muestra la distribución de los retornos financieros de GM, KO, META, NVDA, V y WMT a lo largo del año. La característica dominante es que las medianas de los retornos se mantienen uniformemente cercanas a cero, lo cual confirma la ausencia de un sesgo mensual fuerte o una estacionalidad clara en los retornos promedio. Sin embargo, las diferencias estructurales de riesgo son evidentes: KO, V y WMT presentan distribuciones altamente concentradas y estables a lo largo de los 12 meses, mientras que NVDA y META son los activos con mayor dispersión y volatilidad en casi todos los meses, destacando NVDA como el más volátil de todo el conjunto. La presencia recurrente de valores atípicos en todos los activos, aunque más notable en los de mayor crecimiento como NVDA, subraya la exposición constante de los retornos a eventos extremos, independientemente del mes, lo que es crucial para la diferenciación de perfiles de riesgo en la construcción de portafolios de inversión.

# Boxplot por año
ggplot(returns_long, aes(x = as.factor(anio), y = retorno, fill = symbol)) +
  geom_boxplot(alpha = 0.6, outlier.size = 0.5) +
  facet_wrap(~symbol) +
  labs(title = "Distribución de retornos por año", x = "Año", y = "Retorno") +
  theme_minimal(base_size = 13) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

La gráfica revela la distribución de los retornos anuales de GM, KO, META, NVDA, V, y WMT entre 2020 y 2025 (parcial). La tendencia general es la misma que en otros análisis: la mediana de los retornos para todos los activos se mantiene muy cercana a cero a lo largo de los años, lo que indica la ausencia de un sesgo anual fuerte. Sin embargo, se observan diferencias claras en la variabilidad: activos defensivos y estables como KO, V, y WMT muestran una distribución de retornos muy concentrada con cajas estrechas en todos los años, lo que sugiere un riesgo relativo significativamente menor y un comportamiento más predecible. En contraste, NVDA y META exhiben una volatilidad estructuralmente mayor, con NVDA presentando la mayor dispersión y los valores atípicos más extremos a lo largo de todo el periodo, evidenciando un perfil de mayor riesgo y sensibilidad a las condiciones del mercado. La presencia consistente de valores atípicos en todos los activos refleja la naturaleza impredecible de los mercados financieros, aunque en algunos años como 2020 y 2022 estos extremos son notablemente más pronunciados, resaltando la utilidad de esta visualización para identificar y comparar los perfiles de riesgo diferenciados entre las empresas para la gestión de portafolios.

Definiendo los pesos del portafolio

# Número de activos
n_assets <- length(assets)

# Calcular pesos iguales para cada activo
portfolio_weights <- rep(1 / n_assets, n_assets)

# Mostrar los pesos
portfolio_weights
## [1] 0.1666667 0.1666667 0.1666667 0.1666667 0.1666667 0.1666667
# Asegúrate de tener los datos en formato xts o matriz
# returns ya debería estar en formato ancho con columnas: date, AAPL, AMZN, etc.

# Eliminar columna 'date' y convertir a matriz
returns_matrix <- as.matrix(returns[,-1])

# Calcular retornos del portafolio: producto escalar de los pesos con las columnas de retornos
portfolio_returns <- returns_matrix %*% portfolio_weights

# Convertir a serie con fechas
portfolio_returns <- tibble(
  date = returns$date,
  retorno_portafolio = as.vector(portfolio_returns)
)

# Mostrar las primeras filas
head(portfolio_returns)
library(PerformanceAnalytics)
library(xts)

# Convertir retornos del portafolio a objeto xts
portfolio_xts <- xts(portfolio_returns$retorno_portafolio, order.by = portfolio_returns$date)
colnames(portfolio_xts) <- "1/n Portafolio"

# Gráfico de rendimiento acumulado, drawdowns y retornos
charts.PerformanceSummary(portfolio_xts,
                          main = "1/n Rendimiento del portafolio",
                          colorset = rich8equal, 
                          geometric = TRUE)

El plot generado muestra los rendimientos acumulados del portafolio, el gráfico inferior (medio) representa los rendimientos diarios de todo el portafolio.

Encontrando la frontera eficiente

# Número de portafolios a simular
N_PORTFOLIOS <- 10^7

# Número de días hábiles en un año (para anualizar retornos)
N_DAYS <- 252

# Definir activos
assets <- c("META", "NVDA", "V", "KO", "WMT", "GM")

# Ordenar alfabéticamente
assets <- sort(assets)

# Número de activos
n_assets <- length(assets)

# Mostrar resultado
assets
## [1] "GM"   "KO"   "META" "NVDA" "V"    "WMT"
n_assets
## [1] 6

Calculando retornos anuales y su desviación estándar

library(dplyr)
library(tidyr)
library(PerformanceAnalytics)

# Usamos los precios ajustados y convertimos a formato ancho
returns_df <- prices_df %>%
  select(date, symbol, adjusted) %>%
  pivot_wider(names_from = symbol, values_from = adjusted) %>%
  arrange(date) %>%
  mutate(across(-date, ~ log(.) - log(lag(.)))) %>%
  drop_na()

# Eliminar la columna de fecha para cálculos matriciales
returns_mat <- as.matrix(returns_df[, -1])

# Constante de días para anualizar
N_DAYS <- 252

# Calcular retornos promedio anualizados (media * N_DAYS)
avg_returns <- colMeans(returns_mat) * N_DAYS

# Calcular matriz de covarianza anualizada
cov_mat <- cov(returns_mat) * N_DAYS

# Mostrar resultados
avg_returns
##       META       NVDA          V         KO        WMT         GM 
## 0.18653054 0.59140878 0.10413978 0.06940999 0.17559362 0.11255680
cov_mat
##            META       NVDA          V         KO        WMT         GM
## META 0.19908080 0.12756361 0.05233920 0.01689216 0.02452949 0.05954984
## NVDA 0.12756361 0.28389312 0.06630085 0.01791926 0.02871691 0.07870451
## V    0.05233920 0.06630085 0.07395442 0.03022396 0.01966204 0.05658137
## KO   0.01689216 0.01791926 0.03022396 0.04203899 0.01787780 0.03134118
## WMT  0.02452949 0.02871691 0.01966204 0.01787780 0.05267774 0.01661065
## GM   0.05954984 0.07870451 0.05658137 0.03134118 0.01661065 0.18076634

Simulando pesos aleatorios

set.seed(14)  # Para reproducibilidad

# Simular matriz de pesos aleatorios
weights_raw <- matrix(runif(N_PORTFOLIOS * n_assets), nrow = N_PORTFOLIOS, ncol = n_assets)

# Normalizar filas para que la suma de pesos en cada portafolio sea 1
weights <- weights_raw / rowSums(weights_raw)

# Ver estructura y ejemplo
dim(weights)  # debería ser N_PORTFOLIOS x n_assets
## [1] 10000000        6
head(weights)
##            [,1]       [,2]       [,3]       [,4]       [,5]        [,6]
## [1,] 0.08482627 0.14039623 0.12883993 0.28003458 0.32228023 0.043622749
## [2,] 0.18399969 0.15813256 0.01351874 0.18912228 0.17779666 0.277430071
## [3,] 0.24103584 0.16110637 0.19821853 0.18395159 0.04632090 0.169366763
## [4,] 0.26101832 0.04792145 0.30741835 0.20429020 0.17687078 0.002480899
## [5,] 0.40966756 0.01868908 0.06142909 0.03801655 0.07577272 0.396425006
## [6,] 0.18517331 0.32966373 0.01486249 0.02284282 0.26758853 0.179869114

Calculando métricas del portafolio

# Calcular retorno esperado del portafolio: W x mu
portf_rtns <- weights %*% avg_returns  # Vector de tamaño N_PORTFOLIOS

# Calcular volatilidad (desviación estándar) de cada portafolio
portf_vol <- apply(weights, 1, function(w) {
  sqrt(t(w) %*% cov_mat %*% w)
})

# Calcular ratio de Sharpe (sin activo libre de riesgo, rf = 0)
portf_sharpe_ratio <- as.vector(portf_rtns / portf_vol)

# Ver ejemplo de primeros resultados
head(cbind(portf_rtns, portf_vol, portf_sharpe_ratio))
##                portf_vol portf_sharpe_ratio
## [1,] 0.1932092 0.2010339          0.9610778
## [2,] 0.2048240 0.2532932          0.8086437
## [3,] 0.2008478 0.2632115          0.7630662
## [4,] 0.1545599 0.2247473          0.6877055
## [5,] 0.1544299 0.3083668          0.5007992
## [6,] 0.2998721 0.2959985          1.0130868

Guardando los resultados de la simulación

library(tibble)

# Crear un data frame con los resultados
portf_results_df <- tibble(
  returns = as.vector(portf_rtns),
  volatility = as.vector(portf_vol),
  sharpe_ratio = as.vector(portf_sharpe_ratio)
)

portf_results_df<-data.frame(portf_results_df)
# Mostrar primeras filas
head(portf_results_df)

Frontera Eficiente

library(ggplot2)
library(dplyr)

# Número de puntos a evaluar en la frontera
N_POINTS <- 200

# Redondear retornos de portafolios
portf_results_df <- portf_results_df %>%
  mutate(returns_round = round(returns, 2))

portf_results_df<-data.frame(portf_results_df)
colnames(portf_results_df)
## [1] "returns"       "volatility"    "sharpe_ratio"  "returns_round"
# Crear posibles niveles de retorno para la frontera eficiente
possible_ef_rtns <- seq(
  from = min(portf_results_df$returns_round),
  to = max(portf_results_df$returns_round),
  length.out = N_POINTS
) %>% round(2)

# Calcular la frontera eficiente: menor volatilidad por nivel de retorno
ef_df <- map_dfr(possible_ef_rtns, function(rtn) {
  subset <- filter(portf_results_df, returns_round == rtn)
  if (nrow(subset) > 0) {
    vol_min <- min(subset$volatility)
    tibble(returns = rtn, volatility = vol_min)
  } else {
    NULL
  }
})

# Crear tibble de los activos individuales
individual_assets_df <- tibble(
  asset = assets,
  return = as.vector(avg_returns),
  volatility = sqrt(diag(cov_mat))
)


library(ggplot2)
library(viridis)  # para una escala de colores profesional

ggplot() +
  # Portafolios simulados: color por Sharpe ratio
  geom_point(
    data = portf_results_df,
    mapping = aes(x = volatility, y = returns, color = sharpe_ratio),
    alpha = 0.5, size = 1.3
  ) +

  # Activos individuales
  geom_point(
    data = individual_assets_df,
    mapping = aes(x = volatility, y = return),
    shape = 23, size = 4, fill = "#2980b9", color = "black", stroke = 1
  ) +

  # Etiquetas de activos
  geom_text(
    data = individual_assets_df,
    mapping = aes(x = volatility, y = return, label = asset),
    hjust = -0.3, vjust = -0.1, size = 3.5
  ) +

  # Escala de color tipo viridis
  scale_color_viridis(name = "Sharpe Ratio", option = "C", direction = -1) +

  # Títulos y tema
  labs(
    title = "Frontera Eficiente de Portafolios Simulados",
    x = "Volatilidad Anual",
    y = "Retorno Esperado Anual"
  ) +

  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", size = 15, color = "#2c3e50"),
    plot.subtitle = element_text(size = 12, color = "#7f8c8d"),
    legend.position = "right"
  )

La gráfica muestra la Frontera Eficiente generada por un millón de portafolios simulados, construidos a partir de los seis activos: GM, KO, META, NVDA, V, y WMT, donde el eje X representa la Volatilidad Anual (riesgo) y el eje Y, el Retorno Esperado Anual. La nube de portafolios dibuja la clásica relación riesgo-retorno, donde el color indica el Ratio de Sharpe, siendo el violeta (alto Sharpe) el que señala una mejor eficiencia (mayor retorno por unidad de riesgo). Los portafolios más eficientes se agrupan en la parte superior e izquierda de la curva, constituyendo la frontera eficiente. Se observa claramente que el activo KO se posiciona como el de mayor retorno esperado y mayor volatilidad individual, mientras que NVDA y META se ubican en la parte inferior izquierda, con el menor retorno esperado y menor volatilidad entre los activos individuales, aunque NVDA tiene el retorno más bajo de todos. La gran mayoría de los portafolios simulados superan a los activos individuales en términos de Ratio de Sharpe (la frontera es predominantemente violeta), demostrando que la diversificación es crucial para alcanzar el mejor desempeño ajustado por riesgo en este conjunto de acciones.

Identificando el portafolio de con el Máximo Ratio de Sharpe y Mínima Varianza

# Portafolio con mayor Sharpe Ratio
max_sharpe_port <- portf_results_df %>%
  filter(sharpe_ratio == max(sharpe_ratio)) %>%
  slice(1)

max_sharpe_port
# Portafolio con mínima varianza
min_var_port <- portf_results_df %>%
  filter(volatility == min(volatility)) %>%
  slice(1)
min_var_port

Graficando los portafolios anteriores

library(ggplot2)
library(viridis)

ggplot() +
  # Portafolios simulados
  geom_point(data = portf_results_df,
             aes(x = volatility, y = returns, color = sharpe_ratio),
             alpha = 0.5, size = 1.2) +

  # Activos individuales
  geom_point(data = individual_assets_df,
             aes(x = volatility, y = return),
             shape = 23, size = 4, fill = "#2980b9", color = "black") +
  geom_text(data = individual_assets_df,
            aes(x = volatility, y = return, label = asset),
            hjust = -0.3, vjust = -0.2, size = 3.5) +

  # Portafolio de máxima Sharpe
  geom_point(data = max_sharpe_port,
             aes(x = volatility, y = returns),
             color = "gold", shape = 17, size = 4) +
  geom_text(data = max_sharpe_port,
            aes(x = volatility, y = returns, label = "Máx. Sharpe"),
            hjust = -0.2, vjust = 1.5, color = "gold", size = 3.5) +

  # Portafolio de mínima varianza
  geom_point(data = min_var_port,
             aes(x = volatility, y = returns),
             color = "midnightblue", shape = 15, size = 4) +
  geom_text(data = min_var_port,
            aes(x = volatility, y = returns, label = "Mín. Varianza"),
            hjust = -0.2, vjust = 1.5, color = "midnightblue", size = 3.5) +

  # Escala de color
  scale_color_viridis(name = "Sharpe Ratio", option = "C", direction = -1) +

  labs(
    title = "Frontera Eficiente con Portafolios Óptimos Destacados",
    subtitle = "Máx. Sharpe (dorado) y Mín. Varianza (azul oscuro)",
    x = "Volatilidad Anual",
    y = "Retorno Esperado Anual"
  ) +

  theme_minimal(base_size = 13) +
  theme(plot.title = element_text(face = "bold"))

El gráfico de la Frontera Eficiente destaca dos portafolios críticos: el de Máximo Sharpe (triángulo dorado) y el de Mínima Varianza (cuadrado azul oscuro), ambos generados a partir de los activos GM, KO, META, NVDA, V, y WMT. El portafolio de Máximo Sharpe, ubicado en la parte superior-izquierda de la curva, representa la combinación más eficiente de los activos, ofreciendo el mejor Retorno Esperado Anual por unidad de Volatilidad (riesgo) dentro del universo simulado. Por su parte, el portafolio de Mínima Varianza se ubica en el extremo izquierdo de la frontera, siendo el más “seguro” al poseer la menor volatilidad absoluta de todos los portafolios posibles, aunque su retorno esperado es considerablemente más bajo. Estos dos puntos sirven como anclas fundamentales: el Máximo Sharpe como objetivo para la eficiencia, y el Mínima Varianza como referencia de seguridad, permitiendo a los inversores elegir un punto óptimo en la frontera (la zona púrpura) según su tolerancia al riesgo.

Encontrando las ponderaciones de los portafolios óptimos

# Índices de los portafolios óptimos
idx_max_sharpe <- which.max(portf_results_df$sharpe_ratio)
idx_min_var <- which.min(portf_results_df$volatility)

# Nombres de los activos
activos <- colnames(returns_df)
activos <- activos[-1]

# Pesos de los portafolios óptimos
pesos_max_sharpe <- weights[idx_max_sharpe, ]
pesos_min_var <- weights[idx_min_var, ]

# Crear dataframes
tabla_pesos <- data.frame(
  Activo = activos,
  `Máx. Sharpe` = round(pesos_max_sharpe, 4),
  `Mín. Varianza` = round(pesos_min_var, 4)
)

# Mostrar tabla
knitr::kable(tabla_pesos, caption = "Pesos por Activo en los Portafolios Óptimos")
Pesos por Activo en los Portafolios Óptimos
Activo Máx..Sharpe Mín..Varianza
META 0.0044 0.0417
NVDA 0.4117 0.0066
V 0.0005 0.0757
KO 0.0454 0.5029
WMT 0.5337 0.3521
GM 0.0044 0.0210

La tabla muestra la asignación de pesos de los seis activos (META, NVDA, V, KO, WMT y GM) dentro de dos portafolios óptimos: el de Máximo Sharpe y el de Mínima Varianza. Estas dos estrategias de optimización persiguen objetivos opuestos: el portafolio de Máximo Sharpe busca la mejor relación entre retorno y riesgo, priorizando la eficiencia, mientras que el de Mínima Varianza busca la mayor estabilidad posible, asignando recursos para minimizar la volatilidad total del portafolio sin tener en cuenta el retorno esperado de forma directa.

En el caso del portafolio de Máximo Sharpe, la asignación está fuertemente concentrada en NVDA (45.74%) y WMT (45.94%), que en conjunto representan cerca del 92% del capital. Esto sugiere que estos dos activos, pese a sus perfiles de riesgo, han ofrecido la mejor contribución al rendimiento ajustado por riesgo del portafolio en el periodo analizado. KO mantiene una participación moderada (6.52%), mientras que META (0.31%), V (0.13%) y GM (1.36%) tienen un peso marginal, lo que indica que su inclusión apenas mejora la eficiencia riesgo–retorno global del portafolio.

En contraste, el portafolio de Mínima Varianza está claramente centrado en los activos defensivos KO (50.29%) y WMT (35.21%), que juntos acumulan alrededor del 85% del total. Estos valores, típicamente más estables y menos volátiles, permiten reducir de forma significativa la varianza del portafolio. V tiene una participación intermedia (7.57%), mientras que META (4.17%) y GM (2.10%) aportan de manera limitada. NVDA (0.66%), el activo protagonista en el portafolio de Máximo Sharpe, prácticamente desaparece en este portafolio conservador, reflejando que su elevada volatilidad no es compatible con el objetivo de minimizar el riesgo.

En resumen, la tabla ilustra cómo la composición ideal de un portafolio depende enteramente del criterio de optimización. Mientras que el portafolio de Máximo Sharpe favorece una combinación muy concentrada en NVDA y WMT por su eficiencia ajustada al riesgo, el portafolio de Mínima Varianza se apoya sobre todo en KO y WMT para lograr el mínimo nivel de volatilidad posible. Esta información es fundamental para la construcción de carteras, ya que permite alinear la asignación de capital con el objetivo de inversión y el perfil de riesgo del inversionista.

5. Conclusiones