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.
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.
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")
| 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")
| 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)
| 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.
# 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)
| 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
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
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)
| 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.
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)
| 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)
| 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 |
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)
| 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.
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)
| 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)
| 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)
| 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%.
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)
| 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
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)
| 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)
| 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
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
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
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.
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.