Alcance. Capital inicial: USD 10,000,000. Son tres acciones durante dos años. La cobertura base se mide sobre el número de acciones de cada empresa, no sobre el 85% del dinero. Los controles trimestrales son revaloraciones mark-to-market: las opciones no pagan cupones trimestrales. Las cotizaciones congeladas pueden ser diferidas y una orden real podría ejecutarse a otros precios.

1 Resumen ejecutivo y tesis

Se combinan tres negocios de tecnología de redes (Cisco), equipos de semiconductores (KLA) y biotecnología (Amgen). Esa diversificación sectorial no elimina la exposición conjunta al mercado. La tesis de cobertura es conservar participación en subidas y limitar caídas severas: se compara una put protectora, un collar de menor costo y una put spread adicional en la acción que más contribuye al riesgo. Son escenarios, no predicciones.

2 Fuentes y preparación de datos

leer_precios <- function(ticker) {
  ruta <- file.path(dir_datos, archivos[[ticker]])
  # Los CSV exportados por Excel incluyen BOM y encabezados UTF-8; se usan las
  # dos primeras columnas por posición para que el lector sea robusto al BOM.
  z <- read.csv(ruta, check.names=FALSE, stringsAsFactors=FALSE, fileEncoding="")
  fecha <- as.Date(z[[1]], "%d.%m.%Y")
  # El separador decimal de los CSV del usuario es coma; puede haber punto de millares.
  precio <- as.numeric(gsub(",", ".", gsub(".", "", z[[2]], fixed=TRUE), fixed=TRUE))
  # El exportador de CSCO trae una fila artificial para Navidad (mercado cerrado)
  # con OHLC idénticos; se elimina y se deja constancia en el control.
  fila_no_bursatil <- ticker=="CSCO" & fecha==as.Date("2025-12-25")
  valido <- !is.na(fecha) & is.finite(precio) & precio>0 & !fila_no_bursatil
  data.frame(ticker=ticker, fecha=fecha[valido], cierre=precio[valido],
             stringsAsFactors=FALSE) -> limpio
  limpio <- limpio[order(limpio$fecha), ]
  if (anyDuplicated(limpio$fecha)) stop("Fechas duplicadas en ", ticker)
  list(datos=limpio,
       control=data.frame(ticker=ticker, filas_originales=nrow(z),
                          filas_validas=nrow(limpio), descartadas=sum(!valido),
                          desde=min(limpio$fecha), hasta=max(limpio$fecha)))
}
lecturas <- lapply(tickers, leer_precios)
names(lecturas) <- tickers
control_precios <- do.call(rbind, lapply(lecturas, `[[`, "control"))
tabla(control_precios, "Control de los CSV de precios")
Control de los CSV de precios
ticker filas_originales filas_validas descartadas desde hasta
CSCO CSCO 1248 1247 1 2021-10-01 2026-09-21
KLAC KLAC 1247 1247 0 2021-10-01 2026-09-21
AMGN AMGN 1247 1247 0 2021-10-01 2026-09-21
lista_precios <- lapply(lecturas, `[[`, "datos")
datos_cierre <- Reduce(function(a,b) merge(a,b,by="fecha",all=FALSE),
                       lapply(lista_precios, function(z) {
                         x <- z[,c("fecha","cierre")]
                         names(x)[2] <- unique(z$ticker)
                         x
                       }))
datos_cierre <- datos_cierre[datos_cierre$fecha >= as.Date("2021-10-01"), ]
stopifnot(nrow(datos_cierre)>1000, all(complete.cases(datos_cierre)))

div <- read.csv(file.path(dir_datos,"dividendos_corregidos.csv"), stringsAsFactors=FALSE)
div$fecha_ex_dividendo <- as.Date(div$fecha_ex_dividendo)
div$dividendo_usd_por_accion <- as.numeric(div$dividendo_usd_por_accion)
if (anyNA(div$fecha_ex_dividendo) || anyNA(div$dividendo_usd_por_accion))
  stop("Fechas o importes de dividendos inválidos")
spot <- read.csv(file.path(dir_datos,"spot_2026-09-23.csv"), stringsAsFactors=FALSE)
opciones <- read.csv(file.path(dir_datos,"opciones_2028-01-21.csv"), stringsAsFactors=FALSE)
historico10 <- read.csv(file.path(dir_datos,"historico_yahoo_10y.csv"), stringsAsFactors=FALSE)
tasas <- read.csv(file.path(dir_datos,"treasury_2026.csv"), check.names=FALSE,
                  stringsAsFactors=FALSE)
# Nombres de columnas tal como quedaron en la captura reproducible.
names(spot)[match(c("precio_spot_usd","fecha_hora_cotizacion_ny","fecha_descarga_utc"),
                  names(spot))] <- c("last","trade_time_et","retrieved_utc")
names(opciones)[match(c("tipo","strike_usd","bid_usd","ask_usd",
                        "volumen_contratos","iv_fraccion","vencimiento",
                        "fecha_descarga_utc"),names(opciones))] <-
  c("type","strike","bid","ask","volume","implied_volatility",
    "expiration","retrieved_utc")
names(historico10)[match("cierre_ajustado_usd",names(historico10))] <- "adj_close"

La fuente de cierres diarios es el CSV histórico entregado para cada acción (hasta el 21/09/2026). Los dividendos son el CSV corregido a partir de dividendos.xlsx: la hoja original tenía las fechas desplazadas un día hacia atrás; se conservaron las originales para auditoría. La fuente de contraste del ajuste y de los diez años de GARCH es Yahoo Finance. El spot y las opciones son una captura separada de Yahoo Finance, y las tasas provienen de la curva diaria del Tesoro de EE. UU. No se combina el cierre del 21/09 con la prima capturada el 23/09 como si fueran simultáneos.

# Distribuir cada ex-dividendo al primer día bursátil disponible igual o posterior.
# Se excluyen eventos posteriores al último cierre del CSV.
div_historico <- div[div$fecha_ex_dividendo <= max(datos_cierre$fecha) &
                     div$fecha_ex_dividendo >= min(datos_cierre$fecha), ]
pagos <- matrix(0, nrow=nrow(datos_cierre), ncol=3,
                dimnames=list(NULL,tickers))
for (i in seq_len(nrow(div_historico))) {
  k <- which(tickers==div_historico$ticker[i])
  d <- which(datos_cierre$fecha >= div_historico$fecha_ex_dividendo[i])[1]
  if (length(k)==1 && !is.na(d)) pagos[d,k] <- pagos[d,k] + div_historico$dividendo_usd_por_accion[i]
}
P <- as.matrix(datos_cierre[,tickers])
ret_simple <- (P[-1,,drop=FALSE]+pagos[-1,,drop=FALSE]) /
              P[-nrow(P),,drop=FALSE]-1
ret_log <- log1p(ret_simple)
if (any(!is.finite(ret_simple))) stop("Retornos históricos inválidos")
fechas_ret <- datos_cierre$fecha[-1]
mu_anual <- colMeans(ret_simple)*252
cov_anual <- cov(ret_simple)*252
vol_anual <- sqrt(diag(cov_anual))
corr <- cor(ret_simple)
mu_log_diaria <- colMeans(ret_log)
control_div <- aggregate(dividendo_usd_por_accion ~ ticker, div_historico, length)
names(control_div)[2] <- "eventos_historicos"
tabla(merge(control_precios,control_div,by="ticker"),
      "Observaciones válidas, descarte de fila no bursátil y dividendos")
Observaciones válidas, descarte de fila no bursátil y dividendos
ticker filas_originales filas_validas descartadas desde hasta eventos_historicos
AMGN 1247 1247 0 2021-10-01 2026-09-21 20
CSCO 1248 1247 1 2021-10-01 2026-09-21 20
KLAC 1247 1247 0 2021-10-01 2026-09-21 20

Para media-varianza y Sharpe se usan retornos simples diarios, incluyendo dividendos: \(R_t=(P_t+D_t)/P_{t-1}-1\). Esto evita tratar un pago en efectivo como pérdida de la acción. Para la deriva de la simulación se usa \(\log(1+R_t)\). Los cierres del CSV ya están ajustados por splits cuando corresponde; los dividendos se incorporan por separado. La fila no bursátil de CSCO del 25/12/2025 se descarta por precio inválido. La muestra común tiene 1247 cierres y 1246 retornos diarios, desde 2021-10-01 hasta 2026-09-21.

stopifnot(all(tickers %in% spot$ticker))
spot <- spot[match(tickers,spot$ticker),]
S0 <- setNames(as.numeric(spot$last), tickers)
if (any(!is.finite(S0) | S0<=0)) stop("Spot inválido")
fecha_valor <- as.Date(substr(spot$trade_time_et[1],1,10))
if (any(as.Date(substr(spot$trade_time_et,1,10))!=fecha_valor)) stop("Fechas de spot diferentes")
capturas <- as.POSIXct(sub("Z$","",spot$retrieved_utc),
                       format="%Y-%m-%dT%H:%M:%S",tz="UTC")
if (any(abs(as.numeric(difftime(capturas,capturas[1],units="mins")))>30))
  warning("La captura de spot se extendió más de 30 minutos")

# La tasa se interpola entre Treasury 1y y 2y al plazo de la opción.
fecha_tasa <- as.Date(tasas$Date, "%m/%d/%Y")
if (all(is.na(fecha_tasa))) fecha_tasa <- as.Date(tasas$Date)
posibles <- which(fecha_tasa <= fecha_valor &
                  is.finite(suppressWarnings(as.numeric(tasas[["1 Yr"]]))) &
                  is.finite(suppressWarnings(as.numeric(tasas[["2 Yr"]]))))
if (!length(posibles)) stop("No hay tasa Treasury verificable anterior al spot")
itasa <- posibles[which.max(fecha_tasa[posibles])]
r1 <- as.numeric(tasas[["1 Yr"]][itasa])/100
r2 <- as.numeric(tasas[["2 Yr"]][itasa])/100
vencimiento <- as.Date("2028-01-21")
T_op <- as.numeric(vencimiento-fecha_valor)/365.25
if (T_op < 1 || T_op > 2) stop("El vencimiento no está entre 12 y 24 meses")
r_op <- r1 + (r2-r1)*max(0,min(1,T_op-1))
r_sharpe <- r2

# Rendimiento por dividendos anual prospectivo: últimos 12 meses conocidos.
q <- sapply(tickers, function(s) {
  z <- div[div$ticker==s & div$fecha_ex_dividendo<=fecha_valor &
           div$fecha_ex_dividendo>fecha_valor-365,]
  sum(z$dividendo_usd_por_accion)/unname(S0[s])
})
if (any(!is.finite(q) | q<0)) stop("Dividend yield inválido")
info <- data.frame(ticker=tickers, empresa=c("Cisco Systems","KLA Corporation","Amgen"),
                   sector=c("Redes y software","Equipos de semiconductores","Biotecnología"),
                   mercado=c("NASDAQ","NASDAQ","NASDAQ"),
                   spot_usd=as.numeric(S0), yield_dividendo=q,
                   hora_cotizacion_ET=spot$trade_time_et,
                   captura_UTC=spot$retrieved_utc)
tabla(info,"Acciones, spot sin ajuste de dividendos y captura",digits=4)
Acciones, spot sin ajuste de dividendos y captura
ticker empresa sector mercado spot_usd yield_dividendo hora_cotizacion_ET captura_UTC
CSCO CSCO Cisco Systems Redes y software NASDAQ 106.16 0.0156 2026-09-23T12:08:47-0400 2026-09-23T16:08:51Z
KLAC KLAC KLA Corporation Equipos de semiconductores NASDAQ 186.01 0.0045 2026-09-23T12:08:48-0400 2026-09-23T16:08:51Z
AMGN AMGN Amgen Biotecnología NASDAQ 404.95 0.0245 2026-09-23T12:08:05-0400 2026-09-23T16:08:51Z

La tasa Treasury de 2026-09-22 es 4.43% a un año y 4.71% a dos años. Para la opción de 1.328 años se usa interpolación lineal: 4.522% anual. El Sharpe a dos años toma la tasa Treasury 2y como referencia anualizada (4.71%). El dividend yield es una aproximación con efectivo de los últimos 12 meses dividido por el spot; los dividendos futuros no están garantizados.

3 Portafolio media-varianza

# Búsqueda de cartera larga con 3 activos y mínimo 15% en cada uno.
paso <- 0.005
grid <- expand.grid(w1=seq(.15,.70,by=paso),w2=seq(.15,.70,by=paso))
grid$w3 <- 1-grid$w1-grid$w2
grid <- grid[grid$w3>=.15-1e-9 & grid$w3<=.70+1e-9,]
W <- as.matrix(grid)
colnames(W) <- tickers
rend_grid <- drop(W%*%mu_anual)
vol_grid <- sqrt(rowSums((W%*%cov_anual)*W))
sharpe_grid <- (rend_grid-r_sharpe)/vol_grid
elegido <- which.max(sharpe_grid)
w <- W[elegido,]
mu_cartera <- rend_grid[elegido]
vol_cartera <- vol_grid[elegido]
sharpe_cartera <- sharpe_grid[elegido]
rc_abs <- as.numeric(w*(cov_anual%*%w)/vol_cartera)
names(rc_abs) <- tickers
rc_pct <- rc_abs/vol_cartera
estadisticas <- data.frame(ticker=tickers,peso=w,retorno_anual=mu_anual,
                           volatilidad_anual=vol_anual,
                           contribucion_volatilidad=rc_abs,
                           porcentaje_riesgo=rc_pct)
tabla(estadisticas,"Selección por Sharpe histórico sujeto al mínimo 15%",digits=4)
Selección por Sharpe histórico sujeto al mínimo 15%
ticker peso retorno_anual volatilidad_anual contribucion_volatilidad porcentaje_riesgo
CSCO CSCO 0.220 0.2045 0.2590 0.0341 0.1386
KLAC KLAC 0.385 0.4635 0.4664 0.1584 0.6430
AMGN AMGN 0.395 0.1851 0.2475 0.0538 0.2184

La cartera seleccionada tiene retorno histórico anualizado 29.66%, volatilidad anualizada 24.63% y Sharpe 1.013. La optimización es una comparación retrospectiva, no una garantía de esos retornos futuros. La mayor contribución a volatilidad corresponde a KLAC. La tabla usa contribución \(w_i(\Sigma w)_i/\sigma_p\), cuya suma es \(\sigma_p\).

plot(vol_grid*100,rend_grid*100,pch=16,cex=.35,col=rgb(.2,.4,.7,.2),
     xlab="Volatilidad anual (%)",ylab="Retorno histórico anual (%)")
points(vol_cartera*100,mu_cartera*100,pch=19,cex=1.5,col="firebrick")
legend("topleft",legend="Portafolio elegido",pch=19,col="firebrick",bty="n")
Frontera alcanzable en la grilla y cartera elegida

Frontera alcanzable en la grilla y cartera elegida

par(mar=c(4,4,2,2))
image(seq_len(3),seq_len(3),t(corr[3:1,]),axes=FALSE,
      col=colorRampPalette(c("#b2182b","white","#2166ac"))(100),zlim=c(-1,1),
      xlab="",ylab="")
axis(1,1:3,tickers)
axis(2,1:3,rev(tickers))
for (i in 1:3) for (j in 1:3)
  text(i,4-j,labels=sprintf("%.2f",corr[i,j]),cex=1.2)
Correlacion de retornos diarios totales

Correlacion de retornos diarios totales

4 Modelo GARCH y simulación de escenarios

historico10$fecha <- as.Date(historico10$fecha)
if (!all(tickers %in% unique(historico10$ticker))) stop("Falta historia 10y")
garch_tab <- lapply(tickers,function(s) {
  z <- historico10[historico10$ticker==s,]
  z <- z[order(z$fecha),]
  z <- z[z$fecha>=fecha_valor-3653 & z$fecha<fecha_valor,]
  if (nrow(z)<2400 || any(!is.finite(z$adj_close) | z$adj_close<=0))
    stop("Historia insuficiente para GARCH de ",s)
  ret <- diff(log(z$adj_close))*100
  spec <- rugarch::ugarchspec(
    variance.model=list(model="sGARCH",garchOrder=c(1,1)),
    mean.model=list(armaOrder=c(0,0),include.mean=TRUE),
    distribution.model="std")
  fit <- rugarch::ugarchfit(spec,data=ret,solver="hybrid",solver.control=list(trace=0))
  if (rugarch::convergence(fit)!=0) stop("GARCH no convergió para ",s)
  sig_dia <- as.numeric(tail(rugarch::sigma(fit),1))/100
  data.frame(ticker=s,desde=min(z$fecha),hasta=max(z$fecha),
             cierres=nrow(z),sigma_diaria=sig_dia,
             sigma_anual=sig_dia*sqrt(252))
})
garch_tab <- do.call(rbind,garch_tab)
sigma_d <- setNames(garch_tab$sigma_diaria,garch_tab$ticker)[tickers]
tabla(garch_tab,"GARCH(1,1) sobre cierre ajustado de ~10 años",digits=4)
GARCH(1,1) sobre cierre ajustado de ~10 años
ticker desde hasta cierres sigma_diaria sigma_anual
CSCO 2016-09-22 2026-09-21 2512 0.0184 0.2921
KLAC 2016-09-22 2026-09-21 2512 0.0349 0.5539
AMGN 2016-09-22 2026-09-21 2512 0.0226 0.3594

La volatilidad usada en el MBG es la desviación condicional del último día estimada con GARCH(1,1) sobre aproximadamente diez años de cierres ajustados. Se mantiene fija durante la simulación; por ello el modelo no predice una trayectoria futura de volatilidad GARCH. La deriva proviene de la historia del CSV desde octubre de 2021. La correlación entre innovaciones proviene de esos retornos diarios. Ni GARCH ni MBG incorporan saltos o cambios estructurales.

5 Opciones: evidencia y selección

stopifnot(all(c("ticker","type","strike","bid","ask","volume","open_interest",
                "implied_volatility","expiration","retrieved_utc") %in% names(opciones)))
opciones$expiration <- as.Date(opciones$expiration)
opciones <- opciones[opciones$ticker %in% tickers &
                     opciones$expiration==vencimiento,]
for (nm in c("strike","bid","ask","volume","open_interest","implied_volatility"))
  opciones[[nm]] <- as.numeric(opciones[[nm]])
opciones <- opciones[is.finite(opciones$strike) & opciones$strike>0 &
                       is.finite(opciones$bid) & opciones$bid>0 &
                       is.finite(opciones$ask) & opciones$ask>=opciones$bid &
                       is.finite(opciones$open_interest) & opciones$open_interest>0 &
                       is.finite(opciones$implied_volatility) &
                       opciones$implied_volatility>0,]
opciones$mid <- (opciones$bid+opciones$ask)/2
opciones$spread_rel <- (opciones$ask-opciones$bid)/opciones$mid
opciones$moneyness <- opciones$strike/S0[opciones$ticker]
if (!all(c("call","put") %in% opciones$type)) stop("Faltan calls o puts")

candidatos <- function(s,tipo,objetivo,minimo,maximo) {
  z <- opciones[opciones$ticker==s & opciones$type==tipo &
                 opciones$moneyness>=minimo & opciones$moneyness<=maximo,]
  if (nrow(z)<3) stop("Menos de 3 strikes válidos para ",s," ",tipo)
  z$score <- abs(z$moneyness-objetivo) + .12*z$spread_rel +
             .03/log1p(z$open_interest+1) +
             .015/log1p(pmax(z$volume,0)+2)
  z <- z[order(z$score),]
  # Tres strikes distintos: uno seleccionado y dos alternativas reales.
  z <- z[!duplicated(z$strike),]
  if (nrow(z)<3) stop("Menos de 3 strikes distintos para ",s," ",tipo)
  z[1:3,]
}
puts3 <- do.call(rbind,lapply(tickers,function(s) candidatos(s,"put",.95,.78,1.08)))
calls3 <- do.call(rbind,lapply(tickers,function(s) candidatos(s,"call",1.10,.98,1.30)))
puts <- puts3[!duplicated(puts3$ticker),]
calls <- calls3[!duplicated(calls3$ticker),]
puts <- puts[match(tickers,puts$ticker),]
calls <- calls[match(tickers,calls$ticker),]
riesgo_mayor <- names(which.max(rc_abs))
put_inferior <- opciones[opciones$ticker==riesgo_mayor & opciones$type=="put" &
                           opciones$strike<puts$strike[puts$ticker==riesgo_mayor] &
                           opciones$moneyness>=.65,]
if (!nrow(put_inferior)) stop("No hay put inferior para bear put spread")
put_inferior$score <- abs(put_inferior$moneyness-.82)+.12*put_inferior$spread_rel+
                      .03/log1p(put_inferior$open_interest+1)
put_inferior <- put_inferior[which.min(put_inferior$score),]

evidencia <- rbind(puts3,calls3)
evidencia$seleccion <- ave(seq_len(nrow(evidencia)),
                          interaction(evidencia$ticker,evidencia$type),
                          FUN=function(i) seq_along(i)==1)
mostrar <- evidencia[,c("ticker","type","strike","bid","ask","spread_rel",
                        "volume","open_interest","implied_volatility","moneyness","seleccion")]
tabla(mostrar,"Tres strikes candidatos de put y tres de call por acción",digits=4)
Tres strikes candidatos de put y tres de call por acción
ticker type strike bid ask spread_rel volume open_interest implied_volatility moneyness seleccion
70 CSCO put 100.0 12.00 12.40 0.0328 45 3127 0.3242 0.9420 1
69 CSCO put 97.5 11.15 11.25 0.0089 3 191 0.3263 0.9184 0
71 CSCO put 105.0 14.55 14.80 0.0170 3 172 0.3182 0.9891 0
409 KLAC put 176.0 35.60 38.80 0.0860 5 111 0.5120 0.9462 1
408 KLAC put 175.0 35.10 38.20 0.0846 2 110 0.5124 0.9408 0
407 KLAC put 174.0 34.70 37.90 0.0882 8 117 0.5152 0.9354 0
629 AMGN put 390.0 39.50 42.90 0.0825 2 27 0.2743 0.9631 1
628 AMGN put 380.0 35.00 39.25 0.1145 1 71 0.2820 0.9384 0
630 AMGN put 400.0 44.25 47.80 0.0771 2 65 0.2720 0.9878 0
29 CSCO call 115.0 15.75 16.05 0.0189 11 1329 0.3997 1.0833 1
30 CSCO call 120.0 13.70 14.30 0.0429 2 7221 0.3973 1.1304 0
28 CSCO call 110.0 17.50 18.05 0.0309 62 1455 0.4038 1.0362 0
173 KLAC call 205.0 41.00 48.20 0.1614 2 63 0.6075 1.1021 1
172 KLAC call 204.0 41.30 48.60 0.1624 1 34 0.6079 1.0967 0
174 KLAC call 206.0 40.60 47.90 0.1650 6 36 0.6070 1.1075 0
585 AMGN call 450.0 43.25 46.00 0.0616 50 212 0.3387 1.1112 1
584 AMGN call 440.0 46.60 49.95 0.0694 1 61 0.3421 1.0866 0
583 AMGN call 430.0 50.10 53.50 0.0656 5 77 0.3422 1.0619 0

La fecha común es 2028-01-21, a 1.33 años de la valoración. Se compra al ask y se vende al bid, evitando presentar el mid como precio ejecutable. El puntaje combina cercanía de strike al objetivo (put ~95% del spot, call ~110%), spread relativo, interés abierto y volumen; no se elige la opción solo por IV. Put OTM si \(K/S_0<1\), call OTM si \(K/S_0>1\). El tamaño pequeño de algunas filas exige verificar profundidad real antes de negociar. Hora de captura UTC en archivo: 2026-09-23T16:08:53Z, 2026-09-23T16:08:54Z, 2026-09-23T16:08:55Z.

seleccion_tab <- data.frame(ticker=tickers,
                            put_K=puts$strike,put_ask=puts$ask,put_IV=puts$implied_volatility,
                            call_K=calls$strike,call_bid=calls$bid,call_IV=calls$implied_volatility)
tabla(seleccion_tab,"Contratos elegidos, todos con vencimiento común",digits=3)
Contratos elegidos, todos con vencimiento común
ticker put_K put_ask put_IV call_K call_bid call_IV
CSCO 100 12.4 0.324 115 15.75 0.400
KLAC 176 38.8 0.512 205 41.00 0.608
AMGN 390 42.9 0.274 450 43.25 0.339

6 Valoración binomial europea y americana

binomial <- function(S,K,T,r,q,sigma,tipo=c("put","call"),americano=FALSE,n=250) {
  tipo <- match.arg(tipo)
  dt <- T/n
  u <- exp(sigma*sqrt(dt)); d <- 1/u
  prob <- (exp((r-q)*dt)-d)/(u-d)
  if (!is.finite(prob) || prob<0 || prob>1)
    stop(sprintf("Probabilidad CRR inválida (S=%.4f K=%.4f T=%.4f r=%.4f q=%.4f sigma=%.4f)",
                 S,K,T,r,q,sigma))
  signo <- if (tipo=="call") 1 else -1
  j <- 0:n
  V <- pmax(signo*(S*u^j*d^(n-j)-K),0)
  for (i in (n-1):0) {
    V <- exp(-r*dt)*(prob*V[-1]+(1-prob)*V[-length(V)])
    if (americano) {
      j <- 0:i
      V <- pmax(V,pmax(signo*(S*u^j*d^(i-j)-K),0))
    }
  }
  V[1]
}
valuar_fila <- function(z) {
  s <- z$ticker
  data.frame(ticker=s,tipo=z$type,strike=z$strike,
             mercado_compra_ask=z$ask,mercado_venta_bid=z$bid,
             europea=binomial(S0[s],z$strike,T_op,r_op,q[s],
                               z$implied_volatility,z$type,FALSE),
             americana=binomial(S0[s],z$strike,T_op,r_op,q[s],
                                 z$implied_volatility,z$type,TRUE))
}
teoricas <- do.call(rbind,lapply(seq_len(nrow(rbind(puts,calls,put_inferior))),
                            function(i) valuar_fila(rbind(puts,calls,put_inferior)[i,])))
tabla(teoricas,"Binomial CRR: referencia europea y americana (250 pasos)",digits=3)
Binomial CRR: referencia europea y americana (250 pasos)
ticker tipo strike mercado_compra_ask mercado_venta_bid europea americana
CSCO CSCO put 100 12.40 12.00 10.291 10.652
KLAC KLAC put 176 38.80 35.60 31.793 32.766
AMGN AMGN put 390 42.90 39.50 36.302 37.475
CSCO1 CSCO call 115 16.05 15.75 17.261 17.262
KLAC1 KLAC call 205 48.20 41.00 47.717 47.717
AMGN1 AMGN call 450 46.00 43.25 48.427 48.447
KLAC2 KLAC put 154 27.40 25.00 22.198 22.784

Ambos árboles usan el mismo spot, strike, plazo, tasa interpolada, volatilidad implícita y dividend yield. La opción cotizada estadounidense suele ser americana; la europea es solo un benchmark. El valor teórico no sustituye al bid/ask. En el collar, las calls vendidas pueden ser asignadas anticipadamente, sobre todo cerca de un ex-dividendo; el seguimiento trimestral de abajo usa valoración europea como aproximación y no modela esa asignación.

7 Diseño de coberturas y costo

put_ask <- setNames(puts$ask,puts$ticker)[tickers]
call_bid <- setNames(calls$bid,calls$ticker)[tickers]
if (anyNA(put_ask) || anyNA(call_bid) || anyNA(w) || anyNA(S0))
  stop("Valores NA al dimensionar; puts=",paste(put_ask,collapse=","),
       " calls=",paste(call_bid,collapse=",")," w=",paste(w,collapse=","),
       " S0=",paste(S0,collapse=","))
overlay_frac <- .25
calcular_tamano <- function(frac) {
  n <- floor(capital*frac*w/S0)
  contratos <- round(.85*n/100)
  contratos_extra <- round(overlay_frac*contratos[riesgo_mayor])
  prima_put <- sum(contratos*100*put_ask)
  prima_extra <- unname(contratos_extra*100*(put_ask[riesgo_mayor]-put_inferior$bid))
  c(costo_acciones=unname(sum(n*S0)),prima_put=unname(prima_put),
    prima_extra=prima_extra,total=unname(sum(n*S0)+prima_put+prima_extra))
}
lo <- 0; hi <- 1
for (i in 1:45) {
  med <- (lo+hi)/2
  valor_med <- calcular_tamano(med)["total"]
  if (!is.finite(unname(valor_med))) stop("Total no finito en fracción ",med,
                                          ": ",paste(calcular_tamano(med),collapse=","))
  if (unname(valor_med)<=capital) lo<-med else hi<-med
}
shares <- floor(capital*lo*w/S0)
contracts <- round(.85*shares/100)
names(contracts) <- tickers
contracts_extra <- round(overlay_frac*contracts[riesgo_mayor])
if (contracts_extra<1) stop("No se pudo dimensionar el overlay")
costo_acciones <- sum(shares*S0)
prima_put_activo <- contracts*100*put_ask
credito_call_activo <- contracts*100*call_bid
prima_A <- sum(prima_put_activo)
prima_B <- prima_A-sum(credito_call_activo)
prima_extra <- contracts_extra*100*(put_ask[riesgo_mayor]-put_inferior$bid)
prima_C <- prima_A+prima_extra
efectivo_base <- capital-costo_acciones
if (efectivo_base<prima_C) stop("El capital no alcanza para acciones y coberturas")
tamano <- data.frame(ticker=tickers,peso_objetivo=w,acciones=shares,
                     contratos_base=contracts,acciones_cubiertas=100*contracts,
                     cobertura_efectiva=100*contracts/shares,
                     costo_acciones=shares*S0,prima_put=prima_put_activo,
                     credito_call=credito_call_activo)
tabla(tamano,"Dimensionamiento sin financiación ni apalancamiento",digits=3)
Dimensionamiento sin financiación ni apalancamiento
ticker peso_objetivo acciones contratos_base acciones_cubiertas cobertura_efectiva costo_acciones prima_put credito_call
CSCO CSCO 0.220 18315 156 15600 0.852 1944320 193440 245700
KLAC KLAC 0.385 18293 155 15500 0.847 3402681 601400 635500
AMGN AMGN 0.395 8621 73 7300 0.847 3491074 313170 315725

Las acciones y contratos se redondean a enteros; cada contrato representa 100 acciones. El capital financia conjuntamente acciones y la alternativa más costosa (C), sin asumir deuda al Treasury. El remanente se mantiene como efectivo sin intereses. La base A compra puts sobre aproximadamente 85% de cada posición; B usa la misma cantidad de puts y calls vendidas. Una put protectora 1:1 sobre acciones cubiertas no neutraliza la delta del portafolio. Estrategia C agrega a A un bear put spread sobre KLAC en 39 contratos: compra la put superior \(K=176\) y vende la put inferior \(K=154\). Esto aumenta protección en caídas moderadas de la acción que más domina el riesgo, pero limita el beneficio incremental por debajo del strike inferior.

costos <- data.frame(estrategia=c("A: put protectora","B: collar","C: A + bear put spread"),
                     costo_neto_usd=c(prima_A,prima_B,prima_C),
                     porcentaje_capital=100*c(prima_A,prima_B,prima_C)/capital)
tabla(costos,"Costo inicial neto: compras a ask, ventas a bid",digits=3)
Costo inicial neto: compras a ask, ventas a bid
estrategia costo_neto_usd porcentaje_capital
A: put protectora 1108010 11.080
B: collar -88915 -0.889
C: A + bear put spread 1161830 11.618
rc_pos <- pmax(rc_abs,0)
if (sum(rc_pos)==0) stop("Contribuciones de riesgo no positivas")
presupuestos <- data.frame(ticker=tickers,costo_real_A=as.numeric(prima_put_activo),
                           valor_invertido=prima_A*as.numeric(w),
                           contribucion_riesgo=prima_A*rc_pos/sum(rc_pos))
tabla(presupuestos,"Dos reglas indicativas de distribución del presupuesto de puts",digits=2)
Dos reglas indicativas de distribución del presupuesto de puts
ticker costo_real_A valor_invertido contribucion_riesgo
CSCO CSCO 193440 243762.2 153560.2
KLAC KLAC 601400 426583.8 712500.2
AMGN AMGN 313170 437664.0 241949.6

Se comparan los presupuestos proporcionales al valor invertido y a la contribución positiva a riesgo. Se recomienda priorizar por contribución a riesgo para el presupuesto discrecional; la protección base de 85% no se altera para forzar un reparto teórico. El costo real por activo está determinado por el número entero de contratos y por el ask observado. El mayor presupuesto base recae en KLAC. El costo total de A es 11.08% del portafolio; para B es -0.89% y para C 11.62%.

8 Seguimiento trimestral, riesgo y comparación

nsim <- 10000L
pasos <- 504L                 # 252 días bursátiles por año, 8 trimestres.
qsteps <- seq(0L,pasos,by=63L)
exp_step <- min(pasos,max(1L,round(T_op*252)))
if (exp_step<=qsteps[1]) stop("Vencimiento inválido")
R <- corr
eigenmin <- min(eigen(R,symmetric=TRUE,only.values=TRUE)$values)
if (eigenmin<=0) stop("Correlación no definida positiva")
U <- chol(R)
S <- matrix(rep(S0,each=nsim),nrow=nsim,dimnames=list(NULL,tickers))
cashdiv <- numeric(nsim)
S_q <- vector("list",length(qsteps)); cash_q <- vector("list",length(qsteps))
S_q[[1]] <- S; cash_q[[1]] <- cashdiv
S_exp <- NULL; cash_exp <- NULL
drift <- mu_log_diaria-q/252
for (dia in seq_len(pasos)) {
  Z <- matrix(rnorm(nsim*3),nrow=nsim)%*%U
  cashdiv <- cashdiv+drop(S%*%(shares*q/252))
  S <- S*exp(sweep(Z,2,sigma_d,"*")+
             matrix(rep(drift,each=nsim),nrow=nsim))
  if (dia==exp_step) { S_exp<-S; cash_exp<-cashdiv }
  iq <- match(dia,qsteps)
  if (!is.na(iq)) { S_q[[iq]]<-S; cash_q[[iq]]<-cashdiv }
}
stopifnot(!is.null(S_exp),all(vapply(S_q,function(z)!is.null(z),logical(1))))

bs <- function(S,K,T,r,q,iv,tipo) {
  signo <- if (tipo=="call") 1 else -1
  if (T<=0) return(pmax(signo*(S-K),0))
  d1 <- (log(S/K)+(r-q+iv^2/2)*T)/(iv*sqrt(T))
  d2 <- d1-iv*sqrt(T)
  if (tipo=="call") S*exp(-q*T)*pnorm(d1)-K*exp(-r*T)*pnorm(d2)
  else K*exp(-r*T)*pnorm(-d2)-S*exp(-q*T)*pnorm(-d1)
}
valor_op <- function(contrato, ncontratos, estado_S, step) {
  if (ncontratos==0) return(rep(0,nrow(estado_S)))
  j <- match(contrato$ticker,tickers)
  if (step>=exp_step) {
    sig <- if (contrato$type=="call") 1 else -1
    # Liquidación congelada en la trayectoria EXACTA del vencimiento.
    v <- pmax(sig*(S_exp[,j]-contrato$strike),0)
  } else {
    v <- bs(estado_S[,j],contrato$strike,(exp_step-step)/252,
            r_op,q[j],contrato$implied_volatility,contrato$type)
  }
  ncontratos*100*v
}
valores_estado <- function(estado_S,estado_cash,step) {
  base <- drop(estado_S%*%shares)+efectivo_base+estado_cash
  vp <- numeric(nrow(estado_S)); vc <- vp
  for (j in seq_along(tickers)) {
    vp <- vp+valor_op(puts[j,],contracts[j],estado_S,step)
    vc <- vc+valor_op(calls[j,],contracts[j],estado_S,step)
  }
  vextra <- valor_op(puts[puts$ticker==riesgo_mayor,],contracts_extra,
                     estado_S,step)-
            valor_op(put_inferior,contracts_extra,estado_S,step)
  cbind(Sin_cobertura=base,A_protective_put=base-prima_A+vp,
        B_collar=base-prima_B+vp-vc,
        C_A_mas_spread=base-prima_C+vp+vextra)
}
estados <- lapply(seq_along(qsteps),function(i) valores_estado(S_q[[i]],cash_q[[i]],qsteps[i]))
estado_exp <- valores_estado(S_exp,cash_exp,exp_step)

El MBG genera 10,000 trayectorias diarias correlacionadas con semilla fija. Los dividendos esperados se abonan como efectivo diariamente según el yield estimado (aproximación, no calendario exacto). Antes del vencimiento, las opciones se revaloran con Black–Scholes y la IV capturada; en el día 335 se liquidan por su valor intrínseco simulado. Ese efectivo permanece hasta el final. No se supone renovación de opciones después del 2028-01-21, porque hoy no conocemos las cotizaciones futuras; desde entonces la cartera de acciones vuelve a estar expuesta al riesgo de mercado. La simulación usa una IV fija para el marcado, no una superficie de volatilidad futura.

resumen_q <- do.call(rbind,lapply(seq_along(qsteps),function(i) {
  v <- estados[[i]]
  if (i==1) v[,] <- capital # Valor de transacción inicial; luego rige mark-to-market.
  data.frame(trimestre=i-1,meses=(i-1)*3,estrategia=colnames(v),
             media=colMeans(v),p05=apply(v,2,quantile,.05),
             mediana=apply(v,2,median),p95=apply(v,2,quantile,.95))
}))
tabla(resumen_q,"Valor USD en t=0 y ocho cierres trimestrales (escenarios)",digits=0)
Valor USD en t=0 y ocho cierres trimestrales (escenarios)
trimestre meses estrategia media p05 mediana p95
Sin_cobertura 0 0 Sin_cobertura 10000000 10000000 10000000 10000000
A_protective_put 0 0 A_protective_put 10000000 10000000 10000000 10000000
B_collar 0 0 B_collar 10000000 10000000 10000000 10000000
C_A_mas_spread 0 0 C_A_mas_spread 10000000 10000000 10000000 10000000
Sin_cobertura1 1 3 Sin_cobertura 10740967 8523818 10640764 13440516
A_protective_put1 1 3 A_protective_put 10429226 8838822 10268721 12651034
B_collar1 1 3 B_collar 9944869 9389205 9917431 10618632
C_A_mas_spread1 1 3 C_A_mas_spread 10408205 8838806 10249676 12612407
Sin_cobertura2 2 6 Sin_cobertura 11577077 8251681 11260566 15902613
A_protective_put2 2 6 A_protective_put 11151936 8675967 10736141 14974116
B_collar2 2 6 B_collar 10268305 9426321 10204218 11307298
C_A_mas_spread2 2 6 C_A_mas_spread 11126159 8681430 10710061 14925211
Sin_cobertura3 3 9 Sin_cobertura 12494354 8229951 11942029 18617076
A_protective_put3 3 9 A_protective_put 11969979 8646149 11277623 17635420
B_collar3 3 9 B_collar 10607922 9519612 10527231 11972394
C_A_mas_spread3 3 9 C_A_mas_spread 11940033 8656153 11240349 17581750
Sin_cobertura4 4 12 Sin_cobertura 13499019 8290538 12727811 21380606
A_protective_put4 4 12 A_protective_put 12880184 8641426 11939761 20331414
B_collar4 4 12 B_collar 10961935 9636301 10878622 12573972
C_A_mas_spread4 4 12 C_A_mas_spread 12846058 8644692 11899033 20277595
Sin_cobertura5 5 15 Sin_cobertura 14593239 8317405 13543009 24344961
A_protective_put5 5 15 A_protective_put 13900504 8570641 12688433 23270678
B_collar5 5 15 B_collar 11325177 9718038 11252170 13161982
C_A_mas_spread5 5 15 C_A_mas_spread 13863120 8581228 12639555 23216858
Sin_cobertura6 6 18 Sin_cobertura 15828928 8390334 14419302 28017861
A_protective_put6 6 18 A_protective_put 15115602 8585501 13517471 26933898
B_collar6 6 18 B_collar 12298845 9115647 11721770 17361833
C_A_mas_spread6 6 18 C_A_mas_spread 15077187 8589737 13478525 26880078
Sin_cobertura7 7 21 Sin_cobertura 17173223 8471562 15345712 32408911
A_protective_put7 7 21 A_protective_put 16459897 8523258 14499809 31312191
B_collar7 7 21 B_collar 13643140 8584045 12615309 22363702
C_A_mas_spread7 7 21 C_A_mas_spread 16421481 8520766 14455344 31258371
Sin_cobertura8 8 24 Sin_cobertura 18659616 8652422 16381908 36731121
A_protective_put8 8 24 A_protective_put 17946289 8623988 15552611 35765540
B_collar8 8 24 B_collar 15129533 8384183 13534942 27263055
C_A_mas_spread8 8 24 C_A_mas_spread 17907874 8620572 15510168 35711720

En \(t=0\) se muestra el capital aportado. En los cortes siguientes, la valoración teórica de opciones puede diferir del precio de ejecución por el spread. Los percentiles 5–95% son un rango de escenarios del modelo, no una predicción cierta.

z <- resumen_q[resumen_q$estrategia=="Sin_cobertura",]
plot(z$meses,z$mediana/1e6,type="n",ylim=range(c(z$p05,z$p95))/1e6,
     xlab="Meses",ylab="Valor del portafolio (millones USD)")
polygon(c(z$meses,rev(z$meses)),c(z$p05,rev(z$p95))/1e6,
        col=adjustcolor("steelblue",alpha.f=.25),border=NA)
lines(z$meses,z$mediana/1e6,lwd=2,col="steelblue4")
abline(h=capital/1e6,lty=2,col="gray40")
legend("topleft",legend=c("Mediana","P5–P95","Capital inicial"),
       col=c("steelblue4",adjustcolor("steelblue",alpha.f=.3),"gray40"),
       lty=c(1,1,2),lwd=c(2,8,1),bty="n")
Bandas percentiles del portafolio sin cobertura durante 2 anos

Bandas percentiles del portafolio sin cobertura durante 2 anos

var_f <- function(v,p) -as.numeric(quantile(v-capital,p,names=FALSE))
riesgo <- do.call(rbind,lapply(list(
  list(etapa="Primer trimestre",v=estados[[2]]),
  list(etapa=paste0("Vencimiento ",vencimiento),v=estado_exp)),function(z) {
  data.frame(etapa=z$etapa,estrategia=colnames(z$v),
             VaR95=apply(z$v,2,var_f,.05),VaR99=apply(z$v,2,var_f,.01))
}))
tabla(riesgo,"VaR = −cuantil del P&L simulado, USD",digits=0)
VaR = −cuantil del P&L simulado, USD
etapa estrategia VaR95 VaR99
Sin_cobertura Primer trimestre Sin_cobertura 1476182 2169258
A_protective_put Primer trimestre A_protective_put 1161178 1547611
B_collar Primer trimestre B_collar 610795 771574
C_A_mas_spread Primer trimestre C_A_mas_spread 1161194 1540548
Sin_cobertura1 Vencimiento 2028-01-21 Sin_cobertura 1651093 3237874
A_protective_put1 Vencimiento 2028-01-21 A_protective_put 1441876 1806665
B_collar1 Vencimiento 2028-01-21 B_collar 262539 609740
C_A_mas_spread1 Vencimiento 2028-01-21 C_A_mas_spread 1443092 1774969

El VaR es \(-Q_{0.05}(P\&L)\) o \(-Q_{0.01}(P\&L)\), sin forzarlo artificialmente a cero. Una cifra negativa significaría que hasta el cuantil adverso aún muestra ganancia. La reducción de riesgo debe medirse después de pagar la prima: una cobertura cara puede disminuir VaR99 pero empeorar VaR95 o incluso ambos.

reduccion <- do.call(rbind,lapply(unique(riesgo$etapa),function(e) {
  z <- riesgo[riesgo$etapa==e,]
  data.frame(etapa=e,estrategia=z$estrategia,
             reduccion_VaR95=z$VaR95[1]-z$VaR95,
             reduccion_VaR99=z$VaR99[1]-z$VaR99)
}))
tabla(reduccion,"Reducción positiva (o aumento negativo) de VaR frente a la misma cartera",digits=0)
Reducción positiva (o aumento negativo) de VaR frente a la misma cartera
etapa estrategia reduccion_VaR95 reduccion_VaR99
Primer trimestre Sin_cobertura 0 0
Primer trimestre A_protective_put 315004 621647
Primer trimestre B_collar 865387 1397684
Primer trimestre C_A_mas_spread 314988 628710
Vencimiento 2028-01-21 Sin_cobertura 0 0
Vencimiento 2028-01-21 A_protective_put 209217 1431209
Vencimiento 2028-01-21 B_collar 1388553 2628134
Vencimiento 2028-01-21 C_A_mas_spread 208000 1462905
pnl <- (estado_exp[,"Sin_cobertura"]-capital)/1e6
hist(pnl,breaks=55,freq=FALSE,col="gray85",border="white",
     xlab="P&L al vencimiento (millones USD)",main="")
v95 <- -var_f(estado_exp[,"Sin_cobertura"],.05)/1e6
v99 <- -var_f(estado_exp[,"Sin_cobertura"],.01)/1e6
abline(v=c(v95,v99),col=c("darkorange3","firebrick"),lwd=2,lty=c(2,3))
legend("topright",c("Cuantil 5% (VaR95)","Cuantil 1% (VaR99)"),
       col=c("darkorange3","firebrick"),lty=c(2,3),lwd=2,bty="n")
Distribucion del P&L al vencimiento y VaR del portafolio sin cobertura

Distribucion del P&L al vencimiento y VaR del portafolio sin cobertura

shock <- seq(-.55,.55,length.out=250)
spot_esc <- outer(1+shock,S0)
stock_pnl <- drop((spot_esc-matrix(rep(S0,each=length(shock)),nrow=length(shock)))%*%shares)
put_pay <- sapply(seq_along(tickers),function(j)
  100*contracts[j]*pmax(puts$strike[j]-spot_esc[,j],0))
call_pay <- sapply(seq_along(tickers),function(j)
  100*contracts[j]*pmax(spot_esc[,j]-calls$strike[j],0))
jriesgo <- match(riesgo_mayor,tickers)
extra_pay <- 100*contracts_extra*(
  pmax(puts$strike[jriesgo]-spot_esc[,jriesgo],0)-
  pmax(put_inferior$strike-spot_esc[,jriesgo],0))
curvas <- cbind(A=stock_pnl+rowSums(put_pay)-prima_A,
                B=stock_pnl+rowSums(put_pay)-rowSums(call_pay)-prima_B,
                C=stock_pnl+rowSums(put_pay)+extra_pay-prima_C)
matplot(100*shock,curvas/1e6,type="l",lty=1,lwd=2,
        col=c("steelblue4","darkorange3","darkgreen"),
        xlab="Shock simultáneo en las tres acciones (%)",
        ylab="P&L (millones USD)")
abline(h=0,lty=2,col="gray50")
legend("topleft",c("A: put","B: collar","C: A + spread"),
       col=c("steelblue4","darkorange3","darkgreen"),lty=1,lwd=2,bty="n")
P&L de las tres estrategias ante shocks proporcionales al vencimiento

P&L de las tres estrategias ante shocks proporcionales al vencimiento

El payoff anterior es un corte determinista al vencimiento con un mismo shock porcentual en las tres acciones; excluye dividendos futuros. La distribución de VaR sí usa las diez mil trayectorias correlacionadas y el abono de dividendos supuesto.

mat_med <- reshape(resumen_q[,c("meses","estrategia","mediana")],
                   idvar="meses",timevar="estrategia",direction="wide")
cols <- c("gray30","steelblue4","darkorange3","darkgreen")
matplot(mat_med$meses,as.matrix(mat_med[,-1])/1e6,type="b",pch=16,lty=1,
        col=cols,xlab="Meses",ylab="Mediana del valor (millones USD)")
legend("topleft",c("Sin cobertura","A: put","B: collar","C: A + spread"),
       col=cols,lty=1,pch=16,bty="n")
Mediana trimestral cubierta y no cubierta

Mediana trimestral cubierta y no cubierta

9 Conclusiones, limitaciones y recomendación

El portafolio de USD 10 millones mantiene las tres acciones con pesos no menores a 15% y reserva capital suficiente para las primas. La decisión de cobertura debe sopesar VaR95, VaR99 y costo neto, no elegir automáticamente el VaR más bajo. La opción A mantiene el alza de la parte cubierta; B reduce prima pero limita alza; C refuerza la protección del activo con mayor contribución a riesgo, a costo adicional. La selección ejecutable depende de que el bid/ask, profundidad, multiplicador de contrato y disponibilidad sigan vigentes al enviar la orden.

Limitaciones principales: fecha de spot posterior al último cierre del CSV; cotizaciones Yahoo potencialmente diferidas; rendimiento futuro y correlación estimados de muestra histórica; rendimiento de dividendos constante; volatilidad GARCH inicial fija en el MBG; volatilidad implícita fija para la revaloración trimestral; ejercicio anticipado, costos de corretaje, impuestos, slippage y financiación no modelados. El modelo no rellena datos de opciones ausentes con cifras inventadas. Si la cobertura termina antes de 24 meses, los trimestres siguientes no representan protección activa renovada.

10 Anexo de reproducibilidad

Archivos locales de entrada: precios_CSCO.csv, precios_KLAC.csv, precios_AMGN.csv, dividendos_corregidos.csv, spot_2026-09-23.csv, opciones_2028-01-21.csv, historico_yahoo_10y.csv, treasury_2026.csv. Versión de R: R version 4.6.1 (2026-06-24 ucrt). Semilla: 20260923. Captura de cotizaciones: 2026-09-23T16:08:51Z UTC. Para repetir el HTML en RStudio, abra este .Rmd y use Knit. Para publicar en RPubs, primero inspeccione la versión HTML y luego use Publish → RPubs desde RStudio con su propia sesión; publicar revela el contenido del informe, por lo que no se debe insertar ninguna credencial en el código.