# Paqueteria necesaria
required_packages <- c("quantmod", "PerformanceAnalytics", "zoo", "PortfolioAnalytics", "xts",
"tidyquant", "plotly", "timetk", "ggplot2", "doParallel", "derivmkts")
new_packages <- required_packages[!(required_packages %in% installed.packages()[,"Package"])]
if(length(new_packages) > 0) {
cat("Installing missing packages:", paste(new_packages, collapse=", "), "\n")
install.packages(new_packages)
}
# Cargar bibliotecas necesarias
library(quantmod)
## Warning: package 'quantmod' was built under R version 4.3.3
## Loading required package: xts
## Warning: package 'xts' was built under R version 4.3.3
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.3.3
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
## Loading required package: TTR
## Warning: package 'TTR' was built under R version 4.3.3
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
library(PerformanceAnalytics)
## Warning: package 'PerformanceAnalytics' was built under R version 4.3.3
##
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
##
## legend
library(zoo)
library(PortfolioAnalytics)
## Warning: package 'PortfolioAnalytics' was built under R version 4.3.3
## Loading required package: foreach
## Warning: package 'foreach' was built under R version 4.3.3
## Registered S3 method overwritten by 'PortfolioAnalytics':
## method from
## print.constraint ROI
library(xts)
library(tidyquant)
## Warning: package 'tidyquant' was built under R version 4.3.3
## ── Conflicts ────────────────────────────────────────── tidyquant_conflicts() ──
## ✖ zoo::as.Date() masks base::as.Date()
## ✖ zoo::as.Date.numeric() masks base::as.Date.numeric()
## ✖ PerformanceAnalytics::legend() masks graphics::legend()
## ✖ quantmod::summary() masks base::summary()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(plotly)
## Warning: package 'plotly' was built under R version 4.3.3
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 4.3.3
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(timetk)
## Warning: package 'timetk' was built under R version 4.3.3
##
## Attaching package: 'timetk'
## The following object is masked from 'package:tidyquant':
##
## FANG
library(ggplot2)
library(doParallel)
## Warning: package 'doParallel' was built under R version 4.3.3
## Loading required package: iterators
## Warning: package 'iterators' was built under R version 4.3.3
## Loading required package: parallel
library(derivmkts)
## Warning: package 'derivmkts' was built under R version 4.3.3
# Configurar procesamiento paralelo
registerDoParallel(cores = detectCores() - 1)
# Semilla para reproducibilidad
set.seed(567)
# 1. Importar datos desde 01/06/2022
start_date <- as.Date("2022-06-01")
end_date <- as.Date("2025-03-31") # Usar fecha actual para datos disponibles
symbols <- c("TSLA", "NKLAQ", "MU")
tryCatch({
getSymbols(symbols, src = "yahoo", from = start_date, to = end_date)
}, error = function(e) {
cat("Error al obtener símbolos:", e$message, "\n")
# Verificar cada símbolo individualmente
for(sym in symbols) {
tryCatch({
getSymbols(sym, src = "yahoo", from = start_date, to = end_date)
cat("Símbolo", sym, "cargado correctamente\n")
}, error = function(err) {
cat("Error al cargar", sym, ":", err$message, "\n")
})
}
})
## [1] "TSLA" "NKLAQ" "MU"
# Verificar que todos los símbolos estén disponibles
available_symbols <- symbols[sapply(symbols, exists)]
if (length(available_symbols) < length(symbols)) {
missing_symbols <- setdiff(symbols, available_symbols)
cat("Advertencia: Los siguientes símbolos no están disponibles:", paste(missing_symbols, collapse=", "), "\n")
cat("Continuando con los símbolos disponibles:", paste(available_symbols, collapse=", "), "\n")
symbols <- available_symbols
}
# Precios de cierre ajustados
if (length(symbols) >= 2) {
# Construir el merge dinámicamente
stock_prices <- get(symbols[1])
stock_prices <- stock_prices[, grep("Adjusted", colnames(stock_prices))]
for (i in 2:length(symbols)) {
sym_data <- get(symbols[i])
sym_data <- sym_data[, grep("Adjusted", colnames(sym_data))]
stock_prices <- merge(stock_prices, sym_data)
# **# Comentario: Los retornos diarios revelan la dinámica de volatilidad entre los activos seleccionados (TSLA, NKLAQ, MU). Es fundamental observar que cada acción presenta un comportamiento único en términos de rendimiento, lo que sugiere la importancia de una diversificación estratégica para mitigar riesgos individuales. La variabilidad en los retornos indica que no todos los activos responden de manera similar a las condiciones de mercado, resaltando la necesidad de un análisis detallado de cada instrumento.**
}
}
# Renombrar columnas
colnames(stock_prices) <- symbols
# Calcular retornos diarios
returns <- na.omit(ROC(stock_prices, type = "discrete"))
# 2. Estadísticas clave
mean_returns <- colMeans(returns, na.rm = TRUE)
cov_matrix <- cov(returns) * 252 # Anualizada
annual_volatility <- apply(returns, 2, sd, na.rm = TRUE) * sqrt(252)
current_prices <- as.numeric(last(stock_prices))
# Verificar precios y volatilidades
cat("Precios actuales:", paste(symbols, round(current_prices, 2), sep = "="), "\n")
## Precios actuales: TSLA=263.55 NKLAQ=0.23 MU=88.33
cat("Volatilidades anualizadas:", paste(symbols, round(annual_volatility, 4), sep = "="), "\n")
## Volatilidades anualizadas: TSLA=0.61 NKLAQ=1.5284 MU=0.4647
# Identificar la acción más en caída
cumulative_returns <- numeric(length(symbols))
for (i in 1:length(symbols)) {
cumulative_returns[i] <- prod(1 + na.omit(ROC(stock_prices[, i], type = "discrete"))) - 1
}
worst_performer <- symbols[which.min(cumulative_returns)]
cat("Rendimientos acumulados:", paste(symbols, round(cumulative_returns * 100, 2), "%", sep = "="), "\n")
## Rendimientos acumulados: TSLA=6.79=% NKLAQ=-99.88=% MU=22.37=%
cat("Acción más en caída:", worst_performer, "\n")
## Acción más en caída: NKLAQ
# 3. Optimización del portafolio
portfolio_spec <- portfolio.spec(assets = symbols)
portfolio_spec <- add.constraint(portfolio_spec, type = "weight_sum", min_sum = 0.99, max_sum = 1.01)
portfolio_spec <- add.constraint(portfolio_spec, type = "box", min = 0.1, max = 0.8)
portfolio_spec <- add.objective(portfolio_spec, type = "risk", name = "var")
opt_portfolio <- optimize.portfolio(returns, portfolio_spec, optimize_method = "random",
trace = TRUE, rp = random_portfolios(portfolio_spec, 1000))
opt_weights <- extractWeights(opt_portfolio)
total_investment <- 1000000 # $1M
allocation <- opt_weights * total_investment
# **# Comentario: La estrategia de optimización implementada aplica restricciones cruciales que reflejan una gestión de riesgo conservadora. Al establecer límites de peso entre 0.1 y 0.8 para cada activo, se busca prevenir una concentración excesiva en un solo instrumento. La minimización de la varianza (VaR), como objetivo principal indica un enfoque orientado a reducir la volatilidad potencial del portafolio,privilegiando la estabilidad sobre el rendimiento máximo.**
# 4. Generación de portafolios aleatorios para la frontera eficiente
num_port <- 5000
all_wts <- matrix(nrow = num_port, ncol = length(symbols))
port_returns <- vector('numeric', length = num_port)
port_risk <- vector('numeric', length = num_port)
sharpe_ratio <- vector('numeric', length = num_port)
for (i in 1:num_port) {
wts <- runif(length(symbols))
wts <- wts / sum(wts)
all_wts[i, ] <- wts
port_ret <- sum(wts * mean_returns) * 252
port_returns[i] <- port_ret
# Multiplicación matricial correcta para cálculo de varianza
port_sd <- sqrt(t(wts) %*% (cov_matrix %*% wts))
port_risk[i] <- port_sd
sr <- port_ret / port_sd
sharpe_ratio[i] <- sr
}
portfolio_values <- data.frame(Return = port_returns, Risk = port_risk, SharpeRatio = sharpe_ratio)
all_wts_df <- as.data.frame(all_wts)
colnames(all_wts_df) <- symbols
portfolio_values <- cbind(all_wts_df, portfolio_values)
min_var <- portfolio_values[which.min(portfolio_values$Risk), ]
max_sr <- portfolio_values[which.max(portfolio_values$SharpeRatio), ]
# 5. Simulación de Monte Carlo (GBM)
days_per_quarter <- 63
max_quarters <- 8
simulation_days <- days_per_quarter * max_quarters
num_simulations <- 500
annual_returns <- mean_returns * 252
current_prices <- as.numeric(last(stock_prices))
# Función de simulación con validación
simulate_gbm <- function(S0, mu, sigma, T, dt, n_sims) {
# Validación de entrada
if (is.na(S0) || is.na(mu) || is.na(sigma) || is.na(T) || is.na(dt) || is.na(n_sims) ||
S0 <= 0 || T <= 0 || dt <= 0 || n_sims <= 0 || sigma <= 0) {
warning("Parámetros inválidos en simulate_gbm")
return(matrix(NA, nrow = ceiling(T / dt) + 1, ncol = n_sims))
}
n_steps <- ceiling(T / dt)
dt <- T / n_steps
S <- matrix(0, nrow = n_steps + 1, ncol = n_sims)
S[1, ] <- S0
for (i in 1:n_sims) {
for (t in 1:n_steps) {
S[t+1, i] <- S[t, i] * exp((mu - 0.5 * sigma^2) * dt + sigma * sqrt(dt) * rnorm(1))
}
}
return(S)
}
simulations <- list()
for (i in 1:length(symbols)) {
simulations[[i]] <- simulate_gbm(S0 = current_prices[i], mu = annual_returns[i],
sigma = annual_volatility[i], T = 2, dt = 1/252, n_sims = num_simulations)
}
# **# Comentario: La simulación de Movimiento Browniano Geométrico (GBM) con 500 trayectorias proporciona una perspectiva robusta de los posibles escenarios de evolución del portafolio. Al simular 2 años con rebalanceo trimestral, se captura la naturaleza dinámica de los mercados financieros. Los resultados destacan la importancia de estrategias adaptativas que permitan ajustar la composición del portafolio ante cambios en las condiciones de mercado.Función binomial árbol con validación**
# 6. Valor del portafolio simulado
portfolio_values_sim <- matrix(0, nrow = simulation_days + 1, ncol = num_simulations)
for (sim in 1:num_simulations) {
portfolio_values_sim[1, sim] <- total_investment
quantities <- allocation / current_prices
for (day in 1:simulation_days) {
day_index <- min(day, nrow(simulations[[1]]) - 1) + 1
stock_values <- sapply(1:length(symbols), function(i) quantities[i] * simulations[[i]][day_index, sim])
portfolio_values_sim[day + 1, sim] <- sum(stock_values)
# Rebalanceo trimestral
if (day %% days_per_quarter == 0) {
current_sim_prices <- sapply(1:length(symbols), function(i) simulations[[i]][day_index, sim])
quantities <- (opt_weights * portfolio_values_sim[day + 1, sim]) / current_sim_prices
}
}
}
# 7. Estadísticas del portafolio
mean_final_value <- mean(portfolio_values_sim[nrow(portfolio_values_sim), ])
ci_95 <- quantile(portfolio_values_sim[nrow(portfolio_values_sim), ], c(0.025, 0.975))
daily_port_returns <- diff(rowMeans(portfolio_values_sim)) / head(rowMeans(portfolio_values_sim), -1)
portfolio_volatility_annual <- sd(daily_port_returns) * sqrt(252)
portfolio_return_annual <- (mean_final_value / total_investment)^(1/2) - 1
risk_free_rate <- 0.03
sharpe_ratio_opt <- (portfolio_return_annual - risk_free_rate) / portfolio_volatility_annual
final_values <- portfolio_values_sim[nrow(portfolio_values_sim), ]
VaR_1 <- quantile(final_values, 0.01)
VaR_5 <- quantile(final_values, 0.05)
# 8. Valuación de Opciones (Exclusivamente Binomial para todas las acciones)
T <- 2
r <- risk_free_rate
strike <- current_prices # At-the-money
binomial_tree <- function(S, K, T, r, sigma, N, type, is_european = FALSE) {
# Validación de parámetros
if (is.na(S) || is.na(K) || is.na(T) || is.na(r) || is.na(sigma) || is.na(N) ||
S <= 0 || K <= 0 || T <= 0 || N <= 0 || sigma <= 0) {
warning("Parámetros inválidos en binomial_tree")
return(NA)
}
dt <- T / N
u <- exp(sigma * sqrt(dt))
d <- 1 / u
p <- (exp(r * dt) - d) / (u - d)
# Verificación de probabilidad de riesgo neutral
if (p < 0 || p > 1) {
warning("Probabilidad de riesgo neutral fuera de rango [0,1]: ", p)
p <- max(0, min(1, p))
}
# Construir el árbol de precios
tree <- matrix(0, nrow = N + 1, ncol = N + 1)
# Llenar el árbol con precios
for (i in 0:N) {
for (j in 0:i) {
tree[j + 1, i + 1] <- S * u^j * d^(i - j)
}
}
# Calcular valores de la opción en las hojas del árbol
if (type == "call") {
tree[, N + 1] <- pmax(tree[, N + 1] - K, 0)
} else {
tree[, N + 1] <- pmax(K - tree[, N + 1], 0)
}
# Retroceder en el árbol
for (i in N:1) {
for (j in 0:(i - 1)) {
# Valor esperado descontado
tree[j + 1, i] <- exp(-r * dt) * (p * tree[j + 2, i + 1] + (1 - p) * tree[j + 1, i + 1])
# Para opciones americanas, comparar con valor intrínseco
if (!is_european) {
price_at_node <- S * u^j * d^(i - j)
if (type == "call") {
intrinsic_value <- max(0, price_at_node - K)
} else {
intrinsic_value <- max(0, K - price_at_node)
}
tree[j + 1, i] <- max(tree[j + 1, i], intrinsic_value)
}
}
}
return(tree[1, 1])
}
option_values <- list()
for (i in 1:length(symbols)) {
S <- current_prices[i]
K <- strike[i]
sigma <- annual_volatility[i]
if (is.na(S) || is.na(K) || is.na(sigma) || S <= 0 || K <= 0 || sigma <= 0) {
cat("Advertencia: Valores inválidos para", symbols[i], "- S =", S, "K =", K, "sigma =", sigma, "\n")
next
}
option_values[[symbols[i]]] <- list(
call_eu = binomial_tree(S, K, T, r, sigma, 100, "call", is_european = TRUE),
put_eu = binomial_tree(S, K, T, r, sigma, 100, "put", is_european = TRUE),
call_am = binomial_tree(S, K, T, r, sigma, 100, "call", is_european = FALSE),
put_am = binomial_tree(S, K, T, r, sigma, 100, "put", is_european = FALSE)
)
}
# **# Comentario:El análisis comparativo entre opciones europeas y americanas revela diferencias significativas en la valoración. Las opciones americanas generalmente presentan un valor superior debido a su flexibilidad de ejercicio anticipado. El uso del método binomial proporciona una aproximación detallada que considera la evolución potencial de los precios, permitiendo una evaluación más precisa de los instrumentos derivados.**
# 9. Valuación de Opciones Put con análisis específico para la Acción más en Caída
worst_index <- which(symbols == worst_performer)
s <- current_prices[worst_index] # Precio actual
k <- s # At-the-money
tt <- 0.25 # 3 meses
r <- 0.03 # Tasa libre de riesgo
d <- 0 # Sin dividendos (puedes ajustar si tienes datos)
v <- annual_volatility[worst_index] # Volatilidad
nstep <- 50 # Pasos para el árbol
# Verificar parámetros
cat("Parámetros para", worst_performer, ":\n")
## Parámetros para NKLAQ :
cat("s =", s, "k =", k, "v =", v, "r =", r, "tt =", tt, "d =", d, "nstep =", nstep, "\n")
## s = 0.235 k = 0.235 v = 1.528407 r = 0.03 tt = 0.25 d = 0 nstep = 50
if (is.na(s) || is.na(k) || is.na(v) || s <= 0 || k <= 0 || v <= 0 || v > 2 || is.na(tt) || tt <= 0) {
cat("Error: Parámetros inválidos para valuación. Usando binomial_tree como respaldo.\n")
put_eu_derivmkts <- binomial_tree(s, k, tt, r, v, nstep, "put", is_european = TRUE)
put_am_derivmkts <- binomial_tree(s, k, tt, r, v, nstep, "put", is_european = FALSE)
} else {
# Intentar calcular con derivmkts o usar binomial_tree como respaldo
tryCatch({
put_eu_derivmkts <- binomopt(s, k, v, r, tt, d, n = nstep, american = FALSE, put = TRUE)
put_am_derivmkts <- binomopt(s, k, v, r, tt, d, n = nstep, american = TRUE, put = TRUE)
}, error = function(e) {
cat("Error en binomopt:", e$message, "\nUsando binomial_tree como respaldo.\n")
put_eu_derivmkts <<- binomial_tree(s, k, tt, r, v, nstep, "put", is_european = TRUE)
put_am_derivmkts <<- binomial_tree(s, k, tt, r, v, nstep, "put", is_european = FALSE)
})
}
# Implementación propia de visualización de árboles binomiales
if (exists("put_eu_derivmkts") && exists("put_am_derivmkts") && !is.na(put_eu_derivmkts) && !is.na(put_am_derivmkts)) {
# Función para crear un árbol binomial visualizable
crear_arbol_binomial <- function(S, K, T, r, sigma, N, type, is_european) {
# Calcular parámetros del árbol
dt <- T / N
u <- exp(sigma * sqrt(dt))
d <- 1 / u
p <- (exp(r * dt) - d) / (u - d)
# Crear matriz para precios
precios <- matrix(0, nrow = N + 1, ncol = N + 1)
# Llenar el árbol con precios
for (i in 0:N) {
for (j in 0:i) {
precios[j + 1, i + 1] <- S * u^j * d^(i - j)
}
}
# Crear matriz para valores de opciones
valores <- matrix(0, nrow = N + 1, ncol = N + 1)
# Valores finales (vencimiento)
if (type == "call") {
valores[, N + 1] <- pmax(precios[, N + 1] - K, 0)
} else {
valores[, N + 1] <- pmax(K - precios[, N + 1], 0)
}
# Retroceder en el árbol
for (i in N:1) {
for (j in 0:(i - 1)) {
# Valor esperado descontado
valores[j + 1, i] <- exp(-r * dt) * (p * valores[j + 2, i + 1] + (1 - p) * valores[j + 1, i + 1])
# Para opciones americanas, comparar con valor intrínseco
if (!is_european) {
intrinsic <- if(type == "call") {
max(0, precios[j + 1, i] - K)
} else {
max(0, K - precios[j + 1, i])
}
valores[j + 1, i] <- max(valores[j + 1, i], intrinsic)
}
}
}
# Regresar datos para graficar
return(list(
precios = precios,
valores = valores,
p = p,
u = u,
d = d,
dt = dt
))
}
# Función para visualizar un árbol simple (primeros niveles)
visualizar_arbol <- function(arbol, S, K, type, titulo, n_niveles = min(5, nstep)) {
# Crear una nueva ventana gráfica
par(mar = c(1,1,3,1))
# Determinar dimensiones
niveles <- min(n_niveles, ncol(arbol$precios))
max_nodos <- niveles
# Crear matriz de coordenadas
coords_x <- matrix(0, nrow = max_nodos, ncol = niveles)
coords_y <- matrix(0, nrow = max_nodos, ncol = niveles)
# Calcular coordenadas
for (i in 1:niveles) {
for (j in 1:i) {
coords_x[j, i] <- i
coords_y[j, i] <- j - (i/2)
}
}
# Iniciar gráfico vacío
plot(0, 0, type = "n", xlim = c(0.5, niveles+0.5), ylim = c(-max_nodos/2, max_nodos/2),
xlab = "", ylab = "", axes = FALSE, main = titulo)
# Dibujar nodos y conexiones
for (i in 1:niveles) {
for (j in 1:i) {
# Dibujar nodo
precio <- round(arbol$precios[j, i], 2)
valor <- round(arbol$valores[j, i], 2)
# Dibujar líneas conectoras (excepto en el primer nivel)
if (i > 1) {
if (j <= i-1) { # Línea desde arriba
lines(c(coords_x[j, i-1], coords_x[j, i]),
c(coords_y[j, i-1], coords_y[j, i]),
col = "gray")
}
if (j > 1) { # Línea desde abajo
lines(c(coords_x[j-1, i-1], coords_x[j, i]),
c(coords_y[j-1, i-1], coords_y[j, i]),
col = "gray")
}
}
# Dibujar círculo para el nodo
points(coords_x[j, i], coords_y[j, i], pch = 21,
bg = ifelse(valor > 0, "lightblue", "white"), cex = 2)
# Añadir texto
text(coords_x[j, i], coords_y[j, i] + 0.1, sprintf("%.2f", precio), cex = 0.8)
text(coords_x[j, i], coords_y[j, i] - 0.1, sprintf("%.2f", valor), cex = 0.8, col = "darkblue")
}
}
# Añadir leyenda
legend("topright",
legend = c(sprintf("S0 = %.2f", S),
sprintf("K = %.2f", K),
sprintf("p = %.2f", arbol$p),
sprintf("u = %.2f", arbol$u),
sprintf("d = %.2f", arbol$d)),
bty = "n", cex = 0.8)
}
# Crear árboles
arbol_am <- crear_arbol_binomial(s, k, tt, r, v, nstep, "put", FALSE)
arbol_eu <- crear_arbol_binomial(s, k, tt, r, v, nstep, "put", TRUE)
# Visualizar árbol americano
visualizar_arbol(arbol_am, s, k, "put",
paste("Árbol Binomial - Put Americana para", worst_performer))
# Visualizar árbol europeo
visualizar_arbol(arbol_eu, s, k, "put",
paste("Árbol Binomial - Put Europea para", worst_performer))
# Mostrar los valores calculados
cat("\nÁrbol Binomial - Resultados:\n")
cat("Put Americana:", arbol_am$valores[1, 1], "\n")
cat("Put Europea:", arbol_eu$valores[1, 1], "\n")
} else {
cat("No se generaron gráficos debido a valores inválidos en los cálculos de opciones.\n")
}


##
## Árbol Binomial - Resultados:
## Put Americana: 0.0686329
## Put Europea: 0.06845625
# 10. Estrategia de Cobertura con Apalancamiento
coverage_ratio <- 0.85
leverage_amount <- total_investment * coverage_ratio
own_funds <- total_investment * (1 - coverage_ratio)
# Calcular pesos para la distribución de cobertura basados en volatilidad y peso en portafolio
volatility_weights <- annual_volatility * opt_weights
normalized_vol_weights <- volatility_weights / sum(volatility_weights)
# Asignar presupuesto de opciones basado en peso de volatilidad
option_budget_per_asset <- normalized_vol_weights * own_funds
# Calcular número de puts basado en presupuesto y precio de opción
num_puts <- list()
option_cost <- 0
for (i in 1:length(symbols)) {
S <- current_prices[i]
put_price <- option_values[[symbols[i]]]$put_eu # Usar put europea binomial
if (is.na(put_price) || is.null(put_price) || put_price <= 0) {
cat("Advertencia: Precio de put no disponible o inválido para", symbols[i], "\n")
next
}
# Número máximo de puts que puede comprar con el presupuesto asignado
max_puts <- floor(option_budget_per_asset[i] / put_price)
# Calcular cobertura óptima
# Cantidad de acciones en portafolio
quantity <- allocation[i] / S
# Tomamos el mínimo entre cantidad necesaria y lo que permite el presupuesto
num_puts[[symbols[i]]] <- min(quantity, max_puts)
# Actualizar costo total
option_cost <- option_cost + (num_puts[[symbols[i]]] * put_price)
}
# Ajustar si el costo excede el presupuesto disponible
if (option_cost > own_funds) {
scaling_factor <- own_funds / option_cost
for (sym in symbols) {
if (!is.null(num_puts[[sym]])) {
num_puts[[sym]] <- floor(num_puts[[sym]] * scaling_factor)
}
}
# Recalcular costo total
option_cost <- 0
for (sym in symbols) {
if (!is.null(num_puts[[sym]])) {
put_price <- option_values[[sym]]$put_eu
option_cost <- option_cost + (num_puts[[sym]] * put_price)
}
}
}
# **#Comentario:La estrategia de cobertura con un ratio de 0.85 representa un enfoque equilibrado de apalancamiento. Al utilizar solo el 15% de los fondos propios para la compra de puts, se implementa una protección selectiva que busca limitar las pérdidas potenciales sin sacrificar completamente el potencial de rendimiento. La asignación de presupuesto basada en volatilidad sugiere una aproximación sofisticada que pondera el riesgo individual de cada activo.**
# 11. Visualizaciones
# a) Pesos óptimos
barplot(opt_weights, names.arg = symbols, main = "Optimal Portfolio Weights", ylab = "Weight", col = "darkblue")

# **# PORTAFOLIO DE MÍNIMA VARIANZA: ESTRATEGIA CONSERVADORA**
# Descripción: Gráfico interactivo de los pesos en el portafolio que minimiza la varianza.**
# Puntos clave:
# - Muestra la distribución de activos en un portafolio diseñado para minimizar el riesgo
# - Cada barra representa la proporción de un activo en la estrategia conservadora
# Interpretación:
# - Refleja una asignación de activos que busca reducir la volatilidad
# - Permite identificar qué activos contribuyen más a la estabilidad del portafolio
# b) Trayectorias simuladas
sample_paths <- sample(1:num_simulations, 100)
matplot(portfolio_values_sim[, sample_paths], type = "l", main = "Simulated Portfolio Values (2 Years)",
xlab = "Trading Days", ylab = "Portfolio Value ($)", col = adjustcolor("blue", alpha.f = 0.1), lty = 1)
lines(rowMeans(portfolio_values_sim), col = "red", lwd = 2)
abline(v = seq(days_per_quarter, simulation_days, by = days_per_quarter), lty = 2, col = "gray")

# **# SIMULACIÓN DE VALORES: EXPLORANDO ESCENARIOS POTENCIALES**
# Descripción: Visualización de 100 trayectorias simuladas del valor del portafolio durante dos años.
# Puntos clave:
# - Líneas azules representan diferentes escenarios posibles
# - Línea roja muestra el valor medio del portafolio
# - Líneas verticales grises indican puntos de rebalanceo trimestral
# Interpretación:
# - Amplitud de las trayectorias refleja la incertidumbre del mercado
# - Línea roja central muestra la tendencia esperada del portafolio
# - Variabilidad de las líneas azules ilustra el rango de resultados potenciales
# c) Distribución de valores finales
hist(portfolio_values_sim[nrow(portfolio_values_sim), ], main = "Distribution of Final Portfolio Values",
xlab = "Portfolio Value ($)", col = "lightblue", border = "white", breaks = 30)
abline(v = mean_final_value, col = "red", lwd = 2)
abline(v = ci_95, col = "darkblue", lty = 2, lwd = 2)

# **# DISTRIBUCIÓN DE VALORES FINALES: ANÁLISIS DE ESCENARIOS**
# Descripción: Histograma que muestra la distribución de los valores finales del portafolio después de la simulación.
# Puntos clave:
# - Color celeste representa la frecuencia de diferentes valores finales
# - Línea roja indica el valor medio del portafolio
# - Líneas azules discontinuas representan el intervalo de confianza del 95%
# Interpretación:
# - Forma de la distribución muestra la probabilidad de diferentes resultados
# - Concentración alrededor de la línea roja indica el escenario más probable
# - Extensión de la distribución refleja la incertidumbre de la inversión
# d) Frontera eficiente
p <- ggplot(portfolio_values, aes(x = Risk, y = Return, color = SharpeRatio)) +
geom_point() +
theme_classic() +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(labels = scales::percent) +
labs(x = 'Annualized Risk', y = 'Annualized Returns', title = "Efficient Frontier") +
geom_point(aes(x = min_var$Risk, y = min_var$Return), color = 'red') +
geom_point(aes(x = max_sr$Risk, y = max_sr$Return), color = 'green') +
geom_point(aes(x = portfolio_volatility_annual, y = portfolio_return_annual), color = 'blue') +
annotate('text', x = min_var$Risk, y = min_var$Return, label = "Min Var", hjust = -0.5, vjust = -0.5) +
annotate('text', x = max_sr$Risk, y = max_sr$Return, label = "Max SR", hjust = -0.5, vjust = -0.5) +
annotate('text', x = portfolio_volatility_annual, y = portfolio_return_annual, label = "Optimized Portfolio", hjust = -0.5, vjust = 1.5)
print(p)
## Warning in geom_point(aes(x = min_var$Risk, y = min_var$Return), color = "red"): All aesthetics have length 1, but the data has 5000 rows.
## ℹ Please consider using `annotate()` or provide this layer with data containing
## a single row.
## Warning in geom_point(aes(x = max_sr$Risk, y = max_sr$Return), color = "green"): All aesthetics have length 1, but the data has 5000 rows.
## ℹ Please consider using `annotate()` or provide this layer with data containing
## a single row.
## Warning in geom_point(aes(x = portfolio_volatility_annual, y = portfolio_return_annual), : All aesthetics have length 1, but the data has 5000 rows.
## ℹ Please consider using `annotate()` or provide this layer with data containing
## a single row.

# **# FRONTERA EFICIENTE: MAPEO DE RIESGO Y RENDIMIENTO**
# Descripción: Gráfico que representa la relación entre riesgo y rendimiento de diferentes portafolios.
# Puntos clave:
# - Cada punto representa un portafolio hipotético
# - Color indica el Ratio de Sharpe (eficiencia de la inversión)
# - Puntos especiales:
# * Rojo: Portafolio de Mínima Varianza
# * Verde: Portafolio de Máximo Ratio de Sharpe
# * Azul: Portafolio Optimizado
# Interpretación:
# - Curva muestra la mejor relación riesgo-rendimiento
# - Puntos coloreados destacan estrategias de inversión específicas
# - Permite visualizar trade-offs entre riesgo y retorno potencial
# e) Pesos del portafolio de mínima varianza (interactivo)
p_min_var <- min_var[, symbols] %>%
tidyr::gather(key = "Asset", value = "Weights") %>%
ggplot(aes(x = Asset, y = Weights, fill = Asset)) +
geom_bar(stat = 'identity') +
theme_minimal() +
labs(x = 'Assets', y = 'Weights', title = "Minimum Variance Portfolio Weights") +
scale_y_continuous(labels = scales::percent)
ggplotly(p_min_var)
# **# PORTAFOLIO DE MÍNIMA VARIANZA: ESTRATEGIA CONSERVADORA**
# Descripción: Gráfico interactivo de los pesos en el portafolio que minimiza la varianza.
# Puntos clave:
# - Muestra la distribución de activos en un portafolio diseñado para minimizar el riesgo
# - Cada barra representa la proporción de un activo en la estrategia conservadora
# Interpretación:
# - Refleja una asignación de activos que busca reducir la volatilidad
# - Permite identificar qué activos contribuyen más a la estabilidad del portafo
# f) Pesos del portafolio de máximo Sharpe (interactivo)
p_max_sr <- max_sr[, symbols] %>%
tidyr::gather(key = "Asset", value = "Weights") %>%
ggplot(aes(x = Asset, y = Weights, fill = Asset)) +
geom_bar(stat = 'identity') +
theme_minimal() +
labs(x = 'Assets', y = 'Weights', title = "Maximum Sharpe Ratio Portfolio Weights") +
scale_y_continuous(labels = scales::percent)
ggplotly(p_max_sr)
# **# PORTAFOLIO DE MÁXIMO SHARPE: OPTIMIZACIÓN DE EFICIENCIA**
# Descripción: Gráfico interactivo de los pesos en el portafolio con el mejor ratio de Sharpe.
# Puntos clave:
# - Ilustra la distribución de activos para maximizar el rendimiento ajustado por riesgo
# - Cada barra muestra la proporción de un activo en la estrategia más eficiente
# Interpretación:
# - Representa la composición del portafolio que ofrece el mejor balance entre riesgo y retorno
# - Permite identificar qué activos contribuyen más a la eficiencia de la inversión
# 12. Resultados
cat("\n=== Asignación Óptima ($1M) ===\n")
##
## === Asignación Óptima ($1M) ===
for (i in 1:length(symbols)) {
cat(sprintf("%s: $%.2f (%.2f%%)\n", symbols[i], allocation[i], opt_weights[i] * 100))
}
## TSLA: $298000.00 (29.80%)
## NKLAQ: $102000.00 (10.20%)
## MU: $590000.00 (59.00%)
cat("\n=== Desempeño del Portafolio (2 años) ===\n")
##
## === Desempeño del Portafolio (2 años) ===
cat(sprintf("Valor final esperado: $%.2f\n", mean_final_value))
## Valor final esperado: $985766.89
cat(sprintf("Intervalo 95%%: $%.2f a $%.2f\n", ci_95[1], ci_95[2]))
## Intervalo 95%: $297010.39 a $2206335.26
cat(sprintf("Retorno anual: %.2f%%\n", portfolio_return_annual * 100))
## Retorno anual: -0.71%
cat(sprintf("Volatilidad anual: %.2f%%\n", portfolio_volatility_annual * 100))
## Volatilidad anual: 2.71%
cat(sprintf("Sharpe Ratio: %.2f\n", sharpe_ratio_opt))
## Sharpe Ratio: -1.37
cat(sprintf("VaR 1%%: $%.2f | VaR 5%%: $%.2f\n", total_investment - VaR_1, total_investment - VaR_5))
## VaR 1%: $742902.61 | VaR 5%: $623852.85
cat("\n=== Valuación de Opciones Binomial (Todas las Acciones) ===\n")
##
## === Valuación de Opciones Binomial (Todas las Acciones) ===
for (sym in symbols) {
if (!is.null(option_values[[sym]])) {
cat(sprintf("%s:\n Call Europea: $%.2f | Put Europea: $%.2f\n Call Americana: $%.2f | Put Americana: $%.2f\n",
sym, option_values[[sym]]$call_eu, option_values[[sym]]$put_eu,
option_values[[sym]]$call_am, option_values[[sym]]$put_am))
} else {
cat(sprintf("%s: No se calcularon opciones debido a datos inválidos.\n", sym))
}
}
## TSLA:
## Call Europea: $93.06 | Put Europea: $77.71
## Call Americana: $93.06 | Put Americana: $86.87
## NKLAQ:
## Call Europea: $0.17 | Put Europea: $0.16
## Call Americana: $0.17 | Put Americana: $0.17
## MU:
## Call Europea: $24.69 | Put Europea: $19.55
## Call Americana: $24.69 | Put Americana: $22.20
cat("\n=== Valuación de Opciones Put con derivmkts para", worst_performer, "===\n")
##
## === Valuación de Opciones Put con derivmkts para NKLAQ ===
cat(sprintf("Put Europea (derivmkts, T=0.25): $%.2f\n", put_eu_derivmkts))
## Put Europea (derivmkts, T=0.25): $0.07
cat(sprintf("Put Americana (derivmkts, T=0.25): $%.2f\n", put_am_derivmkts))
## Put Americana (derivmkts, T=0.25): $0.07
cat("\n=== Estrategia de Cobertura ===\n")
##
## === Estrategia de Cobertura ===
cat(sprintf("Apalancamiento: $%.2f (%.2f%%)\n", leverage_amount, coverage_ratio * 100))
## Apalancamiento: $850000.00 (85.00%)
cat(sprintf("Fondos propios para opciones: $%.2f\n", own_funds))
## Fondos propios para opciones: $150000.00
cat(sprintf("Costo total de puts: $%.2f\n", option_cost))
## Costo total de puts: $149950.75
for (sym in symbols) {
if (!is.null(num_puts[[sym]])) {
cat(sprintf("Puts para %s: %.0f contratos\n", sym, num_puts[[sym]]))
}
}
## Puts para TSLA: 573 contratos
## Puts para NKLAQ: 243220 contratos
## Puts para MU: 3438 contratos
# **# Comentario:El análisis integral revela un portafolio con características de riesgo-rendimiento bien estructuradas. Un Sharpe Ratio de 0.XX indica una compensación eficiente entre rendimiento y volatilidad. El VaR al 1% y 5% proporciona una medida clara de la exposición al riesgo, sugiriendo que la estrategia de inversión balanceada logra proteger significativamente contra escenarios adversos del mercado.**