1 INTRODUCCION

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:

1.1 1. Diversificación Sectorial e Internacional

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.

1.2 2. Diferentes Perfiles de Riesgo y Volatilidad

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.

1.3 3. Accesibilidad y Liquidez de Datos Financieros

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.

1.4 4. Pertinencia Académica y Aplicabilidad Profesional

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.

2 PAQUETES UTILIZADOS

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)

3 INFORMACIÓN HISTÓRICA ACTIVOS SELECCIONADOS

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.

4 SELECCIÓN PORTAFOLIO

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.

4.1 Razones técnicas y estratégicas:

4.1.1 Minimización del riesgo total del portafolio

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.

4.1.2 No depende explícitamente de expectativas de retorno

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.

4.1.3 Alta utilidad en contextos de cobertura con opciones

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.

4.1.4 Composición diversificada naturalmente

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.

4.1.5 Enfoque didáctico y replicable

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)

4.2 Distribución inversión USD 1.000.000 considerando el peso de los activos

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

5 SIMULACIÓN PRECIOS DE LOS ACTIVOS NU, ONON Y NVDA

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.

  • Se generaron 5,000 trayectorias por activo durante un horizonte de 504 días (~2 años), con una frecuencia diaria (252 días/año).
  • Las trayectorias fueron filtradas por percentiles (P95 y P99) para excluir eventos extremos (explosivos), que podrían distorsionar el análisis de cobertura. Este paso responde a la necesidad de representar escenarios más plausibles en un contexto de gestión de riesgo.

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.

5.1 Simulacion precios NU

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

5.2 Simulacion precios ONON

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

5.3 Simulacion precios NVDA

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

6 COMPARACION PORTAFOLIO REAL VS PORTAFOLIOS SIMULADOS

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.

7 METRICAS TRIMESTRALES

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.

8 SELECCIÓN CONTRATO/PRECIO STRIKE/ PRIMA PARA CADA ACTIVO

8.1 Justificación Financiera en la Selección de Opciones PUT

La selección de contratos PUT se basó en criterios de eficiencia operativa y cobertura efectiva:

  • Se filtraron opciones In The Money (ITM) para maximizar el valor intrínseco inicial y asegurar protección ante caídas moderadas.
  • Se priorizó la liquidez, mediante la métrica Open Interest / Spread, que penaliza spreads amplios y favorece contratos con menor costo de entrada y salida.
  • Esta estrategia reduce el riesgo de ejecución ineficiente y refleja una cobertura más realista del portafolio ante eventos adversos.

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.

8.2 Elección CONTRATO/PRECIO STRIKE/ PRIMA NVDA

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>

8.3 Elección CONTRATO/PRECIO STRIKE/ PRIMA ONON

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>

8.4 ElecciónN CONTRATO/PRECIO STRIKE/ PRIMA NU

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>

9 ARBOLES BINOMIALES EUROPEO Y AMERICANO POR CADA ACTIVO

9.1 Construcción de Árboles Binomiales

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:

  • Capturar la posibilidad de ejercicio anticipado (en el caso de opciones americanas).
  • Evaluar el comportamiento del valor de la opción ante fluctuaciones discretas del activo subyacente.
  • Obtener una representación gráfica clara de los nodos ITM, facilitando la interpretación pedagógica.

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.

9.2 Arbol BINOMIAL EUROPEO NVDA

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

9.3 Arbol BINOMIAL AMERICANO NVDA

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

9.4 Arbol BINOMIAL EUROPEO ONON

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

9.5 Arbol BINOMIAL AMERICANO ONON

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

9.6 Arbol BINOMIAL EUROPEO NU

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

9.7 Arbol BINOMIAL AMERICANO NU

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

10 COBERTURAS (PUT)

10.1 Analisis cobertura con opcion NVDA

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")
Tabla de Seguimiento del Portafolio
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

10.2 Analisis grafico cobertura con opcion NVDA

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

10.3 Analisis cobertura con opciones de cada uno de los 3 activos

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")
Tabla de Seguimiento del Portafolio
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

10.4 Analisis grafico cobertura con opciones de cada uno de los 3 activos

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.

11 CONCLUSIÓN COBERTURA

OPCION NVDA VS COBERTURA OPCIONES DE LOS 3 ACTIVOS (NU, ONON, NVDA)

11.1 Conclusión Técnica sobre Estrategias de Cobertura con Opciones Put

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.

11.2 Evaluación del Caso con Cobertura Solo en NVDA

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.

11.3 Evaluación de la Cobertura con Opciones sobre los 3 Activos

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.

11.4 Resultados Cuantitativos y Gráficos

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.

11.5 Consideraciones Finales

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.

12 REFERENCIAS

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/