options(repos = c(CRAN = "https://cloud.r-project.org"))
try({
to_install <- c("quantmod", "PerformanceAnalytics", "tseries", "ggplot2",
"dplyr", "tidyverse", "lubridate", "tibble", "RQuantLib",
"scales", "data.tree", "DiagrammeR", "reshape2")
missing_pkgs <- to_install[!(to_install %in% installed.packages()[, "Package"])]
if(length(missing_pkgs)) install.packages(missing_pkgs)
}, silent = TRUE)
library(quantmod)
library(PerformanceAnalytics)
library(tseries)
library(ggplot2)
library(dplyr)
library(tidyverse)
library(lubridate)
library(tibble)
library(RQuantLib)
library(scales)
library(data.tree)
library(DiagrammeR)
library(reshape2)
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")
## [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)
inversion_total <- 1e6 # USD 1 millón
pesos_eq <- rep(1 / length(stocks), length(stocks))
valores_iniciales <- as.numeric(prices[nrow(prices), ]) * pesos_eq * inversion_total / as.numeric(prices[nrow(prices), ])
nombres_acciones <- stocks
portafolio_inicial <- data.frame(Accion = nombres_acciones, Precio = as.numeric(prices[nrow(prices), ]),
Peso = pesos_eq, Monto = pesos_eq * inversion_total,
Num_Acciones = valores_iniciales)
portafolio_inicial
## Accion Precio Peso Monto Num_Acciones
## 1 RXRX 5.810 0.3333333 333333.3 333333.3
## 2 MRNA 31.120 0.3333333 333333.3 333333.3
## 3 BRPHF 11.491 0.3333333 333333.3 333333.3
Este portafolio invierte USD 1 millón distribuidos equitativamente en las tres acciones. Se adquiere el número correspondiente de acciones a precios actuales.
# Cálculo de rendimientos esperados (anualizados)
mu_annual <- mu * 252
# Matriz de covarianzas anualizada
cov_annual <- cov_matrix * 252
# Creamos pesos aleatorios
random_weights <- runif(length(stocks))
random_weights <- random_weights / sum(random_weights)
# Retorno esperado del portafolio
retorno_portafolio <- sum(random_weights * mu_annual)
# Riesgo del portafolio (desviación estándar)
riesgo_portafolio <- sqrt(t(random_weights) %*% cov_annual %*% random_weights)
# Ratio de Sharpe (suponiendo tasa libre de riesgo = 0%)
sharpe_ratio <- retorno_portafolio / riesgo_portafolio
# Mostrar resultados
cat("🔹 Retorno esperado anual del portafolio:", round(retorno_portafolio, 4), "\n")
## 🔹 Retorno esperado anual del portafolio: 0.0615
cat("🔹 Riesgo anual del portafolio (volatilidad):", round(riesgo_portafolio, 4), "\n")
## 🔹 Riesgo anual del portafolio (volatilidad): 0.6633
cat("🔹 Ratio de Sharpe:", round(sharpe_ratio, 4), "\n")
## 🔹 Ratio de Sharpe: 0.0928
# Tabla de pesos
data.frame(
Accion = stocks,
Peso = round(random_weights, 4)
)
## Accion Peso
## 1 RXRX 0.2314
## 2 MRNA 0.1639
## 3 BRPHF 0.6047
# Identificación del portafolio de tangencia dentro de una simulación de muchos portafolios
set.seed(123)
num_portfolios <- 5000
resultados <- data.frame(Retorno = numeric(num_portfolios), Riesgo = numeric(num_portfolios), Sharpe = numeric(num_portfolios))
pesos_lista <- matrix(NA, nrow = num_portfolios, ncol = length(stocks))
for (i in 1:num_portfolios) {
w <- runif(length(stocks))
w <- w / sum(w)
ret <- sum(w * mu_annual)
risk <- sqrt(t(w) %*% cov_annual %*% w)
sharpe <- ret / risk
resultados[i, ] <- c(ret, risk, sharpe)
pesos_lista[i, ] <- w
}
# Portafolio de tangencia (mayor Sharpe)
indice_tangency <- which.max(resultados$Sharpe)
pesos_tangency <- pesos_lista[indice_tangency, ]
retorno_tangency <- resultados$Retorno[indice_tangency]
riesgo_tangency <- resultados$Riesgo[indice_tangency]
sharpe_tangency <- resultados$Sharpe[indice_tangency]
# Portafolio de mínima varianza
indice_minvar <- which.min(resultados$Riesgo)
pesos_minvar <- pesos_lista[indice_minvar, ]
retorno_minvar <- resultados$Retorno[indice_minvar]
riesgo_minvar <- resultados$Riesgo[indice_minvar]
sharpe_minvar <- resultados$Sharpe[indice_minvar]
cat("🔸 Portafolio de máxima pendiente:", "\n")
## 🔸 Portafolio de máxima pendiente:
cat("Retorno esperado:", round(retorno_tangency, 4), "\n")
## Retorno esperado: 0.2334
cat("Volatilidad esperada:", round(riesgo_tangency, 4), "\n")
## Volatilidad esperada: 0.8376
cat("Sharpe ratio:", round(sharpe_tangency, 4), "\n")
## Sharpe ratio: 0.2787
cat("\n🔸 Portafolio de mínima varianza:", "\n")
##
## 🔸 Portafolio de mínima varianza:
cat("Retorno esperado:", round(retorno_minvar, 4), "\n")
## Retorno esperado: -0.2937
cat("Volatilidad esperada:", round(riesgo_minvar, 4), "\n")
## Volatilidad esperada: 0.5282
cat("Sharpe ratio:", round(sharpe_minvar, 4), "\n")
## Sharpe ratio: -0.5561
# Pesos
pesos_tangency_df <- data.frame(Accion = stocks, Peso = round(pesos_tangency, 4))
pesos_minvar_df <- data.frame(Accion = stocks, Peso = round(pesos_minvar, 4))
pesos_tangency_df
## Accion Peso
## 1 RXRX 0.0605
## 2 MRNA 0.0058
## 3 BRPHF 0.9336
pesos_minvar_df
## Accion Peso
## 1 RXRX 0.1067
## 2 MRNA 0.6509
## 3 BRPHF 0.2424
ggplot(resultados, aes(x = Riesgo, y = Retorno)) +
geom_point(aes(color = Sharpe), alpha = 0.5) +
geom_point(aes(x = riesgo_tangency, y = retorno_tangency), color = "red", size = 3) +
geom_point(aes(x = riesgo_minvar, y = retorno_minvar), color = "blue", size = 3) +
annotate("text", x = riesgo_tangency, y = retorno_tangency, label = "Portafolio Tangente", vjust = -1, color = "red") +
annotate("text", x = riesgo_minvar, y = retorno_minvar, label = "Portafolio Min Varianza", vjust = -1, color = "blue") +
labs(title = "Frontera Eficiente - Simulación de 5000 Portafolios",
x = "Riesgo (Volatilidad)",
y = "Retorno Esperado") +
scale_color_gradient(low = "blue", high = "green") +
theme_minimal()
# 3️⃣ *Simulación de Precios con Movimiento Geométrico Browniano (MGB)*
set.seed(123)
sim_days <- 252 * 2 # 2 años
num_sims <- 5000
dt <- 1 / 252
sigma <- apply(returns, 2, sd)
MGB_sim <- array(NA, dim = c(sim_days, length(stocks), num_sims))
dimnames(MGB_sim) <- list(NULL, stocks, NULL)
for (sim in 1:num_sims) {
for (i in 1:length(stocks)) {
last_price <- as.numeric(prices[nrow(prices), i])
S_t <- numeric(sim_days)
S_t[1] <- last_price
for (t in 2:sim_days) {
Z_t <- rnorm(1)
S_t[t] <- S_t[t-1] * exp((mu[i] - 0.5 * sigma[i]^2) * dt + sigma[i] * sqrt(dt) * Z_t)
}
MGB_sim[, i, sim] <- S_t
}
}
df_sim <- data.frame(
Día = rep(1:sim_days, length(stocks) * num_sims),
Precio = as.vector(MGB_sim),
Acción = rep(rep(stocks, each = sim_days), times = num_sims),
Simulación = rep(1:num_sims, each = sim_days * length(stocks))
)
sample_df <- df_sim %>% filter(Simulación <= 30)
ggplot(sample_df, aes(x = Día, y = Precio, color = Acción, group = interaction(Simulación, Acción))) +
geom_line(alpha = 0.2) +
facet_wrap(~Acción, scales = "free_y") +
theme_minimal() +
labs(
title = "Simulación de Precios con Movimiento Geométrico Browniano",
x = "Día Simulado",
y = "Precio"
) +
theme(legend.position = "none")
El comportamiento simulado muestra la posible caída de los precios en las acciones seleccionadas. Este escenario justifica el uso de instrumentos derivados como opciones put para cubrir las pérdidas.
S <- as.numeric(prices[nrow(prices), ])
K <- S
T_exp <- 2
r <- 0.04
sigma_opt <- sigma
binomial_puts <- mapply(function(Si, Ki, voli) {
EuropeanOption(type = "put", underlying = Si, strike = Ki,
dividendYield = 0, riskFreeRate = r, maturity = T_exp,
volatility = voli)$value
}, S, K, sigma_opt)
costo_total_opciones <- sum(binomial_puts)
monto_cobertura <- inversion_total * 0.85
num_contratos <- monto_cobertura / costo_total_opciones
num_contratos
## [1] 5418232
Se calcula la prima de opciones put para cada acción como instrumento de cobertura. Se estima cuántos contratos se pueden adquirir con un 85% del portafolio destinado a protección.
S0_example <- 100
u <- 1.1
d <- 0.9
DiagrammeR::grViz("
digraph binomial_tree {
node [shape = circle, style=filled, color=lightblue]
A [label = '100']
B [label = '110']
C [label = '90']
D [label = '121']
E [label = '99']
F [label = '99']
G [label = '81']
A -> B
A -> C
B -> D
B -> E
C -> F
C -> G
}")
El árbol representa tres trimestres de evolución de precios con nodos que permiten calcular los pagos de opciones. Si la estrategia considera solo 3 trimestres, debe justificarse como una cobertura parcial frente a la caída más esperada en el corto plazo. Debe incluirse también la ganancia esperada por el ejercicio de puts en nodos donde el precio baja.
set.seed(123)
n_sim <- 5000
pesos_random <- matrix(runif(n_sim * length(stocks)), ncol = length(stocks))
pesos_random <- t(apply(pesos_random, 1, function(x) x / sum(x)))
retornos_random <- pesos_random %*% mu
riesgo_random <- sqrt(rowSums((pesos_random %*% cov_matrix) * pesos_random))
sharpe_random <- retornos_random / riesgo_random
idx_tangency <- which.max(sharpe_random)
pesos_tangency <- pesos_random[idx_tangency, ]
ggplot() +
geom_point(aes(x = riesgo_random, y = retornos_random), alpha = 0.3, color = "blue") +
geom_point(aes(x = sqrt(t(pesos_tangency) %*% cov_matrix %*% pesos_tangency),
y = sum(pesos_tangency * mu)), color = "red", size = 3) +
labs(title = "Frontera Eficiente y Cartera Óptima",
x = "Volatilidad", y = "Retorno Esperado") +
theme_minimal()
Incluir cálculo explícito del valor de la cartera bajo escenarios simulados y payoff esperado con cobertura (puts ejercidas). Mostrar comparativo: valor portafolio sin cobertura vs. valor con cobertura en los escenarios bajistas.