1 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 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 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
fecha_escenario dias_habiles_restantes tau
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 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
fecha_escenario dias_habiles_restantes tau r
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 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
fecha_escenario dias_habiles_restantes tau r sigma
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 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 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 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
fecha_escenario dias_habiles_restantes S Call Put
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 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 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
fecha_escenario dias_habiles_restantes tau r sigma label
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 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
escenario fecha_escenario S delta gamma theta vega rho
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 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 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
ticker fecha_inicial spot strike vencimiento_elegido iv_final
NVDA 2026-04-17 198.35 200 2027-03-19 0.4689

14 14. Análisis financiero

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