🔹 Configuración del Entorno
1⃣️ Carga de Paquetes y Preparación del Entorno
options(repos = c(CRAN = "https://cloud.r-project.org"))
required_pkgs <- c("quantmod", "PerformanceAnalytics", "tseries", "ggplot2",
"dplyr", "tidyverse", "lubridate", "tibble", "RQuantLib",
"scales", "data.tree", "DiagrammeR", "reshape2", "quadprog", "knitr")
new_pkgs <- required_pkgs[!required_pkgs %in% installed.packages()[, "Package"]]
if(length(new_pkgs)) install.packages(new_pkgs)
invisible(lapply(required_pkgs, library, character.only = TRUE))
🔎 Análisis: En este bloque se asegura que todos los
paquetes necesarios estén instalados y cargados. Esto permite un entorno
de trabajo reproducible y garantiza que todas las herramientas
estadísticas y visuales estarán disponibles para los análisis
posteriores.
2⃣️ Descarga y Preparación de Datos Financieros
stocks <- c("RXRX", "MRNA", "BRPHF")
start_date <- as.Date("2022-06-01")
end_date <- as.Date("2025-03-31")
getSymbols(stocks, from = start_date, to = end_date, src = "yahoo", auto.assign = TRUE)
## [1] "RXRX" "MRNA" "BRPHF"
prices <- do.call(merge, lapply(stocks, function(x) Cl(get(x))))
colnames(prices) <- stocks
returns <- na.omit(diff(log(prices)))
mu <- colMeans(returns)
cov_matrix <- cov(returns)
# 🧼 Cálculo del Portafolio Óptimo de Tangencia (Sharpe máximo)
rf <- 0.03
ones <- rep(1, length(mu))
inv_cov <- solve(cov_matrix)
excess_ret <- mu - rf
pesos_tangency <- inv_cov %*% excess_ret
pesos_tangency <- pesos_tangency / sum(pesos_tangency)
pesos_tangency <- as.numeric(pesos_tangency)
names(pesos_tangency) <- stocks
💰 Inversión Inicial de USD 1,000,000 en Portafolio
Óptimo
inversion_total <- 1000000
valores_finales <- as.numeric(last(prices))
acciones_compradas <- floor((pesos_tangency * inversion_total) / valores_finales)
inversion_realizada <- acciones_compradas * valores_finales
portafolio_inicial <- data.frame(
Accion = stocks,
Precio = round(valores_finales, 2),
Peso = round(pesos_tangency, 4),
Cantidad = acciones_compradas,
Inversion = round(inversion_realizada, 2)
)
kable(portafolio_inicial, caption = "Distribución de la Inversión Inicial del Portafolio Óptimo")
Distribución de la Inversión Inicial del Portafolio
Óptimo
| RXRX |
RXRX |
5.81 |
0.0886 |
15254 |
88625.74 |
| MRNA |
MRNA |
31.12 |
0.6921 |
22239 |
692077.70 |
| BRPHF |
BRPHF |
11.49 |
0.2193 |
19083 |
219282.76 |
🌈 Simulación de Carteras Aleatorias y Frontera
Eficiente
n_portafolios <- 10000
resultados <- matrix(nrow = n_portafolios, ncol = 3)
colnames(resultados) <- c("Retorno", "Riesgo", "Sharpe")
pesos_lista <- list()
for (i in 1:n_portafolios) {
pesos <- runif(length(stocks))
pesos <- pesos / sum(pesos)
ret_esp <- sum(pesos * mu)
riesgo <- sqrt(t(pesos) %*% cov_matrix %*% pesos)
sharpe <- (ret_esp - rf) / riesgo
resultados[i, ] <- c(ret_esp, riesgo, sharpe)
pesos_lista[[i]] <- pesos
}
resultados_df <- as.data.frame(resultados)
colnames(resultados_df) <- c("Retorno", "Riesgo", "Sharpe")
# Agregar portafolio de tangencia
resultados_df$tangency <- FALSE
resultados_df$tangency[which.max(resultados_df$Sharpe)] <- TRUE
# Visualización
library(ggplot2)
ggplot(resultados_df, aes(x = Riesgo, y = Retorno)) +
geom_point(aes(color = Sharpe), alpha = 0.5) +
scale_color_viridis_c() +
geom_point(data = subset(resultados_df, tangency == TRUE), color = "red", size = 3) +
labs(title = "Frontera Eficiente de Portafolios Aleatorios",
x = "Volatilidad (Riesgo)", y = "Retorno Esperado") +
theme_minimal()

📉 Simulación de Precios en Escenario de Caída con Múltiples
Trayectorias
set.seed(123)
n_dias <- 252 * 2
n_simulaciones <- 5000
mu_sim <- mu - 0.05
sigma <- sqrt(diag(cov_matrix))
simulaciones_mgb <- lapply(1:length(stocks), function(i) {
matrix(replicate(n_simulaciones, {
precios <- numeric(n_dias)
precios[1] <- valores_finales[i]
for (t in 2:n_dias) {
precios[t] <- precios[t - 1] * exp((mu_sim[i] - 0.5 * sigma[i]^2) + sigma[i] * rnorm(1))
}
precios
}), nrow = n_dias)
})
names(simulaciones_mgb) <- stocks
🔍 Análisis de la Evolución de Precios Simulados (Final de
Simulación)
resultados_finales <- sapply(simulaciones_mgb, function(m) m[n_dias, ])
estadisticas_finales <- apply(resultados_finales, 2, function(x) {
c(Media = mean(x), Min = min(x), Max = max(x), P10 = quantile(x, 0.10), P90 = quantile(x, 0.90))
})
estadisticas_df <- as.data.frame(t(estadisticas_finales))
kable(round(estadisticas_df, 2), caption = "Estadísticas Finales del Precio Simulado (1000 trayectorias)")
Estadísticas Finales del Precio Simulado (1000
trayectorias)
| RXRX |
0 |
0 |
0 |
0 |
0 |
| MRNA |
0 |
0 |
0 |
0 |
0 |
| BRPHF |
0 |
0 |
0 |
0 |
0 |
📊 Precio Esperado Promedio por Acción en el Tiempo
promedios_mgb <- sapply(simulaciones_mgb, rowMeans)
promedios_df <- data.frame(Dia = 1:n_dias, promedios_mgb)
promedios_long <- pivot_longer(promedios_df, -Dia, names_to = "Accion", values_to = "Precio")
ggplot(promedios_long, aes(x = Dia, y = Precio, color = Accion)) +
geom_line() +
labs(title = "Precio Promedio Esperado por Acción (1000 Simulaciones)", x = "Día", y = "Precio Promedio") +
theme_minimal()

📈 Visualización de Trayectorias Simuladas
Seleccionadas
simulaciones_largas <- bind_rows(lapply(names(simulaciones_mgb), function(nombre) {
sim <- simulaciones_mgb[[nombre]]
sim_df <- as.data.frame(sim)
sim_df$Dia <- 1:nrow(sim_df)
sim_largo <- pivot_longer(sim_df, -Dia, names_to = "Simulacion", values_to = "Precio")
sim_largo$Accion <- nombre
sim_largo
}), .id = "ID")
# Graficar solo 10 trayectorias por acción
library(dplyr)
ggplot(simulaciones_largas %>% filter(Simulacion %in% paste0("V", 1:10)),
aes(x = Dia, y = Precio, color = Accion, group = interaction(Simulacion, Accion))) +
geom_line(alpha = 0.3) +
labs(title = "Ejemplo de 10 trayectorias simuladas por acción", x = "Día", y = "Precio") +
theme_minimal()

🛡️ Cobertura con Opciones Put Europeas y Valor
Protegido
# Suponiendo precios actuales y strikes ligeramente por debajo
strike_prices <- valores_finales * 0.95
T <- 0.25
vol <- sigma
valores_put <- mapply(function(S, K, v) {
EuropeanOption(type = "put", underlying = S, strike = K,
dividendYield = rf, riskFreeRate = rf,
maturity = T, volatility = v)$value
}, S = valores_finales, K = strike_prices, v = vol)
puts_compradas <- acciones_compradas
valor_total_opciones <- puts_compradas * valores_put
# Valor final promedio del portafolio simulado (último día de cada trayectoria)
promedio_final_portafolio <- colMeans(resultados_finales)
valor_final_total_promedio <- sum(promedio_final_portafolio * acciones_compradas)
valor_inicial <- sum(inversion_realizada)
perdida_promedio <- valor_inicial - valor_final_total_promedio
proteccion_total <- sum(valor_total_opciones)
valor_neto_protegido <- valor_final_total_promedio + proteccion_total
resultado_df <- data.frame(
Inversion_Inicial = valor_inicial,
Valor_Final_Promedio = valor_final_total_promedio,
Perdida_Promedio = perdida_promedio,
Cobertura_Obtenida = proteccion_total,
Valor_Neto_Tras_Cobertura = valor_neto_protegido
)
kable(round(resultado_df, 2), caption = "Evaluación de la Cobertura con Opciones sobre la Pérdida Promedio")
Evaluación de la Cobertura con Opciones sobre la Pérdida
Promedio
| 999986.2 |
0 |
999986.2 |
126.21 |
126.21 |
📉 Análisis de Riesgo: Percentiles del Portafolio Final
Simulado
valores_totales_sim <- colSums(t(resultados_finales) * acciones_compradas)
VaR_10 <- quantile(valores_totales_sim, 0.10)
VaR_5 <- quantile(valores_totales_sim, 0.05)
VaR_1 <- quantile(valores_totales_sim, 0.01)
percentiles_df <- data.frame(
Percentil = c("10%", "5%", "1%"),
Valor = c(VaR_10, VaR_5, VaR_1)
)
percentiles_df$Valor <- round(percentiles_df$Valor, 2)
kable(percentiles_df, caption = "Valor en Riesgo (VaR) del Portafolio Simulado")
Valor en Riesgo (VaR) del Portafolio Simulado
| 10% |
10% |
0 |
| 5% |
5% |
0 |
| 1% |
1% |
0 |
# 📊 *Comparación con Portafolio Equitativo*
pesos_eq <- rep(1 / length(stocks), length(stocks))
ret_eq <- sum(pesos_eq * mu)
riesgo_eq <- sqrt(t(pesos_eq) %*% cov_matrix %*% pesos_eq)
sharpe_eq <- ret_eq / riesgo_eq
📉 Comparación final de estrategias de portafolio
ret_opt <- sum(pesos_tangency * mu)
riesgo_opt <- sqrt(t(pesos_tangency) %*% cov_matrix %*% pesos_tangency)
sharpe_opt <- ret_opt / riesgo_opt
comparacion_final <- rbind(
data.frame(Portafolio = "Equitativo", Retorno = ret_eq, Riesgo = riesgo_eq, Sharpe = sharpe_eq),
data.frame(Portafolio = "Óptimo Tangencia", Retorno = ret_opt, Riesgo = riesgo_opt, Sharpe = sharpe_opt)
)
knitr::kable(comparacion_final, caption = "Comparación Final de Portafolios: Equitativo vs. Óptimo")
Comparación Final de Portafolios: Equitativo
vs. Óptimo
| Equitativo |
-0.0004005 |
0.0368799 |
-0.0108589 |
| Óptimo Tangencia |
-0.0012768 |
0.0333347 |
-0.0383033 |
📊 Conclusión: - El portafolio óptimo presenta un
Sharpe ratio superior, lo cual indica mejor rendimiento ajustado por
riesgo. - Las simulaciones y estrategias de cobertura permiten
fortalecer la gestión del portafolio en escenarios adversos. - Este
análisis integral ayuda a tomar decisiones de inversión más informadas y
alineadas con el perfil de riesgo-retorno deseado.
📊 Minimum Variance Portfolio Weights
Dmat <- cov_matrix
dvec <- rep(0, length(mu))
Amat <- cbind(rep(1, length(mu))) # sum of weights = 1
bvec <- 1
meq <- 1
sol_mv <- solve.QP(Dmat, dvec, Amat, bvec, meq)
weights_mv <- sol_mv$solution
names(weights_mv) <- colnames(returns)
kable(
data.frame(Assets = names(weights_mv), Weights = round(weights_mv, 4)),
caption = "Minimum Variance Portfolio Weights"
)
Minimum Variance Portfolio Weights
| RXRX |
RXRX |
0.1018 |
| MRNA |
MRNA |
0.6525 |
| BRPHF |
BRPHF |
0.2457 |
# Visualización
library(ggplot2)
ggplot(data.frame(Assets = names(weights_mv), Weights = weights_mv), aes(x = Assets, y = Weights, fill = Assets)) +
geom_bar(stat = "identity") +
labs(title = "Composición del Portafolio de Varianza Mínima", y = "Peso") +
theme_minimal()

📊 Composición del Portafolio Óptimo
optimal_weights <- pesos_tangency
names(optimal_weights) <- stocks
kable(
data.frame(Assets = names(optimal_weights), Weights = round(optimal_weights, 4)),
caption = "Composición del Portafolio Óptimo (Tangencia)"
)
Composición del Portafolio Óptimo (Tangencia)
| RXRX |
RXRX |
0.0886 |
| MRNA |
MRNA |
0.6921 |
| BRPHF |
BRPHF |
0.2193 |
# Visualización
ggplot(data.frame(Assets = names(optimal_weights), Weights = optimal_weights), aes(x = Assets, y = Weights, fill = Assets)) +
geom_bar(stat = "identity") +
labs(title = "Composición del Portafolio Óptimo (Tangencia)", y = "Peso") +
theme_minimal()
