Con este proceso vamos a llevar a cabo un análisis de cobertura americana para las acciones NU, ONON y NVDA, utilizando datos recopilados desde el 01/06/2022 al 20/04/2025.
Al final, vamos a considerar una opción de estás 3 acciones, que liquide de forma trimestral hasta la fecha de inversión máxima de la opción (o máximo dos años en el futuro), donde el precio strike se determinará conforme los criterios de liquidez (bid-ask), volatilidad implícita y open interest. Además, la estrategia mencionada anteriormente se comparará con otra estrategia donde se tomaran opciones de cada activo para analizar los pro y contras de ambas. Se realizará un “rolling” al finalizar cada contrato, ajustando el precio de prima cada trimestre. La tasa libre de riesgo se establecerá a un trimestre, basada en los bonos del Tesoro (Tbond).
Es preciso mencionar que la selección de los activos NVDA (NVIDIA Corp.), NU (Nubank) y ONON (On Holding AG) responde a una combinación de criterios estratégicos, financieros y didácticos que permiten construir un portafolio diversificado y representativo para el análisis de derivados financieros, específicamente para el estudio de coberturas con opciones put. A continuación, se detallan los principales argumentos que respaldan su elección:
NVDA pertenece al sector tecnológico (semiconductores e inteligencia artificial), con alta capitalización bursátil y fuerte exposición al ciclo económico global.
NU, una fintech de origen latinoamericano, opera en el sector financiero y bancario digital, con alta penetración en Brasil y crecimiento en América Latina.
ONON, una marca de calzado y apparel deportivo suizo, representa el sector de consumo discrecional, especialmente en el segmento premium de estilo de vida y deporte.
Esta variedad sectorial e internacional permite estudiar un portafolio heterogéneo, sujeto a diferentes riesgos específicos y macroeconómicos, lo que es fundamental para evaluar la efectividad de coberturas diferenciadas por activo.
Cada uno de los activos presenta características diferenciadas en cuanto a:
Volatilidad histórica: NVDA es altamente volátil y muy sensible a noticias tecnológicas y cambios en tasas de interés; ONON muestra una volatilidad intermedia; NU tiene comportamiento más especulativo y con sensibilidad al contexto latinoamericano.
Tamaño y madurez de la empresa: NVDA es una empresa consolidada con alta liquidez en el mercado de opciones; NU y ONON son empresas más jóvenes (post-IPO recientes) que representan el segmento growth emergente.
Esto permite observar cómo se comporta la cobertura ante diferentes trayectorias de precios simuladas y niveles de riesgo, lo que enriquece el análisis de opciones y la construcción del portafolio de mínima varianza.
Los tres activos están listados en mercados estadounidenses (NASDAQ y NYSE), lo que garantiza:
Acceso a datos históricos confiables para simulación y análisis estadístico (a través de APIs como tidyquant, quantmod, FRED, etc.).
Disponibilidad de opciones listadas (PUTs y CALLs) en plataformas como Yahoo Finance, ideal para realizar ejercicios prácticos de valuación con árboles binomiales, estrategias de cobertura y análisis de sensibilidad.
La mezcla de activos permite integrar múltiples temas clave en finanzas cuantitativas y gestión de portafolios:
Modelado de precios mediante Movimiento Browniano Geométrico (MBG).
Construcción de portafolios eficientes (mínima varianza y tangencia).
Cálculo de métricas de desempeño como Sharpe ratio, VaR, y volatilidad.
Implementación de estrategias de cobertura con opciones ITM bajo diferentes enfoques (cobertura parcial vs. diversificada).
Esto lo convierte en un caso idóneo para aplicaciones didácticas, seminarios de derivados o pruebas de concepto en entornos profesionales.
En resumen, los activos NVDA, NU y ONON fueron seleccionados con el propósito de construir un portafolio realista, representativo y pedagógicamente útil. Su diversidad en cuanto a sector, perfil de riesgo, origen geográfico y liquidez en el mercado de opciones los convierte en excelentes candidatos para explorar estrategias avanzadas de cobertura financiera, análisis de riesgo y optimización de portafolios en contextos simulados y reales.
Antes de iniciar con el proceso de analisis, primero vamos a instalar y cargar los paquetes necesarios
install.packages(c(“tidyquant”,“plotly”,“timetk”,“tibble”, “tidyr”, “quantmod”, “rvest”, “dplyr”, “glue”))
library(tidyquant) # Para descargar los datos
library(plotly) # Para crear gr?ficos interactivos
library(timetk) # Para manipular las series de datos
library(tibble) #Para crear y manejar data frames modernos
library(tidyr) #Para organizar y transformar datos
library(forcats) #Para manipular variables categ?ricas (factores) de forma eficiente.
library(dplyr) #Para manipula datos: filtrar, resumir, transformar, ordenar, agrupar.
library(tidyverse) #Es un conjunto de paquetes integrados para an?lisis de datos limpio y estructurado.
library(quantmod) #Para descargar, analizar y visualizar datos financieros y series de tiempo del mercado.
library(rvest) #Sirve para hacer web scraping (extraer datos de páginas web HTML de forma estructurada)
library(glue) #Sirve para crear strings dinámicos fácilmente, insertando variables dentro de texto
library(stringr) #Sirve para manipular cadenas de texto (buscar, reemplazar, dividir, extraer patrones).
library(ggplot2) #Sirve para crear gráficos elegantes y personalizables con una sintaxis coherente basada en capas.
options(scipen=999)
Se procede a descargar los precios historicos de las acciones NU, ONON y NVDA, utilizando datos recopilados desde el 01/06/2022 a 04/04/2025
tick <- c('NU','ONON','NVDA')
price_data <- tq_get(tick,from='2022-06-01',to='2025-04-20',get='stock.prices')
head(price_data)
## # A tibble: 6 × 8
## symbol date open high low close volume adjusted
## <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 NU 2022-06-01 3.84 3.95 3.66 3.82 31535600 3.82
## 2 NU 2022-06-02 3.82 4.5 3.80 4.44 79498000 4.44
## 3 NU 2022-06-03 4.31 4.54 4.22 4.5 54708600 4.5
## 4 NU 2022-06-06 4.60 4.75 4.26 4.29 24604400 4.29
## 5 NU 2022-06-07 4.07 4.32 4 4.32 31965900 4.32
## 6 NU 2022-06-08 4.23 4.52 4.23 4.33 17304400 4.33
Asimismo se descarga la tasa libre de riesgo de FRED (3-Month Treasury Bill: ^IRX o DGS3MO)
getSymbols("DGS3MO", src = "FRED") # Tasa a 3 meses
## [1] "DGS3MO"
rf_data <- na.omit(DGS3MO)
rf_trimestral <- tail(as.numeric(rf_data), 1) / 100 # Ultimo valor y lo llevamos a decimal
print(rf_trimestral)
## [1] 0.0434
Posterior a ello se calcula el rendimiento diario de estas acciones
log_ret_tidy <- price_data%>%group_by(symbol)%>%tq_transmute(select=adjusted,mutate_fun=periodReturn,period='daily',col_rename='ret',type='log')
head(log_ret_tidy)
## # A tibble: 6 × 3
## # Groups: symbol [1]
## symbol date ret
## <chr> <date> <dbl>
## 1 NU 2022-06-01 0
## 2 NU 2022-06-02 0.150
## 3 NU 2022-06-03 0.0134
## 4 NU 2022-06-06 -0.0478
## 5 NU 2022-06-07 0.00697
## 6 NU 2022-06-08 0.00231
y se convierte a un formato ancho y en un objeto de serie temporal
log_ret_xts <- log_ret_tidy%>%tidyr::pivot_wider(names_from=symbol, values_from=ret)%>%tk_xts()
## Warning: Non-numeric columns being dropped: date
## Using column `date` for date_var.
head(log_ret_xts)
## NU ONON NVDA
## 2022-06-01 0.000000000 0.000000000 0.000000000
## 2022-06-02 0.150403984 0.071262994 0.067127931
## 2022-06-03 0.013423007 -0.034540346 -0.045529067
## 2022-06-06 -0.047790673 -0.045281916 0.003519414
## 2022-06-07 0.006968718 0.010451369 0.007424809
## 2022-06-08 0.002312082 0.002831501 -0.014586063
A continuacion, calculamos los rendimientos diarios medios de cada activo.
mean_ret <- colMeans(log_ret_xts)
print(round(mean_ret, 5))
## NU ONON NVDA
## 0.00146 0.00097 0.00237
Para calcular la matriz de covarianza para las 3 acciones; La anualizaremos multiplicando por 252.
cov_mat <- cov(log_ret_xts) * 252
print(round(cov_mat,4))
## NU ONON NVDA
## NU 0.2828 0.0951 0.1145
## ONON 0.0951 0.2791 0.1165
## NVDA 0.1145 0.1165 0.3014
ANÁLISIS TÉCNICO: La matriz de retornos esperados y la matriz de covarianza anualizada permiten evaluar el rendimiento sistemático y la interacción entre activos. Dado que NVDA presenta mayor volatilidad y retornos esperados elevados, cumple un rol especulativo dentro del portafolio. En contraste, NU y ONON, con menor dispersión de retornos, aportan diversificación. Esta matriz es la base para construir fronteras eficientes, estimar betas implícitas y definir asignaciones óptimas bajo media-varianza.
La elección del portafolio óptimo mediante el criterio de mínima varianza se fundamenta en principios clave de la teoría moderna de portafolios (Markowitz, 1952) y responde a un enfoque conservador de gestión del riesgo, especialmente relevante en contextos de análisis con derivados financieros.
El objetivo central de este método es reducir la varianza de los retornos esperados del portafolio, construyendo una combinación de activos que exhiba la menor volatilidad posible para cualquier nivel de retorno esperado. Esto resulta crucial cuando se busca diseñar estrategias de cobertura y análisis de escenarios de pérdidas extremas.
A diferencia del portafolio de máxima Sharpe o de tangencia, el portafolio de mínima varianza no requiere suposiciones fuertes sobre los retornos esperados de cada activo, los cuales pueden estar sujetos a sesgo o incertidumbre. En su lugar, se apoya únicamente en la estructura de varianza-covarianza de los activos.
Al utilizar opciones PUT para proteger el portafolio, es deseable que la base del portafolio subyacente esté configurada para tener una volatilidad controlada, facilitando el diseño de estrategias de cobertura más estables y eficientes en términos de primas pagadas.
Este método tiende a asignar pesos que evitan la concentración excesiva en activos con alta varianza individual, lo cual es coherente con el principio de diversificación. En este caso, la inclusión de NU, ONON y NVDA, activos con distintos perfiles de volatilidad, resulta en una combinación más balanceada desde el punto de vista del riesgo.
Desde una perspectiva académica, el portafolio de mínima varianza permite mostrar de forma clara los beneficios de la diversificación y del análisis cuantitativo del riesgo. Además, es una metodología replicable y estandarizada, ampliamente usada en literatura financiera y herramientas de gestión de inversiones.
En resumen, Se ruliza este medtodo ya que la selección del portafolio de mínima varianza como base del análisis responde a una estrategia prudente, metodológicamente robusta y orientada a la reducción del riesgo total, lo cual es especialmente relevante al evaluar mecanismos de protección mediante instrumentos derivados como las opciones put. Esta elección permite centrar el estudio en la efectividad de las coberturas bajo un perfil conservador, garantizando al mismo tiempo una base sólida para interpretar el impacto de las variaciones de mercado simuladas.
Primero, vamos a crear ponderaciones aleatorias
wts <- runif(n=length(tick))
wts <- wts/sum(wts)
A continuaci?n calculamos la rentabilidad anualizada de la cartera.
port_returns <- (sum(wts*mean_ret)+1)^252-1
adem?s, calculamos el riesgo de la cartera (desviaci?n est?ndar). Esta ser? la desviaci?n est?ndar anualizada de la cartera.
port_risk <- sqrt(t(wts)%*%(cov_mat%*%wts))
Finalemente calculamos el ratio de Sharpe
sharpe_ratio <- port_returns/port_risk
print(wts)
## [1] 0.09890081 0.56574449 0.33535470
print(port_returns)
## [1] 0.4540656
print(port_risk)
## [,1]
## [1,] 0.4341113
print(sharpe_ratio)
## [,1]
## [1,] 1.045966
Tenemos todo lo necesario para realizar la optimizaci?n. Ahora solo necesitamos ejecutar este c?digo en 5000 carteras aleatorias. Para ello, usaremos un bucle llamado for. Antes de hacerlo, necesitamos crear vectores y una matriz de vac?os para almacenar nuestros valores.
num_port <- 5000
Creamos una matriz para almacenar las ponderaciones
all_wts <- matrix(nrow =num_port,ncol=length(tick))
Creamos un vector vac?o para almacenar Rendimientos de la cartera
port_returns <- vector('numeric',length=num_port)
Creamos un vector vac?o para almacenar Desviaci?n est?ndar de la cartera
port_risk <- vector('numeric',length=num_port)
Creamos un vector vac?o para almacenar ?ndice de Sharpe de portafolio
sharpe_ratio <- vector('numeric',length=num_port)
Procedemos a ejecutar el bucle [for] 5000 veces.
for (i in seq_along(port_returns)) {
wts <- runif(length(tick))
wts <- wts/sum(wts)
# Almacenar ponderaci?n en la matriz
all_wts[i,] <- wts
# Rendimientos de la cartera
port_ret <- sum(wts*mean_ret)
port_ret <- ((port_ret+1)^252)-1
# Almacenamiento de valores de retorno de cartera
port_returns[i] <- port_ret
# Creaci?n y almacenamiento de riesgos de cartera
port_sd <- sqrt(t(wts)%*%(cov_mat%*%wts))
port_risk[i] <- port_sd
# Creaci?n y almacenamiento de ratios de Sharpe de cartera
# Suponiendo una tasa libre de riesgo del 0 %
sr <- port_ret/port_sd
sharpe_ratio[i] <- sr
}
Ahora creamos una tabla de datos para almacenar todos los valores juntos.
portfolio_values <- tibble(Return=port_returns,Risk=port_risk,SharpeRatio=sharpe_ratio)
Convertimos una matriz en un tibble y cambiamos los nombres de las columnas
all_wts <- tk_tbl(all_wts)
## Warning in tk_tbl.data.frame(as.data.frame(data), preserve_index, rename_index,
## : Warning: No index to preserve. Object otherwise converted to tibble
## successfully.
colnames(all_wts) <- colnames(log_ret_xts)
Combinamos todos los valores juntos
portfolio_values <- tk_tbl(cbind(all_wts, portfolio_values))
## Warning in tk_tbl.data.frame(cbind(all_wts, portfolio_values)): Warning: No
## index to preserve. Object otherwise converted to tibble successfully.
Tenemos las ponderaciones de cada activo, el riesgo y la rentabilidad, junto con el ratio de Sharpe de cada cartera. A continuaci?n, veamos las carteras m?s importantes.
*La cartera de m?nima varianza
min_var <- portfolio_values[which.min(portfolio_values$Risk),]
Graficamos las ponderaciones de la cartera de m?nima varianza
p <- min_var%>%
pivot_longer(cols=NU:NVDA, names_to="Asset", values_to="Weights")%>%
mutate(Asset=as.factor(Asset))%>%
ggplot(aes(x=fct_reorder(Asset, Weights), 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)
Asignamos USD 1.000.000 seg?n las ponderaciones Extraemos las ponderaciones de min_var
weights_row <- min_var %>% select(NU, ONON, NVDA)
Calculamos la asignaci?n en d?lares (1 mill?n)
allocation_row <- weights_row * 1e6
Creamos y mostramos la tabla usando [rbind]
min_var_allocation <- rbind(Weights = weights_row,AllocationUSD = allocation_row)
print(min_var_allocation)
## # A tibble: 2 × 3
## NU ONON NVDA
## * <dbl> <dbl> <dbl>
## 1 0.358 0.362 0.280
## 2 357630. 362237. 280133.
?ltimos precios historicos
ultimos_precios <- price_data %>%
group_by(symbol) %>%
filter(date == max(date)) %>%
select(symbol, adjusted) %>%
rename(Precio_Final = adjusted)
Tabla resumen con cantidades iniciales
resumen_inicial <- tibble(
Activo = c("NU", "ONON", "NVDA"),USD_Asignado = as.numeric(allocation_row))%>%
left_join(ultimos_precios, by = c("Activo" = "symbol")) %>%
mutate(Cantidad_Acciones = floor(USD_Asignado / Precio_Final),Valor_Real = Cantidad_Acciones * Precio_Final)
Valor inicial del portafolio
valor_inicial <- sum(resumen_inicial$Valor_Real)
ANÁLISIS TÉCNICO: El valor de portafolio inicial se utiliza como referencia base para estimar desviaciones futuras y cuantificar métricas de riesgo como el drawdown o el Delta P&L acumulado. Además, este valor permite valorar la efectividad de las coberturas con opciones PUT en términos absolutos (USD) y relativos (% cubierto).
La simulación de trayectorias de precios se realizó mediante Movimiento Browniano Geométrico (MBG), un modelo estocástico ampliamente utilizado en finanzas para activos que siguen una evolución log-normal.
Cada activo mantuvo su propia volatilidad y tasa de retorno estimada a partir de datos históricos, conservando su dinámica individual para no perder el carácter heterogéneo del portafolio.
nu_data <- price_data %>%
filter(symbol == "NU") %>%
select(date, adjusted)
price_nu=nu_data[,2]
log_ret_nu <- coredata(log_ret_xts[, "NU"])[-1]
Histórico precios/rendimientos NU
plot.ts(price_nu)
plot.ts(log_ret_nu)
factores iniciales
year1=252
s01=as.numeric(nu_data[NROW(nu_data),2])
T1=504
r1=exp(mean(log_ret_nu)*year1)-1
sigma1= as.numeric(sqrt(var(log_ret_nu)*year1))
N1=5000 #N?mero de simulaciones
dt1=1/year1
stx1=as.numeric(s01*exp(r1*(T1/year1)))
vec1=rep(s01,N1)
mb1=matrix(ncol=N1, nrow=T1)
mb1[1,]=vec1
for (i in 1:N1) {
for (t in 2:T1) {
mb1[t,i]=mb1[(t-1),i]*exp((r1-(0.5*(sigma1^2)))*(dt1)+sigma1*(dt1^(1/2))*qnorm(runif(1, min=0, max=1)))
}
}
validacion antes de grafico
print(paste("Último precio de NU:", s01))
## [1] "Último precio de NU: 11"
print(paste("Volatilidad anualizada (sigma1):", sigma1))
## [1] "Volatilidad anualizada (sigma1): 0.532190192772301"
print(paste("Rendimiento anual esperado (r1):", r1))
## [1] "Rendimiento anual esperado (r1): 0.44650497710217"
final_prices <- mb1[nrow(mb1), ]
quantile(final_prices, probs = c(0.01, 0.05, 0.5, 0.95, 0.99))
## 1% 5% 50% 95% 99%
## 3.532652 5.978220 20.126393 69.777640 118.618712
max_value <- max(mb1)
print(max_value)
## [1] 306.223
which_max <- which(mb1 == max_value, arr.ind = TRUE)
print(which_max)
## row col
## [1,] 493 2264
ANALISIS: Dado que bajo el Movimiento Browniano Geométrico (MBG) los precios se simulan de forma exponencial, se observa que aproximadamente el 1% de las trayectorias presentan comportamientos explosivos. Esto se debe a la combinacion de un retorno anual esperado (drift) y una volatilidad anualizada relativamente altos, lo cual amplifica el crecimiento acumulado en el tiempo.
Estos valores extremos distorsionan el analisis general de la distribucion de precios simulados. Por esta razon, se decide excluir las trayectorias por encima del percentil 99% para representar de forma mas realista el comportamiento esperado del precio, tal cual se menciona en la introducción de simulación de precios
grafico Extrae los precios finales
final_prices <- mb1[nrow(mb1), ]
Umbral del percentil 99
p99 <- quantile(final_prices, 0.99)
Filtrar trayectorias que terminan debajo del p99
filtered_mb1 <- mb1[, final_prices <= p99]
Calcular percentiles a lo largo del tiempo
p_mat_nu <- apply(filtered_mb1, 1, function(x) quantile(x, probs = c(0.05, 0.5, 0.95)))
Transponer para graficar
p_mat_nu <- t(p_mat_nu)
Grafica de trayectorias filtradas
matplot(filtered_mb1[, 1:100], type = "l", col = "gray", lty = 1,
main = "Simulaciones NU (trayectorias filtradas)", ylab = "Precio", xlab = "Días")
# Agrega lineas de percentiles
lines(p_mat_nu[, 1], col = "blue", lwd = 2, lty = 2) # P5
lines(p_mat_nu[, 2], col = "black", lwd = 2) # P50 (mediana)
lines(p_mat_nu[, 3], col = "red", lwd = 2, lty = 2) # P95
legend("topleft", legend = c("P5", "P50", "P95"),
col = c("blue", "black", "red"), lty = c(2,1,2), lwd = 2, bty = "n")
onon_data <- price_data %>%
filter(symbol == "ONON") %>%
select(date, adjusted)
price_onon=onon_data[,2]
log_ret_onon <- coredata(log_ret_xts[, "ONON"])[-1]
Histórico precios/rendimientos ONON
plot.ts(price_onon)
plot.ts(log_ret_onon)
factores iniciales
year2=252
s02=as.numeric(onon_data[NROW(onon_data),2])
T2=504
r2=exp(mean(log_ret_onon)*year2)-1
sigma2= as.numeric(sqrt(var(log_ret_onon)*year2))
N2=5000 #numero de simulaciones
dt2=1/year2
stx2=as.numeric(s02*exp(r2*(T2/year2)))
vec2=rep(s02,N2)
mb2=matrix(ncol=N2, nrow=T2)
mb2[1,]=vec2
for (j in 1:N2) {
for (u in 2:T2) {
mb2[u,j]=mb2[(u-1),j]*exp((r2-(0.5*(sigma2^2)))*(dt2)+sigma2*(dt2^(1/2))*qnorm(runif(1, min=0, max=1)))
}
}
validacion antes de grafico
print(paste("ultimo precio de ONON:", s02))
## [1] "ultimo precio de ONON: 42.5"
print(paste("Volatilidad anualizada (sigma2):", sigma2))
## [1] "Volatilidad anualizada (sigma2): 0.528664948393383"
print(paste("Rendimiento anual esperado (r2):", r2))
## [1] "Rendimiento anual esperado (r2): 0.276435306842032"
final_prices2 <- mb2[nrow(mb2), ]
quantile(final_prices2, probs = c(0.01, 0.05, 0.5, 0.95, 0.99))
## 1% 5% 50% 95% 99%
## 9.541667 16.094269 55.460902 187.513050 305.445486
max_value2 <- max(mb2)
print(max_value2)
## [1] 961.3159
which_max2 <- which(mb2 == max_value2, arr.ind = TRUE)
print(which_max2)
## row col
## [1,] 504 4974
ANALISIS: Dado que bajo el Movimiento Browniano Geométrico (MBG) los precios se simulan de forma exponencial, se observa que aproximadamente el 5% de las trayectorias presentan comportamientos explosivos. Esto se debe a la combinacion de un retorno anual esperado (drift) y una volatilidad anualizada relativamente altos, lo cual amplifica el crecimiento acumulado en el tiempo.
Estos valores extremos distorsionan el analisis general de la distribucion de precios simulados. Por esta razon, se decide excluir las trayectorias por encima del percentil 95% para representar de forma mas realista el comportamiento esperado del precio.
grafico Extrae los precios finales}
final_prices2 <- mb2[nrow(mb2), ]
Umbral del percentil 95
p95 <- quantile(final_prices2, 0.95)
Filtrar trayectorias que terminan debajo del p95
filtered_mb2 <- mb2[, final_prices2 <= p95]
Calcular percentiles a lo largo del tiempo
p_mat_onon <- apply(filtered_mb2, 1, function(x) quantile(x, probs = c(0.05, 0.5, 0.95)))
Transponer para graficar
p_mat_onon <- t(p_mat_onon)
Grafica trayectorias filtradas
matplot(filtered_mb2[, 1:100], type = "l", col = "gray", lty = 1,
main = "Simulaciones ONON (trayectorias filtradas)", ylab = "Precio", xlab = "Dias")
# Agrega lineas de percentiles
lines(p_mat_onon[, 1], col = "blue", lwd = 2, lty = 2) # P5
lines(p_mat_onon[, 2], col = "black", lwd = 2) # P50 (mediana)
lines(p_mat_onon[, 3], col = "red", lwd = 2, lty = 2) # P95
legend("topleft", legend = c("P5", "P50", "P95"),
col = c("blue", "black", "red"), lty = c(2,1,2), lwd = 2, bty = "n")
nvda_data <- price_data %>%
filter(symbol == "NVDA") %>%
select(date, adjusted)
price_nvda=nvda_data[,2]
log_ret_nvda <- coredata(log_ret_xts[, "NVDA"])[-1]
Histórico precios/rendimientos NVDA
plot.ts(price_nvda)
plot.ts(log_ret_nvda)
factores iniciales
year3=252
s03=as.numeric(nvda_data[NROW(nvda_data),2])
T3=504
r3=exp(mean(log_ret_nvda)*year3)-1
sigma3=as.numeric(sqrt(var(log_ret_nvda)*year3))
r3 <- 0.4
N3=5000 #numero de simulaciones
dt3=1/year3
stx3=as.numeric(s03*exp(r3*(T3/year3)))
vec3=rep(s03,N3)
mb3=matrix(ncol=N3, nrow=T3)
mb3[1,]=vec3
for (K in 1:N3) {
for (V in 2:T3) {
mb3[V,K]=mb3[(V-1),K]*exp((r3-(0.5*(sigma3^2)))*(dt3)+sigma3*(dt3^(1/2))*qnorm(runif(1, min=0, max=1)))
}
}
validacion antes de grafico
print(paste("Ultimo precio de NVDA:", s03))
## [1] "Ultimo precio de NVDA: 101.48999786377"
print(paste("Volatilidad anualizada (sigma3):", sigma3))
## [1] "Volatilidad anualizada (sigma3): 0.549375773869684"
print(paste("Rendimiento anual esperado (r3):", r3))
## [1] "Rendimiento anual esperado (r3): 0.4"
final_prices3 <- mb3[nrow(mb3), ]
quantile(final_prices3, probs = c(0.01, 0.05, 0.5, 0.95, 0.99))
## 1% 5% 50% 95% 99%
## 26.91712 46.40234 166.78051 601.78029 1047.35672
max_value3 <- max(mb3)
print(max_value3)
## [1] 3206.865
which_max3 <- which(mb3 == max_value3, arr.ind = TRUE)
print(which_max3)
## row col
## [1,] 503 3168
ANALISIS: Dado que bajo el Movimiento Browniano Geométrico (MBG) los precios se simulan de forma exponencial, se observa que aproximadamente el 5% de las trayectorias presentan comportamientos explosivos. Esto se debe a la combinacion de un retorno anual esperado (drift) y una volatilidad anualizada relativamente altos, lo cual amplifica el crecimiento acumulado en el tiempo.
Estos valores extremos distorsionan el analisis general de la distribucion de precios simulados. Por esta razon, se decide excluir las trayectorias por encima del percentil 95% para representar de forma mas realista el comportamiento esperado del precio, adicional para fines academicos se reducira volatilidad anualizada a la mitad.
grafico Extrae los precios finales
final_prices3 <- mb3[nrow(mb3), ]
Umbral del percentil 95
p95_2 <- quantile(final_prices3, 0.95)
Filtrar trayectorias que terminan debajo del p95
filtered_mb3 <- mb3[, final_prices3 <= p95_2]
Calcular percentiles a lo largo del tiempo
p_mat_nvda <- apply(filtered_mb3, 1, function(x) quantile(x, probs = c(0.05, 0.5, 0.95)))
Transponer para graficar
p_mat_nvda <- t(p_mat_nvda)
Plot trayectorias filtradas
matplot(filtered_mb3[, 1:100], type = "l", col = "gray", lty = 1,
main = "Simulaciones NVDA (trayectorias filtradas)", ylab = "Precio", xlab = "Dias")
# Agrega lineas de percentiles
lines(p_mat_nvda[, 1], col = "blue", lwd = 2, lty = 2) # P5
lines(p_mat_nvda[, 2], col = "black", lwd = 2) # P50 (mediana)
lines(p_mat_nvda[, 3], col = "red", lwd = 2, lty = 2) # P95
legend("topleft", legend = c("P5", "P50", "P95"),
col = c("blue", "black", "red"), lty = c(2,1,2), lwd = 2, bty = "n")
Funcion para seleccionar una trayectoria decreciente
elegir_trayectoria_decreciente <- function(sim_matrix) {
indices <- which(sim_matrix[nrow(sim_matrix), ] < sim_matrix[1, ])
if (length(indices) == 0) stop("No hay trayectorias en caida")
sim_matrix[, sample(indices, 1)]
}
Elegimos trayectorias en caida
trayectoria_NU <- elegir_trayectoria_decreciente(mb1)
trayectoria_ONON <- elegir_trayectoria_decreciente(mb2)
trayectoria_NVDA <- elegir_trayectoria_decreciente(mb3)
Seguimiento trismestral del portafolio Dias a observar: cada 63 dias desde el inicio hasta 504 (8 trimestres + dia inicial)
dias_seguimiento <- c(1, seq(63, 504, by = 63))
Extraer cantidades
cantidad_NU <- resumen_inicial %>% filter(Activo == "NU") %>% pull(Cantidad_Acciones)
cantidad_ONON <- resumen_inicial %>% filter(Activo == "ONON") %>% pull(Cantidad_Acciones)
cantidad_NVDA <- resumen_inicial %>% filter(Activo == "NVDA") %>% pull(Cantidad_Acciones)
Crear tabla para registrar evolucion del portafolio
seguimiento_portafolio <- tibble(
Trimestre = 0:(length(dias_seguimiento) - 1),
Dia_Simulacion = dias_seguimiento,
Cantidad_NU = cantidad_NU,
Cantidad_ONON = cantidad_ONON,
Cantidad_NVDA = cantidad_NVDA,
Precio_NU = trayectoria_NU[dias_seguimiento],
Precio_ONON = trayectoria_ONON[dias_seguimiento],
Precio_NVDA = trayectoria_NVDA[dias_seguimiento],
Valor_NU = Precio_NU * cantidad_NU,
Valor_ONON = Precio_ONON * cantidad_ONON,
Valor_NVDA = Precio_NVDA * cantidad_NVDA,
Valor_Portafolio = Valor_NU + Valor_ONON + Valor_NVDA,
Diferencia_Sin_Cobertura = Valor_Portafolio - valor_inicial,
)
Para efectos academicos usualemnte se visualizaran perdidas entre la comparacion del portafolio simulado versus el portafolio real, debido a que el codigo se estructuro para tomar trayectorias en caida de los precios simulados de los activos con el fin de analizar a detalle el impacto de las estrategias de cobertura de opciones.
Cálculo de retornos
retornos <- c(NA, diff(log(seguimiento_portafolio$Valor_Portafolio)))
Volatilidades (rolling de 2 períodos)
volatilidades <- numeric(length(retornos))
for (i in 2:length(retornos)) {
if (i > 2) {
volatilidades[i] <- sd(retornos[(i-1):i], na.rm = TRUE)
} else {
volatilidades[i] <- NA
}
}
Sharpe Ratios
sharpe_ratios <- numeric(length(retornos))
for (i in 1:length(retornos)) {
if (!is.na(retornos[i]) && !is.na(volatilidades[i]) && volatilidades[i] > 0) {
sharpe_ratios[i] <- (retornos[i] - (rf_trimestral / 4)) / volatilidades[i]
} else {
sharpe_ratios[i] <- NA
}
}
Calculamos VaR histórico (1% y 5%)
var_1 <- numeric(length(retornos))
var_5 <- numeric(length(retornos))
var_1_usd <- numeric(length(retornos))
var_5_usd <- numeric(length(retornos))
for (i in 3:length(retornos)) {
ret_window <- retornos[(i-1):i]
var_1[i] <- min(quantile(ret_window, 0.01, na.rm = TRUE), 0)
var_5[i] <- min(quantile(ret_window, 0.05, na.rm = TRUE), 0)
var_1_usd[i] <- var_1[i] * seguimiento_portafolio$Valor_Portafolio[i-1]
var_5_usd[i] <- var_5[i] * seguimiento_portafolio$Valor_Portafolio[i-1]
}
Mostramos los resultados en la consola
cat("===== MÉTRICAS DE DESEMPEÑO DEL PORTAFOLIO =====\n\n")
## ===== MÉTRICAS DE DESEMPEÑO DEL PORTAFOLIO =====
cat("--- Retornos (log-return trimestral) ---\n")
## --- Retornos (log-return trimestral) ---
for (i in 1:length(retornos)) {
trimestre_label <- i - 1 # Ajuste para que empiece desde 0
if (!is.na(retornos[i])) {
cat(sprintf("Trimestre %d: %.4f (%.2f%%)\n", trimestre_label, retornos[i], retornos[i]*100))
}
}
## Trimestre 1: 0.0243 (2.43%)
## Trimestre 2: -0.0088 (-0.88%)
## Trimestre 3: 0.2582 (25.82%)
## Trimestre 4: -0.2623 (-26.23%)
## Trimestre 5: -0.0813 (-8.13%)
## Trimestre 6: -0.0927 (-9.27%)
## Trimestre 7: 0.1475 (14.75%)
## Trimestre 8: -0.4240 (-42.40%)
cat("\n--- Volatilidad (rolling 2 trimestres) ---\n")
##
## --- Volatilidad (rolling 2 trimestres) ---
for (i in 1:length(volatilidades)) {
trimestre_label <- i - 1 # Ajuste para que empiece desde 0
if (!is.na(volatilidades[i])) {
cat(sprintf("Trimestre %d: %.4f (%.2f%%)\n", trimestre_label, volatilidades[i], volatilidades[i]*100))
}
}
## Trimestre 0: 0.0000 (0.00%)
## Trimestre 2: 0.0234 (2.34%)
## Trimestre 3: 0.1888 (18.88%)
## Trimestre 4: 0.3680 (36.80%)
## Trimestre 5: 0.1280 (12.80%)
## Trimestre 6: 0.0081 (0.81%)
## Trimestre 7: 0.1699 (16.99%)
## Trimestre 8: 0.4041 (40.41%)
cat("\n--- Sharpe Ratio ---\n")
##
## --- Sharpe Ratio ---
for (i in 1:length(sharpe_ratios)) {
trimestre_label <- i - 1 # Ajuste para que empiece desde 0
if (!is.na(sharpe_ratios[i])) {
cat(sprintf("Trimestre %d: %.4f\n", trimestre_label, sharpe_ratios[i]))
}
}
## Trimestre 2: -0.8411
## Trimestre 3: 1.3100
## Trimestre 4: -0.7422
## Trimestre 5: -0.7202
## Trimestre 6: -12.8461
## Trimestre 7: 0.8045
## Trimestre 8: -1.0760
cat("\n--- Value at Risk (VaR) ---\n")
##
## --- Value at Risk (VaR) ---
cat("VaR al 1%:\n")
## VaR al 1%:
for (i in 1:length(var_1)) {
trimestre_label <- i - 1 # Ajuste para que empiece desde 0
if (!is.na(var_1[i])) {
cat(sprintf("Trimestre %d: %.4f (%.2f%%) | USD: %.2f\n",
trimestre_label, var_1[i], var_1[i]*100, var_1_usd[i]))
}
}
## Trimestre 0: 0.0000 (0.00%) | USD: 0.00
## Trimestre 1: 0.0000 (0.00%) | USD: 0.00
## Trimestre 2: -0.0085 (-0.85%) | USD: -8699.41
## Trimestre 3: -0.0062 (-0.62%) | USD: -6247.53
## Trimestre 4: -0.2571 (-25.71%) | USD: -337997.61
## Trimestre 5: -0.2605 (-26.05%) | USD: -263446.49
## Trimestre 6: -0.0926 (-9.26%) | USD: -86341.21
## Trimestre 7: -0.0903 (-9.03%) | USD: -76750.95
## Trimestre 8: -0.4183 (-41.83%) | USD: -411912.90
cat("\nVaR al 5%:\n")
##
## VaR al 5%:
for (i in 1:length(var_5)) {
trimestre_label <- i - 1 # Ajuste para que empiece desde 0
if (!is.na(var_5[i])) {
cat(sprintf("Trimestre %d: %.4f (%.2f%%) | USD: %.2f\n",
trimestre_label, var_5[i], var_5[i]*100, var_5_usd[i]))
}
}
## Trimestre 0: 0.0000 (0.00%) | USD: 0.00
## Trimestre 1: 0.0000 (0.00%) | USD: 0.00
## Trimestre 2: -0.0072 (-0.72%) | USD: -7343.90
## Trimestre 3: 0.0000 (0.00%) | USD: 0.00
## Trimestre 4: -0.2363 (-23.63%) | USD: -310627.79
## Trimestre 5: -0.2533 (-25.33%) | USD: -256125.01
## Trimestre 6: -0.0922 (-9.22%) | USD: -85915.99
## Trimestre 7: -0.0807 (-8.07%) | USD: -68585.24
## Trimestre 8: -0.3954 (-39.54%) | USD: -389400.29
Resumen de última observación
ultimo_idx <- length(retornos)
ultimo_trimestre_label <- ultimo_idx - 1 # Ajuste para que empiece desde 0
cat("\n===== RESUMEN ÚLTIMA OBSERVACIÓN (Trimestre ", ultimo_trimestre_label, ") =====\n", sep="")
##
## ===== RESUMEN ÚLTIMA OBSERVACIÓN (Trimestre 8) =====
cat(sprintf("Retorno: %.4f (%.2f%%)\n", retornos[ultimo_idx], retornos[ultimo_idx]*100))
## Retorno: -0.4240 (-42.40%)
cat(sprintf("Volatilidad: %.4f (%.2f%%)\n", volatilidades[ultimo_idx], volatilidades[ultimo_idx]*100))
## Volatilidad: 0.4041 (40.41%)
cat(sprintf("Sharpe Ratio: %.4f\n", sharpe_ratios[ultimo_idx]))
## Sharpe Ratio: -1.0760
cat(sprintf("VaR 1%%: %.4f (USD: %.2f)\n", var_1[ultimo_idx], var_1_usd[ultimo_idx]))
## VaR 1%: -0.4183 (USD: -411912.90)
cat(sprintf("VaR 5%%: %.4f (USD: %.2f)\n", var_5[ultimo_idx], var_5_usd[ultimo_idx]))
## VaR 5%: -0.3954 (USD: -389400.29)
ANÁLISIS TÉCNICO: El análisis de desempeño trimestral permite evaluar el comportamiento dinámico del portafolio, incorporando la variación temporal de retorno y riesgo. El ratio de Sharpe ajustado con tasa libre de riesgo trimestral permite identificar si el portafolio está generando alfa consistente, o si está expuesto a pérdidas sistemáticas. Comportamientos negativos sostenidos del Sharpe podrían activar estrategias defensivas como puts o spreads.
El Value at Risk (VaR) histórico estimado por ventanas móviles ofrece una medida cuantitativa del peor escenario esperado bajo condiciones normales de mercado. El VaR al 1% representa un umbral extremo para gestión de capital, mientras que el VaR al 5% se usa comúnmente en cumplimiento regulatorio (ej. Basilea III). Es crucial contrastar estos niveles con el valor de las opciones compradas, para validar si la cobertura propuesta es efectiva en los escenarios de mayor pérdida.
La selección de contratos PUT se basó en criterios de eficiencia operativa y cobertura efectiva:
Open Interest / Spread, que penaliza spreads amplios y
favorece contratos con menor costo de entrada y salida.La estructura de primas resultante permite realizar un análisis posterior de coberturas con apalancamiento controlado, alineado al 85% del valor invertido en cada activo.
Precio actual de NVDA (ya calculado previamente)
nvda_price <- s03
URL de opciones para NVDA
url <- "https://finance.yahoo.com/quote/NVDA/options/?date=1829001600"
Leer la página con cabecera de navegador (para evitar bloqueos)
page <- read_html(httr::GET(url, httr::add_headers(`User-Agent` = "Mozilla/5.0")))
Extraer la tabla de PUTs (segunda tabla)
puts_table <- page %>%
html_elements("table") %>%
.[[2]] %>%
html_table(fill = TRUE)
Limpiar nombres de columnas
names(puts_table) <- gsub("\\s+", "_", names(puts_table))
Convertir columnas clave y calcular el spread
puts_clean <- puts_table %>%
mutate(
Strike = as.numeric(Strike),
Bid = as.numeric(Bid),
Ask = as.numeric(Ask),
Last_Price = as.numeric(Last_Price),
Implied_Volatility = as.numeric(gsub("%", "", Implied_Volatility)) / 100,
spread = Ask - Bid
) %>%
filter(
!is.na(Strike), !is.na(Bid), !is.na(Ask),
Strike > nvda_price # <-- solo ITM
) %>%
arrange(spread)
Relación open inteterest y spread
puts_clean <- puts_clean %>%
mutate(
Open_Interest = as.numeric(gsub(",", "", Open_Interest)),
# Score que premia alto interés abierto y bajo spread
score = Open_Interest / (spread + 1e-6) # evitamos división por 0
) %>%
arrange(desc(score)) # Mejor score = mayor liquidez y menor spread
Seleccionar la mejor opción
best_put <- puts_clean %>% slice(1)
selected_strike <- best_put$Strike
selected_prima <- best_put$Last_Price
cantidad_acciones_por_contrato <- 1000
Mostrar resultados
glue::glue("Strike seleccionado (PUT ITM con alto interés y bajo spread): {selected_strike}")
## Strike seleccionado (PUT ITM con alto interés y bajo spread): 105
print("Opción put seleccionada:")
## [1] "Opción put seleccionada:"
print(best_put)
## # A tibble: 1 × 13
## Contract_Name Last_Trade_Date_(EDT…¹ Strike Last_Price Bid Ask Change
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 NVDA271217P001050… 4/17/2025 3:55 PM 105 27.6 0 0 0.45
## # ℹ abbreviated name: ¹`Last_Trade_Date_(EDT)`
## # ℹ 6 more variables: `%_Change` <chr>, Volume <chr>, Open_Interest <dbl>,
## # Implied_Volatility <dbl>, spread <dbl>, score <dbl>
Precio actual de ONON
ONON_price <- s02
URL de opciones para ONON
url <- "https://finance.yahoo.com/quote/ONON/options/?date=1799971200"
Leer la página con cabecera de navegador (para evitar bloqueos)
page <- read_html(httr::GET(url, httr::add_headers(`User-Agent` = "Mozilla/5.0")))
Extraer la tabla de PUTs (segunda tabla)
puts_table <- page %>%
html_elements("table") %>%
.[[2]] %>%
html_table(fill = TRUE)
Limpiar nombres de columnas
names(puts_table) <- gsub("\\s+", "_", names(puts_table))
Convertir columnas clave y calcular el spread
puts_clean <- puts_table %>%
mutate(
Strike = as.numeric(Strike),
Bid = as.numeric(Bid),
Ask = as.numeric(Ask),
Last_Price = as.numeric(Last_Price),
Implied_Volatility = as.numeric(gsub("%", "", Implied_Volatility)) / 100,
spread = Ask - Bid
) %>%
filter(
!is.na(Strike), !is.na(Bid), !is.na(Ask),
Strike > ONON_price # <-- solo ITM
) %>%
arrange(spread)
Relación open inteterest y spread
puts_clean <- puts_clean %>%
mutate(
Open_Interest = as.numeric(gsub(",", "", Open_Interest)),
# Score que premia alto interés abierto y bajo spread
score = Open_Interest / (spread + 1e-6) # evitamos división por 0
) %>%
arrange(desc(score)) # Mejor score = mayor liquidez y menor spread
Seleccionar la mejor opción
best_put2 <- puts_clean %>% slice(1)
selected_strike2 <- best_put2$Strike
selected_prima2 <- best_put2$Last_Price
Mostrar resultados
glue::glue("Strike seleccionado (PUT ITM con alto interés y bajo spread): {selected_strike2}")
## Strike seleccionado (PUT ITM con alto interés y bajo spread): 47.5
print("Opción put seleccionada:")
## [1] "Opción put seleccionada:"
print(best_put2)
## # A tibble: 1 × 13
## Contract_Name Last_Trade_Date_(EDT…¹ Strike Last_Price Bid Ask Change
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 ONON270115P000475… 3/7/2025 2:45 PM 47.5 10.6 0 0 0
## # ℹ abbreviated name: ¹`Last_Trade_Date_(EDT)`
## # ℹ 6 more variables: `%_Change` <chr>, Volume <chr>, Open_Interest <dbl>,
## # Implied_Volatility <dbl>, spread <dbl>, score <dbl>
Precio actual de NU
NU_price <- s01
URL de opciones para NU
url <- "https://finance.yahoo.com/quote/NU/options/?date=1829001600"
Leer la página con cabecera de navegador (para evitar bloqueos)
page <- read_html(httr::GET(url, httr::add_headers(`User-Agent` = "Mozilla/5.0")))
Extraer la tabla de PUTs (segunda tabla)
puts_table <- page %>%
html_elements("table") %>%
.[[2]] %>%
html_table(fill = TRUE)
Limpiar nombres de columnas
names(puts_table) <- gsub("\\s+", "_", names(puts_table))
Convertir columnas clave y calcular el spread
puts_clean <- puts_table %>%
mutate(
Strike = as.numeric(Strike),
Bid = as.numeric(Bid),
Ask = as.numeric(Ask),
Last_Price = as.numeric(Last_Price),
Implied_Volatility = as.numeric(gsub("%", "", Implied_Volatility)) / 100,
spread = Ask - Bid
) %>%
filter(
!is.na(Strike), !is.na(Bid), !is.na(Ask),
Strike > NU_price # <-- solo ITM
) %>%
arrange(spread)
Relación open inteterest y spread
puts_clean <- puts_clean %>%
mutate(
Open_Interest = as.numeric(gsub(",", "", Open_Interest)),
# Score que premia alto interés abierto y bajo spread
score = Open_Interest / (spread + 1e-6) # evitamos división por 0
) %>%
arrange(desc(score)) # Mejor score = mayor liquidez y menor spread
Seleccionar la mejor opción
best_put1 <- puts_clean %>% slice(1)
selected_strike1 <- best_put1$Strike
selected_prima1 <- best_put1$Last_Price
Mostrar resultados
glue::glue("Strike seleccionado (PUT ITM con alto interés y bajo spread): {selected_strike1}")
## Strike seleccionado (PUT ITM con alto interés y bajo spread): 15
print("Opción put seleccionada:")
## [1] "Opción put seleccionada:"
print(best_put1)
## # A tibble: 1 × 13
## Contract_Name `Last_Trade_Date_(EDT)` Strike Last_Price Bid Ask Change
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 NU271217P00015000 3/19/2025 1:59 PM 15 5.05 4.6 5.55 0
## # ℹ 6 more variables: `%_Change` <chr>, Volume <int>, Open_Interest <dbl>,
## # Implied_Volatility <dbl>, spread <dbl>, score <dbl>
Para la valuación de las opciones PUT seleccionadas, se utilizaron modelos binomiales recombinantes con un horizonte de 8 pasos (equivalente a trimestres durante 2 años). Esta elección permite:
Se utilizó la fórmula clásica de Cox-Ross-Rubinstein (CRR) para
calcular los factores de subida (u), bajada
(d) y la probabilidad neutral al riesgo (p).
Esta metodología es coherente con el enfoque de valoración no arbitrado
y permite extender fácilmente el análisis a opciones exóticas en
trabajos futuros.
PARÁMETROS INICIALES
precio_inicial <- s03 # Precio actual de NVDA
strike <- selected_strike # Precio de ejercicio (strike)
horizonte <- 2 # Horizonte de tiempo (años)
pasos <- 8 # Número de pasos (8 trimestres)
volatilidad <- sigma3 # Volatilidad anual
tasa_rf <- rf_trimestral # Tasa libre de riesgo anual
CÁLCULOS BASE
delta_t <- horizonte / pasos
sube <- exp(volatilidad * sqrt(delta_t))
baja <- 1 / sube
probabilidad <- (exp(tasa_rf * delta_t) - baja) / (sube - baja)
Árbol de precios del subyacente
precios_arbol <- matrix(0, nrow = pasos+1, ncol = pasos+1)
for (columna in 0:pasos) {
for (fila in 0:columna) {
precios_arbol[fila+1, columna+1] <- precio_inicial * sube^(columna-fila) * baja^fila
}
}
Árbol de valoración de la put
valor_opcion <- matrix(0, nrow = pasos+1, ncol = pasos+1)
valor_opcion[, pasos+1] <- pmax(strike - precios_arbol[, pasos+1], 0)
Valoración hacia atrás
for (columna in (pasos-1):0) {
for (fila in 0:columna) {
valor_opcion[fila+1, columna+1] <- exp(-tasa_rf * delta_t) * (
probabilidad * valor_opcion[fila+1, columna+2] +
(1 - probabilidad) * valor_opcion[fila+2, columna+2]
)
}
}
Preparar DataFrame para graficar
datos_grafico <- data.frame()
for (columna in 0:pasos) {
for (fila in 0:columna) {
datos_grafico <- rbind(datos_grafico, data.frame(
Paso = columna,
Nodo = fila,
Precio = precios_arbol[fila+1, columna+1],
Valor = round(valor_opcion[fila+1, columna+1], 2),
InTheMoney = strike > precios_arbol[fila+1, columna+1] # Añadimos esta variable para identificar cuando está ITM
))
}
}
Ajustar posiciones para graficar tipo árbol
datos_grafico <- datos_grafico %>%
mutate(x = Paso,
y = -Nodo + Paso / 2)
Graficar árbol binomial
ggplot(datos_grafico, aes(x = x, y = y)) +
geom_point(aes(color = InTheMoney), size = 5) + # Cambiado a InTheMoney
geom_text(aes(label = paste0("S=", round(Precio,1), "\nP=", Valor)), size = 3) +
scale_color_manual(values = c("FALSE" = "gray70", "TRUE" = "red")) +
labs(title = "Árbol Binomial de una Put Europea sobre NVDA",
subtitle = "Nodos donde strike > precio del activo (in the money) resaltados en rojo",
x = "Paso (Trimestre)", y = "Nodo") +
theme_minimal() +
theme(legend.position = "none")
PARÁMETROS INICIALES
precio_inicial <- s03 # Precio actual de NVDA
strike <- selected_strike # Precio de ejercicio (strike)
horizonte <- 2 # Horizonte de tiempo (años)
pasos <- 8 # Número de pasos (8 trimestres)
volatilidad <- sigma3 # Volatilidad anual
tasa_rf <- rf_trimestral # Tasa libre de riesgo anual
CÁLCULOS BASE
delta_t <- horizonte / pasos
sube <- exp(volatilidad * sqrt(delta_t))
baja <- 1 / sube
probabilidad <- (exp(tasa_rf * delta_t) - baja) / (sube - baja)
Árbol de precios del subyacente
precios_arbol <- matrix(0, nrow = pasos+1, ncol = pasos+1)
for (columna in 0:pasos) {
for (fila in 0:columna) {
precios_arbol[fila+1, columna+1] <- precio_inicial * sube^(columna-fila) * baja^fila
}
}
Arbol de valoración de la put (Americana)
valor_opcion <- matrix(0, nrow = pasos+1, ncol = pasos+1)
ejercicio <- matrix(FALSE, nrow = pasos+1, ncol = pasos+1)
valor_opcion[, pasos+1] <- pmax(strike - precios_arbol[, pasos+1], 0)
ejercicio[, pasos+1] <- (strike - precios_arbol[, pasos+1]) > 0
Valoración hacia atrás
for (columna in (pasos-1):0) {
for (fila in 0:columna) {
valor_continuar <- exp(-tasa_rf * delta_t) * (
probabilidad * valor_opcion[fila+1, columna+2] +
(1 - probabilidad) * valor_opcion[fila+2, columna+2]
)
valor_ejercer <- pmax(strike - precios_arbol[fila+1, columna+1], 0)
valor_opcion[fila+1, columna+1] <- pmax(valor_continuar, valor_ejercer)
ejercicio[fila+1, columna+1] <- (valor_ejercer > valor_continuar) & (valor_ejercer > 0)
}
}
Preparar DataFrame para graficar
datos_grafico <- data.frame()
for (columna in 0:pasos) {
for (fila in 0:columna) {
datos_grafico <- rbind(datos_grafico, data.frame(
Paso = columna,
Nodo = fila,
Precio = precios_arbol[fila+1, columna+1],
Valor = round(valor_opcion[fila+1, columna+1], 2),
Ejercida = ejercicio[fila+1, columna+1]
))
}
}
Ajustar posiciones para graficar tipo árbol
datos_grafico <- datos_grafico %>%
mutate(x = Paso,
y = -Nodo + Paso / 2)
Graficar árbol binomial
ggplot(datos_grafico, aes(x = x, y = y)) +
geom_point(aes(color = strike > Precio), size = 5) + # Cambiado a strike > Precio
geom_text(aes(label = paste0("S=", round(Precio,1), "\nP=", Valor)), size = 3) +
scale_color_manual(values = c("TRUE" = "blue", "FALSE" = "gray70")) +
labs(title = "Árbol Binomial de una Put Americana sobre NVDA",
subtitle = "Nodos con precio de ejercicio mayor al subyacente (in the money) resaltados en azul",
x = "Paso (Trimestre)", y = "Nodo") +
theme_minimal() +
theme(legend.position = "none")
PARÁMETROS INICIALES
precio_inicial <- s02 # Precio actual de ONON
strike <- selected_strike2 # Precio de ejercicio (strike)
horizonte <- 2 # Horizonte de tiempo (años)
pasos <- 8 # Número de pasos (8 trimestres)
volatilidad <- sigma2 # Volatilidad anual
tasa_rf <- rf_trimestral # Tasa libre de riesgo anual
CÁLCULOS BASE
delta_t <- horizonte / pasos
sube <- exp(volatilidad * sqrt(delta_t))
baja <- 1 / sube
probabilidad <- (exp(tasa_rf * delta_t) - baja) / (sube - baja)
Árbol de precios del subyacente
precios_arbol2 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
for (columna in 0:pasos) {
for (fila in 0:columna) {
precios_arbol2[fila+1, columna+1] <- precio_inicial * sube^(columna-fila) * baja^fila
}
}
Árbol de valoración de la put
valor_opcion2 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
valor_opcion2[, pasos+1] <- pmax(strike - precios_arbol2[, pasos+1], 0)
Valoración hacia atrás
for (columna in (pasos-1):0) {
for (fila in 0:columna) {
valor_opcion2[fila+1, columna+1] <- exp(-tasa_rf * delta_t) * (
probabilidad * valor_opcion2[fila+1, columna+2] +
(1 - probabilidad) * valor_opcion2[fila+2, columna+2]
)
}
}
Preparar DataFrame para graficar
datos_grafico2 <- data.frame()
for (columna in 0:pasos) {
for (fila in 0:columna) {
datos_grafico2 <- rbind(datos_grafico2, data.frame(
Paso = columna,
Nodo = fila,
Precio = precios_arbol2[fila+1, columna+1],
Valor = round(valor_opcion2[fila+1, columna+1], 2),
InTheMoney = strike > precios_arbol2[fila+1, columna+1] # Añadimos esta variable para identificar cuando está ITM
))
}
}
Ajustar posiciones para graficar tipo árbol
datos_grafico2 <- datos_grafico2 %>%
mutate(x = Paso,
y = -Nodo + Paso / 2)
Graficar árbol binomial —
ggplot(datos_grafico2, aes(x = x, y = y)) +
geom_point(aes(color = InTheMoney), size = 5) + # Cambiado a InTheMoney
geom_text(aes(label = paste0("S=", round(Precio,1), "\nP=", Valor)), size = 3) +
scale_color_manual(values = c("TRUE" = "green", "FALSE" = "lightblue")) +
labs(title = "Árbol Binomial de una Put Europea sobre ONON",
subtitle = "Nodos donde strike > precio del activo (in the money) resaltados en rojo",
x = "Paso (Trimestre)", y = "Nodo") +
theme_minimal() +
theme(legend.position = "none")
PARÁMETROS INICIALES
precio_inicial <- s02 # Precio actual de ONON
strike <- selected_strike2 # Precio de ejercicio (strike)
horizonte <- 2 # Horizonte de tiempo (años)
pasos <- 8 # Número de pasos (8 trimestres)
volatilidad <- sigma2 # Volatilidad anual
tasa_rf <- rf_trimestral # Tasa libre de riesgo anual
CÁLCULOS BASE
delta_t <- horizonte / pasos
sube <- exp(volatilidad * sqrt(delta_t))
baja <- 1 / sube
probabilidad <- (exp(tasa_rf * delta_t) - baja) / (sube - baja)
Árbol de precios del subyacente
precios_arbol2 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
for (columna in 0:pasos) {
for (fila in 0:columna) {
precios_arbol2[fila+1, columna+1] <- precio_inicial * sube^(columna-fila) * baja^fila
}
}
Árbol de valoración de la put (Americana)
valor_opcion2 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
ejercicio <- matrix(FALSE, nrow = pasos+1, ncol = pasos+1)
valor_opcion2[, pasos+1] <- pmax(strike - precios_arbol2[, pasos+1], 0)
ejercicio[, pasos+1] <- (strike - precios_arbol2[, pasos+1]) > 0
Valoración hacia atrás
for (columna in (pasos-1):0) {
for (fila in 0:columna) {
valor_continuar <- exp(-tasa_rf * delta_t) * (
probabilidad * valor_opcion2[fila+1, columna+2] +
(1 - probabilidad) * valor_opcion2[fila+2, columna+2]
)
valor_ejercer <- pmax(strike - precios_arbol2[fila+1, columna+1], 0)
valor_opcion2[fila+1, columna+1] <- pmax(valor_continuar, valor_ejercer)
ejercicio[fila+1, columna+1] <- (valor_ejercer > valor_continuar) & (valor_ejercer > 0)
}
}
Preparar DataFrame para graficar
datos_grafico2 <- data.frame()
for (columna in 0:pasos) {
for (fila in 0:columna) {
datos_grafico2 <- rbind(datos_grafico2, data.frame(
Paso = columna,
Nodo = fila,
Precio = precios_arbol2[fila+1, columna+1],
Valor = round(valor_opcion2[fila+1, columna+1], 2),
Ejercida = ejercicio[fila+1, columna+1]
))
}
}
Ajustar posiciones para graficar tipo árbol
datos_grafico2 <- datos_grafico2 %>%
mutate(x = Paso,
y = -Nodo + Paso / 2)
Graficar árbol binomial
ggplot(datos_grafico2, aes(x = x, y = y)) +
geom_point(aes(color = strike > Precio), size = 5) + # Cambiado a strike > Precio
geom_text(aes(label = paste0("S=", round(Precio,1), "\nP=", Valor)), size = 3) +
scale_color_manual(values = c("TRUE" = "green", "FALSE" = "lightblue")) +
labs(title = "Árbol Binomial de una Put Americana sobre ONON",
subtitle = "Nodos con precio de ejercicio mayor al subyacente (in the money) resaltados en azul",
x = "Paso (Trimestre)", y = "Nodo") +
theme_minimal() +
theme(legend.position = "none")
PARÁMETROS INICIALES
precio_inicial <- s01 # Precio actual de NU
strike <- selected_strike1 # Precio de ejercicio (strike)
horizonte <- 2 # Horizonte de tiempo (años)
pasos <- 8 # Número de pasos (8 trimestres)
volatilidad <- sigma1 # Volatilidad anual
tasa_rf <- rf_trimestral # Tasa libre de riesgo anual
CÁLCULOS BASE
delta_t <- horizonte / pasos
sube <- exp(volatilidad * sqrt(delta_t))
baja <- 1 / sube
probabilidad <- (exp(tasa_rf * delta_t) - baja) / (sube - baja)
Árbol de precios del subyacente
precios_arbol1 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
for (columna in 0:pasos) {
for (fila in 0:columna) {
precios_arbol1[fila+1, columna+1] <- precio_inicial * sube^(columna-fila) * baja^fila
}
}
Árbol de valoración de la put
valor_opcion1 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
valor_opcion1[, pasos+1] <- pmax(strike - precios_arbol1[, pasos+1], 0)
Valoración hacia atrás
for (columna in (pasos-1):0) {
for (fila in 0:columna) {
valor_opcion1[fila+1, columna+1] <- exp(-tasa_rf * delta_t) * (
probabilidad * valor_opcion1[fila+1, columna+2] +
(1 - probabilidad) * valor_opcion1[fila+2, columna+2]
)
}
}
Preparar DataFrame para graficar
datos_grafico1 <- data.frame()
for (columna in 0:pasos) {
for (fila in 0:columna) {
datos_grafico1 <- rbind(datos_grafico1, data.frame(
Paso = columna,
Nodo = fila,
Precio = precios_arbol1[fila+1, columna+1],
Valor = round(valor_opcion1[fila+1, columna+1], 2),
InTheMoney = strike > precios_arbol1[fila+1, columna+1] # Añadimos esta variable para identificar cuando está ITM
))
}
}
Ajustar posiciones para graficar tipo árbol
datos_grafico1 <- datos_grafico1 %>%
mutate(x = Paso,
y = -Nodo + Paso / 2)
Graficar árbol binomial
ggplot(datos_grafico1, aes(x = x, y = y)) +
geom_point(aes(color = InTheMoney), size = 5) + # Cambiado a InTheMoney
geom_text(aes(label = paste0("S=", round(Precio,1), "\nP=", Valor)), size = 3) +
scale_color_manual(values = c("TRUE" = "green", "FALSE" = "lightblue")) +
labs(title = "Árbol Binomial de una Put Europea sobre NU",
subtitle = "Nodos donde strike > precio del activo (in the money) resaltados en rojo",
x = "Paso (Trimestre)", y = "Nodo") +
theme_minimal() +
theme(legend.position = "none")
PARÁMETROS INICIALES
precio_inicial <- s01 # Precio actual de NU
strike <- selected_strike1 # Precio de ejercicio (strike)
horizonte <- 2 # Horizonte de tiempo (años)
pasos <- 8 # Número de pasos (8 trimestres)
volatilidad <- sigma1 # Volatilidad anual
tasa_rf <- rf_trimestral # Tasa libre de riesgo anual
CÁLCULOS BASE
delta_t <- horizonte / pasos
sube <- exp(volatilidad * sqrt(delta_t))
baja <- 1 / sube
probabilidad <- (exp(tasa_rf * delta_t) - baja) / (sube - baja)
Árbol de precios del subyacente
precios_arbol1 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
for (columna in 0:pasos) {
for (fila in 0:columna) {
precios_arbol1[fila+1, columna+1] <- precio_inicial * sube^(columna-fila) * baja^fila
}
}
Árbol de valoración de la put (Americana)
valor_opcion1 <- matrix(0, nrow = pasos+1, ncol = pasos+1)
ejercicio <- matrix(FALSE, nrow = pasos+1, ncol = pasos+1)
valor_opcion1[, pasos+1] <- pmax(strike - precios_arbol1[, pasos+1], 0)
ejercicio[, pasos+1] <- (strike - precios_arbol1[, pasos+1]) > 0
Valoración hacia atrás
for (columna in (pasos-1):0) {
for (fila in 0:columna) {
valor_continuar <- exp(-tasa_rf * delta_t) * (
probabilidad * valor_opcion1[fila+1, columna+2] +
(1 - probabilidad) * valor_opcion1[fila+2, columna+2]
)
valor_ejercer <- pmax(strike - precios_arbol1[fila+1, columna+1], 0)
valor_opcion1[fila+1, columna+1] <- pmax(valor_continuar, valor_ejercer)
ejercicio[fila+1, columna+1] <- (valor_ejercer > valor_continuar) & (valor_ejercer > 0)
}
}
Preparar DataFrame para graficar
datos_grafico1 <- data.frame()
for (columna in 0:pasos) {
for (fila in 0:columna) {
datos_grafico1 <- rbind(datos_grafico1, data.frame(
Paso = columna,
Nodo = fila,
Precio = precios_arbol1[fila+1, columna+1],
Valor = round(valor_opcion1[fila+1, columna+1], 2),
Ejercida = ejercicio[fila+1, columna+1]
))
}
}
Ajustar posiciones para graficar tipo árbol
datos_grafico1 <- datos_grafico1 %>%
mutate(x = Paso,
y = -Nodo + Paso / 2)
Graficar árbol binomial
ggplot(datos_grafico1, aes(x = x, y = y)) +
geom_point(aes(color = strike > Precio), size = 5) + # Cambiado a strike > Precio
geom_text(aes(label = paste0("S=", round(Precio,1), "\nP=", Valor)), size = 3) +
scale_color_manual(values = c("TRUE" = "green", "FALSE" = "lightblue")) +
labs(title = "Árbol Binomial de una Put Americana sobre NU",
subtitle = "Nodos con precio de ejercicio mayor al subyacente (in the money) resaltados en azul",
x = "Paso (Trimestre)", y = "Nodo") +
theme_minimal() +
theme(legend.position = "none")
Apalancamiento por 850000 dolares
Definir cantidad de contratos a comprar
Inversion_inicial <- 1000000
porcentaje_cubierto <- 0.85
apalancamiento_nvda = porcentaje_cubierto*Inversion_inicial
cantidad_contratos = apalancamiento_nvda/(selected_prima*cantidad_acciones_por_contrato)
strike_price <- selected_strike # Asumimos que ya está definida
Función para encontrar valor de opción desde el árbol en un trimestre y precio dado
obtener_valor_opcion_desde_arbol <- function(trimestre, precio_simulado, precios_arbol, valor_opcion) {
if (trimestre == 0) return(0) # No hay cobertura en t=0
# Extraer precios y valores para el trimestre correspondiente
precios_trimestre <- precios_arbol[1:(trimestre+1), trimestre+1]
valores_trimestre <- valor_opcion[1:(trimestre+1), trimestre+1]
# Buscar el nodo más cercano al precio simulado
idx_cercano <- which.min(abs(precios_trimestre - precio_simulado))
return(valores_trimestre[idx_cercano])
}
Aplicar la función por cada fila
seguimiento_portafolio$Valor_Put_NVDA <- mapply(
obtener_valor_opcion_desde_arbol,
seguimiento_portafolio$Trimestre,
seguimiento_portafolio$Precio_NVDA,
MoreArgs = list(precios_arbol = precios_arbol, valor_opcion = valor_opcion)
)
cobertura NVDA (valor PUT * 100 * opciones) por trimestre
seguimiento_portafolio$Cobertura_NVDA <- seguimiento_portafolio$Valor_Put_NVDA * (cantidad_contratos*cantidad_acciones_por_contrato) - apalancamiento_nvda
seguimiento_portafolio$Cobertura_NVDA[1]=0
Diferencias respecto al valor inicial
seguimiento_portafolio$Diferencia_Con_Cobertura <- seguimiento_portafolio$Diferencia_Sin_Cobertura + seguimiento_portafolio$Cobertura_NVDA
library(knitr)
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.4.3
##
## Adjuntando el paquete: 'kableExtra'
## The following object is masked from 'package:dplyr':
##
## group_rows
seguimiento_portafolio %>%
kable("html",
caption = "Tabla de Seguimiento del Portafolio",
align = "c", # Centra las columnas
escape = FALSE) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive", "bordered"),
full_width = TRUE,
position = "center",
font_size = 13) %>%
scroll_box(width = "100%", height = "400px")
| Trimestre | Dia_Simulacion | Cantidad_NU | Cantidad_ONON | Cantidad_NVDA | Precio_NU | Precio_ONON | Precio_NVDA | Valor_NU | Valor_ONON | Valor_NVDA | Valor_Portafolio | Diferencia_Sin_Cobertura | Valor_Put_NVDA | Cobertura_NVDA | Diferencia_Con_Cobertura |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 32511 | 8523 | 2760 | 11.000000 | 42.50000 | 101.49000 | 357621.0 | 362227.5 | 280112.4 | 999960.9 | 0.00 | 0.000000 | 0.0 | 0.00 |
| 1 | 63 | 32511 | 8523 | 2760 | 11.965552 | 38.02261 | 112.83788 | 389012.0 | 324066.7 | 311432.5 | 1024511.3 | 24550.36 | 17.802531 | -301733.7 | -277183.29 |
| 2 | 126 | 32511 | 8523 | 2760 | 6.277024 | 55.09551 | 123.86281 | 204072.3 | 469579.0 | 341861.4 | 1015512.7 | 15551.83 | 25.436387 | -66633.0 | -51081.17 |
| 3 | 189 | 32511 | 8523 | 2760 | 6.380179 | 64.87006 | 200.84457 | 207426.0 | 552887.5 | 554331.0 | 1314644.5 | 314683.63 | 2.963095 | -758745.3 | -444061.63 |
| 4 | 252 | 32511 | 8523 | 2760 | 7.526612 | 39.53684 | 155.67141 | 244697.7 | 336972.4 | 429653.1 | 1011323.2 | 11362.34 | 5.209265 | -689569.7 | -678207.41 |
| 5 | 315 | 32511 | 8523 | 2760 | 6.157764 | 30.25386 | 171.84406 | 200195.1 | 257853.7 | 474289.6 | 932338.3 | -67622.56 | 9.136959 | -568608.1 | -636230.70 |
| 6 | 378 | 32511 | 8523 | 2760 | 5.439693 | 28.54253 | 155.67380 | 176849.9 | 243268.0 | 429659.7 | 849777.5 | -150183.37 | 1.033828 | -818161.1 | -968344.47 |
| 7 | 441 | 32511 | 8523 | 2760 | 6.205420 | 41.00456 | 157.10823 | 201744.4 | 349481.8 | 433618.7 | 984845.0 | -15115.94 | 1.904925 | -791333.8 | -806449.78 |
| 8 | 504 | 32511 | 8523 | 2760 | 5.290320 | 27.33320 | 86.80193 | 171993.6 | 232960.9 | 239573.3 | 644527.8 | -355433.10 | 3.510002 | -741902.1 | -1097335.21 |
Crear un dataframe con los datos exactos de la tabla compartida
datos <- data.frame(
Trimestre = seguimiento_portafolio$Trimestre,
Diferencia_Sin_Cobertura = seguimiento_portafolio$Diferencia_Sin_Cobertura,
Diferencia_Con_Cobertura = seguimiento_portafolio$Diferencia_Con_Cobertura)
Convertir los datos a formato largo para ggplot2
datos_largo <- tidyr::pivot_longer(
datos,
cols = c("Diferencia_Sin_Cobertura", "Diferencia_Con_Cobertura"),
names_to = "Tipo",
values_to = "Valor"
)
Crear el gráfico de barras comparativo con etiquetas de texto
library(ggplot2)
library(scales)
## Warning: package 'scales' was built under R version 4.4.3
##
## Adjuntando el paquete: 'scales'
## The following object is masked from 'package:purrr':
##
## discard
## The following object is masked from 'package:readr':
##
## col_factor
Función para formatear valores grandes
formatear_valor <- function(x) {
ifelse(abs(x) >= 1000000,
paste0(round(x/1000000, 1), "M"),
ifelse(abs(x) >= 1000,
paste0(round(x/1000, 1), "K"),
round(x, 0)))
}
Asegurarse de que “Trimestre” y “Tipo” sean factores con orden correcto
datos_largo$Trimestre <- factor(datos_largo$Trimestre, levels = unique(datos_largo$Trimestre))
datos_largo$Tipo <- factor(datos_largo$Tipo,
levels = c("Diferencia_Sin_Cobertura", "Diferencia_Con_Cobertura"))
Gráfico
ggplot(datos_largo, aes(x = Trimestre, y = Valor, fill = Tipo)) +
geom_bar(stat = "identity", position = position_dodge(width = 0.7), width = 0.6) +
# Etiquetas dentro de las barras
geom_text(aes(label = format(round(Valor), big.mark = ",", scientific = FALSE),
y = ifelse(Valor >= 0, Valor / 2, Valor / 2)),
position = position_dodge(width = 0.7),
angle = 90,
hjust = 0.7,
size = 3.5,
color = "black") +
# Colores manuales
scale_fill_manual(values = c("Diferencia_Sin_Cobertura" = "lightblue",
"Diferencia_Con_Cobertura" = "green"),
labels = c("Sin Cobertura", "Con Cobertura")) +
# Títulos y ejes
labs(title = "Comparación PyG Con y Sin Cobertura",
x = "Trimestre",
y = "PyG ($)",
fill = "Tipo de Cobertura") +
# Tema y estilo
theme_minimal() +
theme(axis.text.x = element_text(angle = 0, hjust = 0.6),
legend.position = "bottom") +
# Línea horizontal en cero
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
# Formato de valores del eje Y
scale_y_continuous(labels = scales::comma)
ANÁLISIS TÉCNICO: Esta cobertura representa una estrategia de “protective put”, donde se protege el activo subyacente (NVDA) frente a caídas severas, manteniendo la exposición al alza. El costo de la prima reduce el retorno neto si el escenario alcista se materializa. Sin embargo, en escenarios bajistas, la PUT se valoriza, compensando las pérdidas y reduciendo el downside risk. Esta estrategia es efectiva cuando se prevé alta volatilidad asimétrica o eventos de cola (tail risk).
Reinicio
rm(seguimiento_portafolio)
seguimiento_portafolio <- tibble(
Trimestre = 0:(length(dias_seguimiento) - 1),
Dia_Simulacion = dias_seguimiento,
Cantidad_NU = cantidad_NU,
Cantidad_ONON = cantidad_ONON,
Cantidad_NVDA = cantidad_NVDA,
Precio_NU = trayectoria_NU[dias_seguimiento],
Precio_ONON = trayectoria_ONON[dias_seguimiento],
Precio_NVDA = trayectoria_NVDA[dias_seguimiento],
Valor_NU = Precio_NU * cantidad_NU,
Valor_ONON = Precio_ONON * cantidad_ONON,
Valor_NVDA = Precio_NVDA * cantidad_NVDA,
Valor_Portafolio = Valor_NU + Valor_ONON + Valor_NVDA,
Diferencia_Sin_Cobertura = Valor_Portafolio - valor_inicial,
)
Apalancamiento por 850000 dolares
Definir cantidad de contratos a comprar
Inversion_inicial2 <- 1000000
porcentaje_cubierto2 <- 0.85
apalancamiento_total2 = porcentaje_cubierto2*Inversion_inicial2
apalancamiento_NU = as.numeric(min_var_allocation[1, 1]*apalancamiento_total2)
apalancamiento_ONON = as.numeric(min_var_allocation[1, 2]*apalancamiento_total2)
apalancamiento_NVDA = as.numeric(min_var_allocation[1, 3]*apalancamiento_total2)
cantidad_contratos_NU = apalancamiento_NU/(selected_prima1*cantidad_acciones_por_contrato)
cantidad_contratos_ONON = apalancamiento_ONON/(selected_prima2*cantidad_acciones_por_contrato)
cantidad_contratos_NVDA = apalancamiento_NVDA/(selected_prima*cantidad_acciones_por_contrato)
strike_price <- selected_strike # Asumimos que ya está definida
Función para encontrar valor de opción desde el árbol en un trimestre y precio dado
obtener_valor_opcion_desde_arbol <- function(trimestre, precio_simulado, precios_arbol, valor_opcion) {
if (trimestre == 0) return(0) # No hay cobertura en t=0
# Extraer precios y valores para el trimestre correspondiente
precios_trimestre <- precios_arbol[1:(trimestre+1), trimestre+1]
valores_trimestre <- valor_opcion[1:(trimestre+1), trimestre+1]
# Buscar el nodo más cercano al precio simulado
idx_cercano <- which.min(abs(precios_trimestre - precio_simulado))
return(valores_trimestre[idx_cercano])
}
Aplicar la función por cada fila
seguimiento_portafolio$Valor_Put_NU <- mapply(
obtener_valor_opcion_desde_arbol,
seguimiento_portafolio$Trimestre,
seguimiento_portafolio$Precio_NU,
MoreArgs = list(precios_arbol = precios_arbol1, valor_opcion = valor_opcion1)
)
seguimiento_portafolio$Valor_Put_ONON <- mapply(
obtener_valor_opcion_desde_arbol,
seguimiento_portafolio$Trimestre,
seguimiento_portafolio$Precio_ONON,
MoreArgs = list(precios_arbol = precios_arbol2, valor_opcion = valor_opcion2)
)
seguimiento_portafolio$Valor_Put_NVDA <- mapply(
obtener_valor_opcion_desde_arbol,
seguimiento_portafolio$Trimestre,
seguimiento_portafolio$Precio_NVDA,
MoreArgs = list(precios_arbol = precios_arbol, valor_opcion = valor_opcion)
)
cobertura (valor PUT * 100 * opciones) por trimestre
seguimiento_portafolio$Cobertura_NU <- seguimiento_portafolio$Valor_Put_NU * (cantidad_contratos_NU*cantidad_acciones_por_contrato) - apalancamiento_NU
seguimiento_portafolio$Cobertura_NU[1]=0
seguimiento_portafolio$Cobertura_ONON <- seguimiento_portafolio$Valor_Put_ONON * (cantidad_contratos_ONON*cantidad_acciones_por_contrato) - apalancamiento_ONON
seguimiento_portafolio$Cobertura_ONON[1]=0
seguimiento_portafolio$Cobertura_NVDA <- seguimiento_portafolio$Valor_Put_NVDA * (cantidad_contratos_NVDA*cantidad_acciones_por_contrato) - apalancamiento_NVDA
seguimiento_portafolio$Cobertura_NVDA[1]=0
seguimiento_portafolio$Cobertura_total = seguimiento_portafolio$Cobertura_NU + seguimiento_portafolio$Cobertura_ONON + seguimiento_portafolio$Cobertura_NVDA
Diferencia con cobertura respecto al valor inicial
seguimiento_portafolio$Diferencia_Con_Cobertura <- seguimiento_portafolio$Diferencia_Sin_Cobertura + seguimiento_portafolio$Cobertura_total
seguimiento_portafolio %>%
kable("html",
caption = "Tabla de Seguimiento del Portafolio",
align = "c", # Centra las columnas
escape = FALSE) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive", "bordered"),
full_width = TRUE,
position = "center",
font_size = 13) %>%
scroll_box(width = "100%", height = "400px")
| Trimestre | Dia_Simulacion | Cantidad_NU | Cantidad_ONON | Cantidad_NVDA | Precio_NU | Precio_ONON | Precio_NVDA | Valor_NU | Valor_ONON | Valor_NVDA | Valor_Portafolio | Diferencia_Sin_Cobertura | Valor_Put_NU | Valor_Put_ONON | Valor_Put_NVDA | Cobertura_NU | Cobertura_ONON | Cobertura_NVDA | Cobertura_total | Diferencia_Con_Cobertura |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 32511 | 8523 | 2760 | 11.000000 | 42.50000 | 101.49000 | 357621.0 | 362227.5 | 280112.4 | 999960.9 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 0.00 | 0.00 | 0.00 | 0.0000 | 0.00 |
| 1 | 63 | 32511 | 8523 | 2760 | 11.965552 | 38.02261 | 112.83788 | 389012.0 | 324066.7 | 311432.5 | 1024511.3 | 24550.36 | 3.923296 | 18.143015 | 17.802531 | -67822.08 | 216630.00 | -84525.65 | 64282.2707 | 88832.63 |
| 2 | 126 | 32511 | 8523 | 2760 | 6.277024 | 55.09551 | 123.86281 | 204072.3 | 469579.0 | 341861.4 | 1015512.7 | 15551.83 | 8.539511 | 12.739582 | 25.436387 | 210051.47 | 60411.76 | -18666.12 | 251797.1058 | 267348.93 |
| 3 | 189 | 32511 | 8523 | 2760 | 6.380179 | 64.87006 | 200.84457 | 207426.0 | 552887.5 | 554331.0 | 1314644.5 | 314683.63 | 10.048899 | 7.626208 | 2.963095 | 300909.27 | -87420.62 | -212549.84 | 938.8136 | 315622.44 |
| 4 | 252 | 32511 | 8523 | 2760 | 7.526612 | 39.53684 | 155.67141 | 244697.7 | 336972.4 | 429653.1 | 1011323.2 | 11362.34 | 8.539511 | 11.308403 | 5.209265 | 210051.47 | 19035.04 | -193171.47 | 35915.0452 | 47277.38 |
| 5 | 315 | 32511 | 8523 | 2760 | 6.157764 | 30.25386 | 171.84406 | 200195.1 | 257853.7 | 474289.6 | 932338.3 | -67622.56 | 10.048899 | 16.264404 | 9.136959 | 300909.27 | 162317.61 | -159286.09 | 303940.7889 | 236318.23 |
| 6 | 378 | 32511 | 8523 | 2760 | 5.439693 | 28.54253 | 155.67380 | 176849.9 | 243268.0 | 429659.7 | 849777.5 | -150183.37 | 8.539511 | 22.450869 | 1.033828 | 210051.47 | 341174.06 | -229194.19 | 322031.3363 | 171847.97 |
| 7 | 441 | 32511 | 8523 | 2760 | 6.205420 | 41.00456 | 157.10823 | 201744.4 | 349481.8 | 433618.7 | 984845.0 | -15115.94 | 10.048899 | 14.871974 | 1.904925 | 300909.27 | 122061.19 | -221678.99 | 201291.4751 | 186175.53 |
| 8 | 504 | 32511 | 8523 | 2760 | 5.290320 | 27.33320 | 86.80193 | 171993.6 | 232960.9 | 239573.3 | 644527.8 | -355433.10 | 8.539511 | 22.450869 | 3.510002 | 210051.47 | 341174.06 | -207831.51 | 343394.0220 | -12039.08 |
Crear un dataframe con los datos exactos de la tabla compartida
datos <- data.frame(
Trimestre = seguimiento_portafolio$Trimestre,
Diferencia_Sin_Cobertura = seguimiento_portafolio$Diferencia_Sin_Cobertura,
Diferencia_Con_Cobertura = seguimiento_portafolio$Diferencia_Con_Cobertura)
Convertir los datos a formato largo para ggplot2
datos_largo <- tidyr::pivot_longer(
datos,
cols = c("Diferencia_Sin_Cobertura", "Diferencia_Con_Cobertura"),
names_to = "Tipo",
values_to = "Valor"
)
Crear el gráfico de barras comparativo con etiquetas de texto
library(ggplot2)
library(scales)
Función para formatear valores grandes
formatear_valor <- function(x) {
ifelse(abs(x) >= 1000000,
paste0(round(x/1000000, 1), "M"),
ifelse(abs(x) >= 1000,
paste0(round(x/1000, 1), "K"),
round(x, 0)))
}
Asegurarse de que “Trimestre” y “Tipo” sean factores con orden correcto
datos_largo$Trimestre <- factor(datos_largo$Trimestre, levels = unique(datos_largo$Trimestre))
datos_largo$Tipo <- factor(datos_largo$Tipo,
levels = c("Diferencia_Sin_Cobertura", "Diferencia_Con_Cobertura"))
Gráfico
ggplot(datos_largo, aes(x = Trimestre, y = Valor, fill = Tipo)) +
geom_bar(stat = "identity", position = position_dodge(width = 0.7), width = 0.6) +
# Etiquetas dentro de las barras
geom_text(aes(label = format(round(Valor), big.mark = ",", scientific = FALSE),
y = ifelse(Valor >= 0, Valor / 2, Valor / 2)),
position = position_dodge(width = 0.7),
angle = 90,
hjust = 0.7,
size = 3.5,
color = "black") +
# Colores manuales
scale_fill_manual(values = c("Diferencia_Sin_Cobertura" = "lightblue",
"Diferencia_Con_Cobertura" = "green"),
labels = c("Sin Cobertura", "Con Cobertura")) +
# Títulos y ejes
labs(title = "Comparación PyG Con y Sin Cobertura",
x = "Trimestre",
y = "PyG ($)",
fill = "Tipo de Cobertura") +
# Tema y estilo
theme_minimal() +
theme(axis.text.x = element_text(angle = 0, hjust = 0.6),
legend.position = "bottom") +
# Línea horizontal en cero
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
# Formato de valores del eje Y
scale_y_continuous(labels = scales::comma)
ANÁLISIS TÉCNICO: La cobertura proporcional utilizando puts ITM para cada activo replica un portafolio asegurado, segmentado por componente. Esta aproximación minimiza el riesgo idiosincrático, dado que cada activo tiene protección directa acorde a su peso en el portafolio. Esta estrategia reduce la exposición conjunta a eventos extremos específicos de cada activo (riesgo específico) y puede interpretarse como un seguro colectivo tipo portfolio insurance.
OPCION NVDA VS COBERTURA OPCIONES DE LOS 3 ACTIVOS (NU, ONON, NVDA)
La implementación de coberturas con opciones PUT dentro de un portafolio diversificado compuesto por NU, ONON y NVDA permite mitigar el riesgo ante escenarios adversos en el precio de los activos. En este análisis se compararon dos estrategias:
Cobertura concentrada: Utilizando únicamente opciones PUT sobre NVDA, el activo con mayor peso en el portafolio de mínima varianza.
Cobertura diversificada: Utilizando opciones PUT sobre cada uno de los tres activos, asignando protección proporcional al peso del activo en el portafolio.
Esta estrategia busca mitigar pérdidas en el portafolio protegiendo al activo más representativo. Desde el punto de vista operativo, presenta ventajas en simplicidad, costos y liquidez, dado que NVDA es un activo altamente líquido con opciones eficientes.
Sin embargo, al analizar el comportamiento de la cobertura en los diferentes trimestres, se observa una protección parcial y desequilibrada frente a movimientos negativos que afectan a NU y ONON. Dado que la opción put sobre NVDA reacciona exclusivamente a su precio, no es capaz de cubrir los riesgos idiosincráticos de los demás activos. En los trimestres donde NU o ONON sufren mayores depreciaciones, la cobertura con NVDA resulta insuficiente.
Además, el análisis muestra que en ciertos trimestres la cobertura puede estar fuera del dinero o tener valores bajos, lo que reduce su impacto protector, generando una exposición residual significativa.
Cuando la estrategia de cobertura incluye opciones PUT sobre los tres activos subyacentes, los beneficios se hacen más evidentes:
Se logra una distribución eficiente del apalancamiento destinado a la protección, alineado con la composición del portafolio.
Cada activo cuenta con una cobertura específica, ajustada a su nivel de riesgo y volatilidad individual.
Se reduce el riesgo específico (idiosincrático) al abordar de manera directa las fluctuaciones negativas en cada subyacente.
El comportamiento del portafolio con cobertura diversificada exhibe menores drawdowns y menor dispersión intertrimestral en comparación con la cobertura única, mejorando la estabilidad del portafolio ajustado por riesgo.
Desde el punto de vista técnico, la combinación de puts sobre activos no perfectamente correlacionados genera un efecto de portafolio en la propia cobertura, permitiendo capturar los beneficios de la no-linealidad de las opciones en diferentes puntos del tiempo y trayectorias simuladas.
Los resultados gráficos y tabulados del portafolio con cobertura diversificada muestran:
Una mayor consistencia en la protección trimestral.
Menor dispersión en las diferencias respecto al portafolio base.
-Reducción del riesgo de cola (tail risk), como se evidencia en las métricas de VaR y en la suavización del perfil de pérdidas extremas.
Desde el enfoque de gestión activa del riesgo, la cobertura diversificada permite construir un portafolio más robusto frente a escenarios adversos, sin depender exclusivamente del comportamiento de un solo activo para mitigar pérdidas. Aunque conlleva una mayor complejidad operativa (múltiples primas, contratos y seguimiento), los beneficios en términos de reducción de riesgo, mejor comportamiento del Sharpe ratio ajustado, y control sobre las pérdidas máximas justifican su implementación.
Por tanto, para un portafolio estructurado bajo criterios de mínima varianza, la cobertura específica por activo es una estrategia más alineada con los principios de eficiencia y control de riesgo. En contraste, una cobertura parcial concentrada en un solo activo, aunque válida como estrategia básica, puede no ser suficiente bajo escenarios de mercado más volátiles o con shocks sectoriales diferenciados.
Yahoo Finance. (2025). NVIDIA Corporation (NVDA) Options Chain. Yahoo. https://finance.yahoo.com/quote/NVDA/options
Yahoo Finance. (2025). Nubank (NU) Options Chain. Yahoo. https://finance.yahoo.com/quote/NU/options
Yahoo Finance. (2025). On Holding AG (ONON) Options Chain. Yahoo. https://finance.yahoo.com/quote/ONON/options
OpenAI. (2025). ChatGPT (versión GPT-4) [Modelo de lenguaje grande]. https://chat.openai.com/