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.
¿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?
\(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\)
\[ \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)} \]
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)\)
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}) \]
La frontera eficiente representa los portafolios con:
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\).
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}} \]
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} \]
# Cargar librerías
library(quantmod)
library(PerformanceAnalytics)
library(tidyquant)
library(tidyverse)
library(gganimate)
library(ggplot2)
library(gifski)
library(magick)
# Definir los activos
assets <- c("META", "AMZN", "AAPL", "NFLX", "GOOG", "TSLA")
n_assets <- length(assets)
# Descargar precios ajustados desde Yahoo Finance usando tidyquant
prices_df <- tq_get(assets,
from = "2020-01-01",
to = "2025-07-07",
get = "stock.prices")
# Ver los primeros registros
head(prices_df)
## # A tibble: 6 × 8
## symbol date open high low close volume adjusted
## <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 META 2020-01-02 207. 210. 206. 210. 12077100 209.
## 2 META 2020-01-03 207. 210. 207. 209. 11188400 208.
## 3 META 2020-01-06 207. 213. 207. 213. 17058900 211.
## 4 META 2020-01-07 213. 215. 212. 213. 14912400 212.
## 5 META 2020-01-08 213 216. 213. 215. 13475000 214.
## 6 META 2020-01-09 218. 218. 216. 218. 12642800 217.
# Mostrar las últimas filas del dataframe
tail(prices_df)
## # A tibble: 6 × 8
## symbol date open high low close volume adjusted
## <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 TSLA 2025-06-26 325. 331. 324. 326. 80440900 326.
## 2 TSLA 2025-06-27 325. 329. 318. 324. 89067000 324.
## 3 TSLA 2025-06-30 320. 326. 317. 318. 76695100 318.
## 4 TSLA 2025-07-01 298. 306. 293. 301. 145085700 301.
## 5 TSLA 2025-07-02 313. 317. 304. 316. 119483700 316.
## 6 TSLA 2025-07-03 318. 318. 313. 315. 58042300 315.
# 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, ~ (. - lag(.)) / lag(.))) %>%
drop_na()
# Mostrar primeras filas
head(returns)
## # A tibble: 6 × 7
## date META AMZN AAPL NFLX GOOG TSLA
## <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2020-01-03 -0.00529 -0.0121 -0.00972 -0.0119 -0.00491 0.0296
## 2 2020-01-06 0.0188 0.0149 0.00797 0.0305 0.0247 0.0193
## 3 2020-01-07 0.00216 0.00209 -0.00470 -0.0151 -0.000624 0.0388
## 4 2020-01-08 0.0101 -0.00781 0.0161 0.0257 0.00788 0.0492
## 5 2020-01-09 0.0143 0.00480 0.0212 -0.0106 0.0110 -0.0219
## 6 2020-01-10 -0.00110 -0.00941 0.00226 -0.0197 0.00697 -0.00663
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)
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))
El gráfico muestra la distribución de los retornos diarios por día de la semana para seis activos tecnológicos (AAPL, AMZN, GOOG, META, NFLX y TSLA). En general, los retornos están centrados cerca de cero, lo que indica ausencia de un sesgo sistemático y sugiere que no hay un efecto del día de la semana claramente aprovechable. La mayoría de los activos presenta una dispersión similar entre lunes y viernes, aunque TSLA y NFLX destacan por su mayor volatilidad, evidenciada en la amplitud de sus boxplots y la presencia frecuente de valores atípicos. Esta mayor variabilidad sugiere que son activos más sensibles a eventos externos o noticias. A pesar de leves asimetrías en algunos casos, como colas negativas más extendidas en TSLA, el análisis visual respalda la idea de que los retornos diarios no muestran patrones consistentes por día de la semana.
# 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))
El gráfico permite observar cómo varía la distribución de los retornos financieros de seis empresas tecnológicas a lo largo de los meses del año. Aunque las medianas de los retornos se mantienen generalmente cercanas a cero lo cual indica que, en promedio, no hay un sesgo mensual fuerte, existen diferencias en la dispersión y simetría de las distribuciones. TSLA, por ejemplo, muestra una mayor amplitud en sus cajas y bigotes en casi todos los meses, lo que refleja una mayor sensibilidad a eventos del mercado o a condiciones específicas de la empresa. Por otro lado, acciones como AAPL y GOOG exhiben distribuciones más concentradas, lo que puede interpretarse como mayor estabilidad en sus retornos. La recurrencia de outliers en todos los activos sugiere que, independientemente del mes, los retornos financieros están expuestos a eventos extremos, lo cual es típico en instrumentos de renta variable. Más que mostrar una estacionalidad marcada, la gráfica destaca diferencias estructurales entre las acciones, revelando perfiles de riesgo distintos que podrían ser relevantes para decisiones de inversión o construcción de portafolios diversificados.
# 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 diferencias claras en la variabilidad de los retornos entre las acciones analizadas a lo largo de varios años. AAPL, GOOG y AMZN muestran una distribución de retornos más estable y concentrada en torno a cero, lo que sugiere un comportamiento predecible y menor riesgo relativo. En contraste, TSLA se destaca por presentar una alta dispersión en todos los años, evidenciando una volatilidad estructural significativamente mayor. La presencia consistente de valores atípicos en todas las acciones refleja la naturaleza impredecible de los mercados financieros, aunque en algunos años como 2020 y 2022 estos extremos son más pronunciados, posiblemente debido a eventos macroeconómicos. Aunque la mayoría de las distribuciones son simétricas, en algunos años se observan ligeros sesgos que indican periodos con retornos positivos o negativos predominantes. En conjunto, esta visualización resalta perfiles de riesgo diferenciados entre las empresas, útiles para la toma de decisiones en contextos de inversión y gestión de portafolios.
# 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)
## # A tibble: 6 × 2
## date retorno_portafolio
## <date> <dbl>
## 1 2020-01-03 -0.00238
## 2 2020-01-06 0.0193
## 3 2020-01-07 0.00377
## 4 2020-01-08 0.0169
## 5 2020-01-09 0.00314
## 6 2020-01-10 -0.00460
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.
# Número de portafolios a simular
N_PORTFOLIOS <- 10^6
# Número de días hábiles en un año (para anualizar retornos)
N_DAYS <- 252
# Definir activos
assets <- c("META", "AMZN", "AAPL", "NFLX", "GOOG", "TSLA")
# Ordenar alfabéticamente
assets <- sort(assets)
# Número de activos
n_assets <- length(assets)
# Mostrar resultado
assets
## [1] "AAPL" "AMZN" "GOOG" "META" "NFLX" "TSLA"
n_assets
## [1] 6
library(tidyquant)
# Ya deberías tener definidos los activos en 'assets', ordenados alfabéticamente
# Descargar precios diarios ajustados de Yahoo Finance
prices_df <- tq_get(assets,
from = "2021-01-01",
to = "2025-07-07",
get = "stock.prices")
# Vista previa
head(prices_df)
## # A tibble: 6 × 8
## symbol date open high low close volume adjusted
## <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 AAPL 2021-01-04 134. 134. 127. 129. 143301900 126.
## 2 AAPL 2021-01-05 129. 132. 128. 131. 97664900 128.
## 3 AAPL 2021-01-06 128. 131. 126. 127. 155088000 123.
## 4 AAPL 2021-01-07 128. 132. 128. 131. 109578200 128.
## 5 AAPL 2021-01-08 132. 133. 130. 132. 105158200 129.
## 6 AAPL 2021-01-11 129. 130. 128. 129. 100384500 126.
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, ~ (. / lag(.) - 1))) %>%
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
## AAPL AMZN GOOG META NFLX TSLA
## 0.1577279 0.1386332 0.2146874 0.3208894 0.3059226 0.2494252
cov_mat
## AAPL AMZN GOOG META NFLX TSLA
## AAPL 0.08116578 0.05864788 0.05281375 0.06471880 0.05311366 0.09074834
## AMZN 0.05864788 0.12655115 0.07241290 0.09778774 0.07876057 0.10136037
## GOOG 0.05281375 0.07241290 0.09773094 0.08304984 0.05830386 0.08113383
## META 0.06471880 0.09778774 0.08304984 0.19786274 0.09457012 0.09801674
## NFLX 0.05311366 0.07876057 0.05830386 0.09457012 0.19782693 0.09830896
## TSLA 0.09074834 0.10136037 0.08113383 0.09801674 0.09830896 0.38608202
set.seed(42) # 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] 1000000 6
head(weights)
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 0.30739088 0.15508958 0.03430836 0.07286651 0.19021835 0.24012631
## [2,] 0.31406338 0.13868581 0.02405097 0.16114362 0.18131000 0.18074622
## [3,] 0.08184604 0.24440838 0.14243640 0.20740734 0.20942488 0.11447696
## [4,] 0.27370299 0.05233305 0.16390355 0.13057442 0.10038064 0.27910536
## [5,] 0.21031295 0.17129578 0.22633776 0.22762969 0.08786683 0.07655699
## [6,] 0.15061600 0.11719253 0.21601262 0.21345629 0.27309102 0.02963153
# 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.2188180 0.3213010 0.6810375
## [2,] 0.2261852 0.3114370 0.7262632
## [3,] 0.2365479 0.3145405 0.7520426
## [4,] 0.2278383 0.3266782 0.6974394
## [5,] 0.2245310 0.2952433 0.7604948
## [6,] 0.2458097 0.3013836 0.8156043
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)
## returns volatility sharpe_ratio
## 1 0.2188180 0.3213010 0.6810375
## 2 0.2261852 0.3114370 0.7262632
## 3 0.2365479 0.3145405 0.7520426
## 4 0.2278383 0.3266782 0.6974394
## 5 0.2245310 0.2952433 0.7604948
## 6 0.2458097 0.3013836 0.8156043
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"
)
El gráfico muestra la frontera eficiente generada a partir de un millón de portafolios simulados construidos con combinaciones aleatorias de seis acciones tecnológicas (AAPL, AMZN, GOOG, META, NFLX y TSLA). En el eje X se representa la volatilidad anual (riesgo), y en el eje Y, el retorno esperado anual. Cada punto corresponde a un portafolio, y su color indica el índice de Sharpe: los colores más oscuros (violeta) representan valores más altos del ratio de Sharpe, lo que indica una mejor relación retorno-riesgo, mientras que los colores más claros (amarillo) indican desempeño menos eficiente. La nube de portafolios forma una parábola cóncava que ilustra la clásica relación riesgo-retorno. Aquellos portafolios ubicados sobre la parte superior de la curva conforman la frontera eficiente. Se observa que activos como AAPL y META están relativamente cerca de esta frontera, mientras que TSLA se ubica más lejos y hacia la derecha, reflejando su mayor volatilidad. Este análisis permite identificar visualmente combinaciones de activos que ofrecen el mejor rendimiento ajustado por riesgo, así como evaluar el comportamiento relativo de cada acción dentro del conjunto simulado.
# Portafolio con mayor Sharpe Ratio
max_sharpe_port <- portf_results_df %>%
filter(sharpe_ratio == max(sharpe_ratio)) %>%
slice(1)
max_sharpe_port
## returns volatility sharpe_ratio returns_round
## 1 0.2688419 0.3136914 0.8570267 0.27
# Portafolio con mínima varianza
min_var_port <- portf_results_df %>%
filter(volatility == min(volatility)) %>%
slice(1)
min_var_port
## returns volatility sharpe_ratio returns_round
## 1 0.1806792 0.2633127 0.6861773 0.18
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"))
En el gráfico se visualizan dos portafolios clave dentro del universo simulado: el de máximo Sharpe Ratio, marcado con un triángulo amarillo, y el de mínima varianza, señalado con un cuadrado negro. El portafolio con mayor Sharpe Ratio representa la mejor combinación riesgo-retorno posible, ya que ofrece el mayor retorno esperado ajustado por unidad de riesgo; se ubica en la parte superior de la nube eficiente, donde los valores del Sharpe son más altos (zona púrpura), con una volatilidad moderada y un rendimiento elevado. Por otro lado, el portafolio de mínima varianza es el más seguro dentro del conjunto, con la menor volatilidad entre todas las combinaciones posibles, aunque con un retorno esperado también más bajo. Este equilibrio permite a los inversores elegir entre eficiencia (máximo Sharpe) o seguridad (mínima varianza), según su perfil de riesgo. Ambos portafolios actúan como referencias fundamentales en la construcción de carteras óptimas bajo el enfoque de teoría moderna de portafolios.
# Í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")
| Activo | Máx..Sharpe | Mín..Varianza |
|---|---|---|
| AAPL | 0.0622 | 0.4998 |
| AMZN | 0.0035 | 0.1213 |
| GOOG | 0.3403 | 0.3366 |
| META | 0.2714 | 0.0027 |
| NFLX | 0.3174 | 0.0358 |
| TSLA | 0.0053 | 0.0037 |
La tabla muestra la asignación de pesos para cada uno de los seis activos (AAPL, AMZN, GOOG, META, NFLX y TSLA) dentro de dos portafolios construidos bajo criterios de eficiencia: el primero busca maximizar el Sharpe Ratio, y el segundo busca minimizar la varianza. Estas estrategias responden a objetivos distintos: el portafolio de máximo Sharpe Ratio busca obtener el mayor rendimiento ajustado por riesgo, mientras que el de mínima varianza privilegia la estabilidad, asignando los recursos para minimizar la volatilidad total del portafolio, sin considerar directamente el retorno.
En el caso del portafolio de Sharpe máximo, se observa una fuerte concentración en GOOG (34.03%), NFLX (31.74%) y META (27.14%), lo cual sugiere que estos activos han mostrado un buen equilibrio entre rendimiento y riesgo durante el periodo analizado. AAPL, a pesar de ser una empresa sólida, solo representa un 6.22% del portafolio, lo que indica que, aunque tiene buen rendimiento, su inclusión no maximiza la eficiencia total. Por su parte, AMZN (0.35%) y TSLA (0.53%) tienen participaciones marginales, probablemente debido a una menor relación retorno-riesgo o mayor volatilidad relativa.
En contraste, el portafolio de mínima varianza está altamente concentrado en AAPL (49.98%) y GOOG (33.66%), activos que aparentemente presentan baja correlación con otros activos y una volatilidad reducida. Este portafolio distribuye muy poco capital en META (0.27%), NFLX (3.58%), TSLA (0.37%) y AMZN (12.13%), reflejando una estrategia defensiva que favorece empresas con rendimientos más estables y menos sensibles a choques del mercado.
En conjunto, la tabla ilustra cómo varía la composición de un portafolio dependiendo del criterio de optimización adoptado. Mientras que el portafolio eficiente en términos de Sharpe diversifica más el capital entre activos con buen desempeño ajustado por riesgo, el portafolio de mínima varianza concentra el capital en unos pocos activos con menor volatilidad. Esta información es crucial para que un inversionista pueda tomar decisiones alineadas con su perfil: quienes prioricen el crecimiento ajustado por riesgo pueden inclinarse por el portafolio de Sharpe máximo, mientras que los más conservadores podrían preferir el de menor varianza.
El análisis realizado se desarrolló bajo el marco de la teoría moderna de portafolio de Markowitz, la cual plantea que los inversionistas racionales deben tomar decisiones basadas en la optimización del retorno esperado frente al riesgo, medido a través de la varianza de los retornos. A través de la simulación de un millón de portafolios utilizando datos históricos de seis activos tecnológicos relevantes (AAPL, AMZN, GOOG, META, NFLX y TSLA), se logró construir la frontera eficiente, una herramienta visual clave que delimita las combinaciones óptimas de portafolios en el espacio retorno-riesgo.
Entre los portafolios generados, se destacaron dos configuraciones: el portafolio con el máximo Sharpe Ratio y el portafolio de mínima varianza. El primero mostró una asignación significativa en GOOG (34.03 %), NFLX (31.74 %) y META (27.14 %), lo cual sugiere que estos activos han ofrecido una atractiva combinación de rendimiento y riesgo durante el periodo analizado. Por su parte, el portafolio de mínima varianza concentró más del 80 % del capital en AAPL (49.98 %) y GOOG (33.66 %), evidenciando que estos activos han sido los más estables dentro del conjunto considerado.
El gráfico de la frontera eficiente confirmó que el portafolio de máxima eficiencia se ubica en la parte superior izquierda de la nube de puntos, alcanzando altos retornos con una volatilidad contenida, mientras que el portafolio de mínima varianza se posiciona en el extremo izquierdo, siendo el más estable pero con menor rendimiento esperado. Estas observaciones respaldan empíricamente los principios teóricos de la optimización de portafolios.
Al considerar elementos de las finanzas comportamentales, se pone de relieve que los inversionistas no siempre actúan de manera puramente racional. Sesgos como la sobreconfianza, la aversión a las pérdidas o la tendencia a seguir al mercado pueden llevar a decisiones que se desvían de las recomendaciones del modelo de Markowitz. Por ejemplo, activos como TSLA o AMZN, que tienen fuerte presencia mediática pero no aparecen significativamente en los portafolios óptimos, podrían ser sobreponderados por razones emocionales más que técnicas. Asimismo, un inversor conservador podría priorizar el portafolio de mínima varianza no por razones de eficiencia financiera, sino por la tranquilidad emocional que brinda su baja volatilidad.
Por lo tanto, los resultados del ejercicio no solo validan la aplicabilidad del enfoque de Markowitz, sino que también muestran la necesidad de interpretar los modelos cuantitativos dentro de un contexto más amplio que incluya el comportamiento real de los agentes. La integración de ambos enfoques permite diseñar estrategias más robustas, adaptadas tanto a los objetivos financieros como a las características psicológicas del inversionista.