1. Introducción
En este informe se analiza la convexidad de una opción sobre la
acción NVIDIA (NVDA), empresa perteneciente al índice
S&P 500. El ejercicio se desarrolla con vencimiento cercano a un
año, año comercial de 252 días, escenarios mensuales del subyacente,
interpolación de la tasa libre de riesgo mediante CME Term
SOFR, y evaluación de las griegas en tres vencimientos
relevantes.
2. Descarga de precios
y selección del vencimiento
# Precio spot y serie histórica
ticker <- "NVDA"
start_hist <- Sys.Date() - years(2)
getSymbols(ticker, src = "yahoo", from = start_hist, auto.assign = TRUE)
## [1] "NVDA"
px <- get(ticker)
S0 <- as.numeric(last(Cl(px)))
fecha_inicial <- Sys.Date()
S0
## [1] 198.35
fecha_inicial
## [1] "2026-04-17"
# Cadena de opciones desde Yahoo vía quantmod
opc_all <- getOptionChain(ticker, Exp = NULL)
# Parseo robusto de fechas de vencimiento
parse_exp <- function(x) {
suppressWarnings(parse_date_time(
x,
orders = c("ymd", "Ymd", "b d Y", "B d Y", "b d, Y", "B d, Y", "m/d/Y")
))
}
exp_names <- names(opc_all)
exp_dates <- as.Date(parse_exp(exp_names))
# Objetivo: vencimiento cercano a 1 año
target_date <- fecha_inicial + 365
idx_exp <- which.min(abs(as.numeric(exp_dates - target_date)))
chosen_exp_name <- exp_names[idx_exp]
chosen_exp_date <- exp_dates[idx_exp]
chosen_exp_name
## [1] "mar..19.2027"
chosen_exp_date
## [1] "2027-03-19"
# Calls y puts del vencimiento elegido
calls_raw <- opc_all[[chosen_exp_name]]$calls
puts_raw <- opc_all[[chosen_exp_name]]$puts
# Selección de strike cercano a S0 (riesgo moderado)
call_sel <- calls_raw %>%
mutate(diff_spot = abs(Strike - S0)) %>%
arrange(diff_spot) %>%
slice(1)
put_sel <- puts_raw %>%
mutate(diff_spot = abs(Strike - S0)) %>%
arrange(diff_spot) %>%
slice(1)
K <- call_sel$Strike[[1]]
# Volatilidad implícita final
iv_final <- call_sel$IV[[1]]
if (iv_final > 1) iv_final <- iv_final / 100
list(
spot = S0,
strike = K,
vencimiento = chosen_exp_date,
iv_final = iv_final
)
## $spot
## [1] 198.35
##
## $strike
## [1] 200
##
## $vencimiento
## [1] "2027-03-19"
##
## $iv_final
## [1] 0.4689079
3. Año comercial y
vencimientos mensuales
if (!("cal_252" %in% names(bizdays::calendars()))) {
bizdays::create.calendar(
name = "cal_252",
weekdays = c("saturday", "sunday")
)
}
monthly_raw <- seq.Date(from = fecha_inicial, to = chosen_exp_date, by = "month")
if (tail(monthly_raw, 1) != chosen_exp_date) {
monthly_raw <- c(monthly_raw, chosen_exp_date)
}
monthly_dates <- adjust.next(monthly_raw, "cal_252")
monthly_dates <- unique(monthly_dates)
bdays_to_exp <- bizdays(monthly_dates, chosen_exp_date, "cal_252")
tau <- pmax(bdays_to_exp / 252, 1/252)
tabla_vtos <- data.frame(
fecha_escenario = monthly_dates,
dias_habiles_restantes = bdays_to_exp,
tau = tau
)
kable(tabla_vtos, digits = 4, caption = "Vencimientos mensuales en año comercial 252")
Vencimientos mensuales en año comercial 252
| 2026-04-17 |
240 |
0.9524 |
| 2026-05-18 |
219 |
0.8690 |
| 2026-06-17 |
197 |
0.7817 |
| 2026-07-17 |
175 |
0.6944 |
| 2026-08-17 |
154 |
0.6111 |
| 2026-09-17 |
131 |
0.5198 |
| 2026-10-19 |
109 |
0.4325 |
| 2026-11-17 |
88 |
0.3492 |
| 2026-12-17 |
66 |
0.2619 |
| 2027-01-18 |
44 |
0.1746 |
| 2027-02-17 |
22 |
0.0873 |
| 2027-03-17 |
2 |
0.0079 |
| 2027-03-19 |
0 |
0.0040 |
4. Tasa libre de
riesgo: interpolación CME Term SOFR
# Tasas CME Term SOFR (hardcodeadas para facilitar ejecución)
# 1M = 3.66101%, 3M = 3.67523%, 6M = 3.68767%, 12M = 3.69088%
sofr_nodes_months <- c(1, 3, 6, 12)
sofr_nodes_rates <- c(0.0366101, 0.0367523, 0.0368767, 0.0369088)
tau_months <- pmax(tau * 12, 1/12)
r_interp <- approx(
x = sofr_nodes_months,
y = sofr_nodes_rates,
xout = tau_months,
method = "linear",
rule = 2
)$y
tabla_vtos <- tabla_vtos %>%
mutate(r = r_interp)
kable(tabla_vtos, digits = 6, caption = "Tasas interpoladas desde CME Term SOFR")
Tasas interpoladas desde CME Term SOFR
| 2026-04-17 |
240 |
0.952381 |
0.036906 |
| 2026-05-18 |
219 |
0.869048 |
0.036900 |
| 2026-06-17 |
197 |
0.781746 |
0.036895 |
| 2026-07-17 |
175 |
0.694444 |
0.036889 |
| 2026-08-17 |
154 |
0.611111 |
0.036884 |
| 2026-09-17 |
131 |
0.519841 |
0.036878 |
| 2026-10-19 |
109 |
0.432540 |
0.036843 |
| 2026-11-17 |
88 |
0.349206 |
0.036802 |
| 2026-12-17 |
66 |
0.261905 |
0.036758 |
| 2027-01-18 |
44 |
0.174603 |
0.036688 |
| 2027-02-17 |
22 |
0.087302 |
0.036613 |
| 2027-03-17 |
2 |
0.007937 |
0.036610 |
| 2027-03-19 |
0 |
0.003968 |
0.036610 |
5. Estructura temporal
de volatilidad
# Se toma la IV final como referencia y se aplica un decremento lineal de 2%
# hacia la fecha inicial, tal como solicita el ejercicio.
n_steps <- length(monthly_dates)
sigma_seq <- seq(
from = iv_final - 0.02 * (n_steps - 1),
to = iv_final,
length.out = n_steps
)
sigma_seq <- pmax(sigma_seq, 0.05)
tabla_vtos <- tabla_vtos %>%
mutate(sigma = sigma_seq)
kable(tabla_vtos, digits = 4, caption = "Estructura temporal de volatilidad implícita ajustada")
Estructura temporal de volatilidad implícita ajustada
| 2026-04-17 |
240 |
0.9524 |
0.0369 |
0.2289 |
| 2026-05-18 |
219 |
0.8690 |
0.0369 |
0.2489 |
| 2026-06-17 |
197 |
0.7817 |
0.0369 |
0.2689 |
| 2026-07-17 |
175 |
0.6944 |
0.0369 |
0.2889 |
| 2026-08-17 |
154 |
0.6111 |
0.0369 |
0.3089 |
| 2026-09-17 |
131 |
0.5198 |
0.0369 |
0.3289 |
| 2026-10-19 |
109 |
0.4325 |
0.0368 |
0.3489 |
| 2026-11-17 |
88 |
0.3492 |
0.0368 |
0.3689 |
| 2026-12-17 |
66 |
0.2619 |
0.0368 |
0.3889 |
| 2027-01-18 |
44 |
0.1746 |
0.0367 |
0.4089 |
| 2027-02-17 |
22 |
0.0873 |
0.0366 |
0.4289 |
| 2027-03-17 |
2 |
0.0079 |
0.0366 |
0.4489 |
| 2027-03-19 |
0 |
0.0040 |
0.0366 |
0.4689 |
6. Simulación del
subyacente a 1 año y construcción del rango de precios
ret <- dailyReturn(Cl(px), type = "log")
ret <- na.omit(ret)
mu_hat <- mean(ret) * 252
set.seed(123)
n_sims <- 100000
ST_sim <- S0 * exp((mu_hat - 0.5 * iv_final^2) * 1 + iv_final * rnorm(n_sims))
S_min <- floor(as.numeric(quantile(ST_sim, 0.025)) / 2) * 2
S_max <- ceiling(as.numeric(quantile(ST_sim, 0.975)) / 2) * 2
S_grid <- seq(S_min, S_max, by = 2)
summary(ST_sim)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 39.43 199.90 273.83 305.61 375.66 2077.81
S_min
## [1] 108
S_max
## [1] 686
head(S_grid)
## [1] 108 110 112 114 116 118
tail(S_grid)
## [1] 676 678 680 682 684 686
7. Funciones
Black-Scholes y griegas
bs_d1 <- function(S, K, r, sigma, tau) {
(log(S / K) + (r + 0.5 * sigma^2) * tau) / (sigma * sqrt(tau))
}
bs_d2 <- function(S, K, r, sigma, tau) {
bs_d1(S, K, r, sigma, tau) - sigma * sqrt(tau)
}
bs_call <- function(S, K, r, sigma, tau) {
d1 <- bs_d1(S, K, r, sigma, tau)
d2 <- bs_d2(S, K, r, sigma, tau)
S * pnorm(d1) - K * exp(-r * tau) * pnorm(d2)
}
bs_put <- function(S, K, r, sigma, tau) {
d1 <- bs_d1(S, K, r, sigma, tau)
d2 <- bs_d2(S, K, r, sigma, tau)
K * exp(-r * tau) * pnorm(-d2) - S * pnorm(-d1)
}
greeks_call <- function(S, K, r, sigma, tau) {
d1 <- bs_d1(S, K, r, sigma, tau)
d2 <- bs_d2(S, K, r, sigma, tau)
delta <- pnorm(d1)
gamma <- dnorm(d1) / (S * sigma * sqrt(tau))
theta <- -(S * dnorm(d1) * sigma) / (2 * sqrt(tau)) - r * K * exp(-r * tau) * pnorm(d2)
vega <- S * dnorm(d1) * sqrt(tau)
rho <- K * tau * exp(-r * tau) * pnorm(d2)
data.frame(delta, gamma, theta, vega, rho)
}
greeks_put <- function(S, K, r, sigma, tau) {
d1 <- bs_d1(S, K, r, sigma, tau)
d2 <- bs_d2(S, K, r, sigma, tau)
delta <- pnorm(d1) - 1
gamma <- dnorm(d1) / (S * sigma * sqrt(tau))
theta <- -(S * dnorm(d1) * sigma) / (2 * sqrt(tau)) + r * K * exp(-r * tau) * pnorm(-d2)
vega <- S * dnorm(d1) * sqrt(tau)
rho <- -K * tau * exp(-r * tau) * pnorm(-d2)
data.frame(delta, gamma, theta, vega, rho)
}
8. Tablas de convexidad
para cada vencimiento
convexidad_tbl <- map_dfr(seq_len(nrow(tabla_vtos)), function(i) {
tau_i <- tabla_vtos$tau[i]
r_i <- tabla_vtos$r[i]
sigma_i <- tabla_vtos$sigma[i]
fecha_i <- tabla_vtos$fecha_escenario[i]
data.frame(
fecha_escenario = fecha_i,
dias_habiles_restantes = tabla_vtos$dias_habiles_restantes[i],
S = S_grid,
Call = bs_call(S_grid, K, r_i, sigma_i, tau_i),
Put = bs_put(S_grid, K, r_i, sigma_i, tau_i)
)
})
kable(head(convexidad_tbl, 15), digits = 4, caption = "Vista parcial de la tabla de convexidad")
Vista parcial de la tabla de convexidad
| 2026-04-17 |
240 |
108 |
0.0468 |
85.1393 |
| 2026-04-17 |
240 |
110 |
0.0612 |
83.1537 |
| 2026-04-17 |
240 |
112 |
0.0792 |
81.1716 |
| 2026-04-17 |
240 |
114 |
0.1014 |
79.1939 |
| 2026-04-17 |
240 |
116 |
0.1287 |
77.2212 |
| 2026-04-17 |
240 |
118 |
0.1618 |
75.2543 |
| 2026-04-17 |
240 |
120 |
0.2017 |
73.2942 |
| 2026-04-17 |
240 |
122 |
0.2494 |
71.3419 |
| 2026-04-17 |
240 |
124 |
0.3060 |
69.3984 |
| 2026-04-17 |
240 |
126 |
0.3726 |
67.4650 |
| 2026-04-17 |
240 |
128 |
0.4504 |
65.5428 |
| 2026-04-17 |
240 |
130 |
0.5407 |
63.6332 |
| 2026-04-17 |
240 |
132 |
0.6449 |
61.7374 |
| 2026-04-17 |
240 |
134 |
0.7644 |
59.8568 |
| 2026-04-17 |
240 |
136 |
0.9006 |
57.9930 |
9. Figuras dinámicas de
convexidad
convexidad_tbl <- convexidad_tbl %>%
mutate(
fecha_txt = as.character(fecha_escenario),
dias_txt = as.character(dias_habiles_restantes)
)
fig_call <- plot_ly(
data = convexidad_tbl,
x = ~S,
y = ~Call,
color = ~fecha_txt,
type = "scatter",
mode = "lines",
text = ~paste(
"Fecha:", fecha_txt,
"<br>Días hábiles:", dias_txt,
"<br>S:", round(S, 2),
"<br>Call:", round(Call, 4)
),
hoverinfo = "text"
) %>%
layout(
title = "Convexidad de la Call sobre NVDA",
xaxis = list(title = "Precio del subyacente"),
yaxis = list(title = "Precio de la call"),
legend = list(title = list(text = "Fecha escenario"))
)
fig_put <- plot_ly(
data = convexidad_tbl,
x = ~S,
y = ~Put,
color = ~fecha_txt,
type = "scatter",
mode = "lines",
text = ~paste(
"Fecha:", fecha_txt,
"<br>Días hábiles:", dias_txt,
"<br>S:", round(S, 2),
"<br>Put:", round(Put, 4)
),
hoverinfo = "text"
) %>%
layout(
title = "Convexidad de la Put sobre NVDA (complemento)",
xaxis = list(title = "Precio del subyacente"),
yaxis = list(title = "Precio de la put"),
legend = list(title = list(text = "Fecha escenario"))
)
subplot(fig_call, fig_put, nrows = 2, shareX = TRUE, titleY = TRUE)
10. Selección del
primer, sexto y último vencimiento
idx_first <- 1
idx_sixth <- min(6, nrow(tabla_vtos))
idx_last <- nrow(tabla_vtos)
idx_focus <- c(idx_first, idx_sixth, idx_last)
tabla_focus <- tabla_vtos[idx_focus, ] %>%
mutate(label = c("Primer", "Sexto", "Último"))
kable(tabla_focus, digits = 4, caption = "Vencimientos usados para griegas")
Vencimientos usados para griegas
| 1 |
2026-04-17 |
240 |
0.9524 |
0.0369 |
0.2289 |
Primer |
| 6 |
2026-09-17 |
131 |
0.5198 |
0.0369 |
0.3289 |
Sexto |
| 13 |
2027-03-19 |
0 |
0.0040 |
0.0366 |
0.4689 |
Último |
11. Tablas de
griegas
greeks_tbl <- map_dfr(seq_len(nrow(tabla_focus)), function(j) {
tau_j <- tabla_focus$tau[j]
r_j <- tabla_focus$r[j]
sigma_j <- tabla_focus$sigma[j]
label_j <- tabla_focus$label[j]
fecha_j <- tabla_focus$fecha_escenario[j]
gc <- greeks_call(S_grid, K, r_j, sigma_j, tau_j)
data.frame(
escenario = label_j,
fecha_escenario = fecha_j,
S = S_grid,
delta = gc$delta,
gamma = gc$gamma,
theta = gc$theta,
vega = gc$vega,
rho = gc$rho
)
})
kable(head(greeks_tbl, 15), digits = 6, caption = "Vista parcial de la tabla de griegas")
Vista parcial de la tabla de griegas
| Primer |
2026-04-17 |
108 |
0.006400 |
0.000746 |
-0.251810 |
1.897449 |
0.613674 |
| Primer |
2026-04-17 |
110 |
0.008039 |
0.000896 |
-0.314359 |
2.363053 |
0.783848 |
| Primer |
2026-04-17 |
112 |
0.009996 |
0.001065 |
-0.388363 |
2.912111 |
0.990825 |
| Primer |
2026-04-17 |
114 |
0.012312 |
0.001254 |
-0.475033 |
3.552921 |
1.240079 |
| Primer |
2026-04-17 |
116 |
0.015026 |
0.001464 |
-0.575548 |
4.293440 |
1.537414 |
| Primer |
2026-04-17 |
118 |
0.018180 |
0.001694 |
-0.691035 |
5.141080 |
1.888912 |
| Primer |
2026-04-17 |
120 |
0.021814 |
0.001944 |
-0.822541 |
6.102515 |
2.300868 |
| Primer |
2026-04-17 |
122 |
0.025968 |
0.002214 |
-0.971003 |
7.183481 |
2.779712 |
| Primer |
2026-04-17 |
124 |
0.030682 |
0.002503 |
-1.137229 |
8.388597 |
3.331933 |
| Primer |
2026-04-17 |
126 |
0.035990 |
0.002809 |
-1.321870 |
9.721200 |
3.963984 |
| Primer |
2026-04-17 |
128 |
0.041927 |
0.003131 |
-1.525400 |
11.183208 |
4.682196 |
| Primer |
2026-04-17 |
130 |
0.048523 |
0.003467 |
-1.748105 |
12.775006 |
5.492684 |
| Primer |
2026-04-17 |
132 |
0.055805 |
0.003816 |
-1.990060 |
14.495365 |
6.401256 |
| Primer |
2026-04-17 |
134 |
0.063794 |
0.004175 |
-2.251128 |
16.341393 |
7.413322 |
| Primer |
2026-04-17 |
136 |
0.072508 |
0.004541 |
-2.530950 |
18.308518 |
8.533810 |
12. Figuras dinámicas
de griegas
plot_greek <- function(df, greek_name, y_title) {
df <- df %>%
mutate(
escenario_txt = as.character(escenario),
fecha_txt = as.character(fecha_escenario)
)
plot_ly(
data = df,
x = ~S,
y = as.formula(paste0("~", greek_name)),
color = ~escenario_txt,
type = "scatter",
mode = "lines",
text = ~paste(
"Escenario:", escenario_txt,
"<br>Fecha:", fecha_txt,
"<br>S:", round(S, 2),
paste0("<br>", y_title, ": ", round(.data[[greek_name]], 6))
),
hoverinfo = "text"
) %>%
layout(
title = paste("Griega", toupper(greek_name), "- Call NVDA"),
xaxis = list(title = "Precio del subyacente"),
yaxis = list(title = y_title)
)
}
13. Resultados
puntuales del contrato seleccionado
resumen_opcion <- data.frame(
ticker = ticker,
fecha_inicial = fecha_inicial,
spot = S0,
strike = K,
vencimiento_elegido = chosen_exp_date,
iv_final = iv_final
)
kable(resumen_opcion, digits = 4, caption = "Resumen del contrato base")
Resumen del contrato base
| NVDA |
2026-04-17 |
198.35 |
200 |
2027-03-19 |
0.4689 |
14. Análisis
financiero
14.1 Convexidad
La call presenta una relación no lineal entre el precio de la opción
y el precio del subyacente. A medida que el vencimiento se acerca, la
curvatura se hace más pronunciada alrededor del strike, lo que refleja
mayor sensibilidad marginal del precio de la opción ante movimientos del
activo.
14.2 Efecto del
tiempo
Las curvas para vencimientos más lejanos son más suaves. Conforme
disminuyen los días hábiles restantes, la convexidad se concentra con
mayor intensidad cerca del strike.
14.3 Griegas
- Delta: aumenta con el precio del subyacente y se
vuelve más abrupta cerca del strike en vencimientos cercanos.
- Gamma: se intensifica en opciones at-the-money
cuando el vencimiento es corto, evidenciando el corazón de la
convexidad.
- Theta: muestra pérdida de valor temporal, más
relevante a medida que se aproxima el vencimiento.
- Vega: es mayor en zonas cercanas al strike y para
vencimientos más largos.
- Rho: recoge la sensibilidad a la tasa libre de
riesgo, con mayor impacto en plazos más amplios.
15. Conclusión
El análisis de NVDA evidencia que la convexidad de una opción call no
es constante en el tiempo ni en el rango de precios del subyacente. La
gamma confirma que la curvatura se intensifica en la región
at-the-money, especialmente cuando el vencimiento se aproxima. La
incorporación de una estructura temporal de volatilidad y la
interpolación de la tasa libre de riesgo con CME Term SOFR permiten una
valoración más consistente con el mercado.