🔹 Configuración del Entorno

1️⃣ Carga de Paquetes y Preparación del Entorno

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)

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")
## [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)

💼 Estructura del Portafolio Invertido

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.

4.5️⃣ ✳️ Ejemplo Paso a Paso de Optimización de un Portafolio

# 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

🔝 Portafolio de Máxima Pendiente (Tangency Portfolio)

# 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

📈 Visualización de Portafolios Aleatorios y Frontera Eficiente

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
  }
}

📉 Visualización de Trayectorias Simuladas (con tendencia bajista)

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")

Análisis:

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.

4️⃣ Cobertura con Opciones Put Europeas

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

Análisis:

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.

🌳 Árbol Binomial - Evolución de Precios y Cobertura

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
}")

Análisis del Árbol:

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.

5️⃣ Simulación de Carteras y Frontera Eficiente

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, ]

6️⃣ Visualización de Frontera Eficiente

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()

Recomendación final:

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.