No contamos con el archivo de trayectorias de A.4, así que simulamos un camino log-normal para el spot (una trayectoria anual por año de la cobertura), calibrado a los dos datos que sí reporta A.3: el valor más probable a 12 meses y el rango 5–95 %. De ahí se despeja una deriva y una volatilidad anuales; para la t de Student usamos los mismos parámetros con 5 grados de libertad (supuesto propio, señalado porque reemplaza un insumo que debería venir de A.4).
S0 <- 3184.00 # TRM de hoy (B.1)
mediana_12m <- 3326 # A.3: valor más probable a 12 meses
p5_12m <- 2795 # A.3: percentil 5
p95_12m <- 3953 # A.3: percentil 95
mu <- log(mediana_12m / S0)
z95 <- qnorm(0.95)
sigma <- mean(c(log(p95_12m/mediana_12m)/z95, log(p5_12m/mediana_12m)/(-z95)))
r_COP <- 0.12; r_USD <- 0.0418 # tasas de B.1
factor_paridad <- (1 + r_COP) / (1 + r_USD)
calibracion <- data.frame(
Parámetro = c("Deriva anual (μ)", "Volatilidad anual (σ)", "Factor de paridad (B.1)", "Grados de libertad, t-Student"),
Valor = c(sprintf("%.2f %%", mu*100), sprintf("%.2f %%", sigma*100), sprintf("%.6f", factor_paridad), "5")
)
kable(calibracion) %>% kable_styling(bootstrap_options = c("striped","hover"), full_width = TRUE)| Parámetro | Valor |
|---|---|
| Deriva anual (μ) | 4.36 % |
| Volatilidad anual (σ) | 10.54 % |
| Factor de paridad (B.1) | 1.075062 |
| Grados de libertad, t-Student | 5 |
Cada forward se cotiza con la paridad de B.1 sobre el spot simulado al inicio de su año, usando el mismo diferencial de tasas de hoy por no contar con curvas futuras de IBR ni de T-bill (mismo límite señalado en el alcance del cálculo de B.1). El nominal de cada forward es el 75 % de la cuota anual en dólares del crédito.
principal_usd <- 113065
r_nom_anual <- 0.074
n_cuotas <- 40
r_trim <- r_nom_anual/4
PMT <- principal_usd * r_trim / (1 - (1+r_trim)^(-n_cuotas))
cuota_anual <- PMT*4
nominal_cubierto <- 0.75*cuota_anual
cat(sprintf("Cuota trimestral (sistema francés): %.2f USD\n", PMT))## Cuota trimestral (sistema francés): 4025.22 USD
cat(sprintf("Cuota anual: %.2f USD | Nominal cubierto por forward (75%%): %.2f USD\n", cuota_anual, nominal_cubierto))## Cuota anual: 16100.89 USD | Nominal cubierto por forward (75%): 12075.66 USD
Se simulan 2e+05 trayectorias anuales (incrementos i.i.d. en el logaritmo del spot) para los 10 años del crédito, bajo dos distribuciones de innovaciones: normal y t de Student (5 g.l., estandarizada a varianza unitaria). Los cuatro forwards de la estrategia se pactan al inicio de los años 7 a 10 (6 a 9 años transcurridos) y vencen un año después.
set.seed(123)
N <- 2e5; nu <- 5; years <- 10
sim_spot <- function(dist = c("normal","t")) {
dist <- match.arg(dist)
Z <- if (dist == "normal") matrix(rnorm(N*years), N, years) else
matrix(rt(N*years, df = nu)*sqrt((nu-2)/nu), N, years)
logret <- mu + sigma*Z
S0 * exp(t(apply(logret, 1, cumsum)))
}
set.seed(123); S_n <- sim_spot("normal")
set.seed(123); S_t <- sim_spot("t")
forwards_idx <- 1:4
pactar_year <- 5 + forwards_idx
venc_year <- 6 + forwards_idx
calc_metrics <- function(S) {
prot_all <- dep_all <- fail_hedge_all <- fail_nohedge_all <- logical(0)
costo_sin <- costo_con <- numeric(N)
tabla <- data.frame()
for (i in forwards_idx) {
sp_pactar <- S[, pactar_year[i]]; sp_venc <- S[, venc_year[i]]
fwd <- sp_pactar * factor_paridad
prot <- sp_venc > fwd
prot_all <- c(prot_all, prot)
dep_all <- c(dep_all, (sp_venc/sp_pactar - 1) > 0.25)
fail_hedge_all <- c(fail_hedge_all, sp_venc > 1.20*fwd)
fail_nohedge_all <- c(fail_nohedge_all, sp_venc > 1.05*fwd)
costo_sin <- costo_sin + cuota_anual*sp_venc
costo_con <- costo_con + cuota_anual*(0.75*fwd + 0.25*sp_venc)
tabla <- rbind(tabla, data.frame(Forward = i, Año = 6+i,
fwd_mediana = median(fwd), spot_mediana = median(sp_venc),
prot_pct = mean(prot)*100,
fwd_p5 = quantile(fwd,.05), fwd_p95 = quantile(fwd,.95)))
}
list(tabla = tabla,
prot_pct = mean(prot_all)*100, dep_extrema_pct = mean(dep_all)*100,
fail_hedge_pct = mean(fail_hedge_all)*100, fail_nohedge_pct = mean(fail_nohedge_all)*100,
costo_sin_mean = mean(costo_sin)/1e6, costo_sin_sd = sd(costo_sin)/1e6,
costo_con_mean = mean(costo_con)/1e6, costo_con_sd = sd(costo_con)/1e6)
}
m_n <- calc_metrics(S_n)
m_t <- calc_metrics(S_t)tabla_fwd <- m_n$tabla
kable(data.frame(
`Forward (año)` = sprintf("%d (año %d)", tabla_fwd$Forward, tabla_fwd$Año),
`Forward pactado (mediana)` = sprintf("%.2f", tabla_fwd$fwd_mediana),
`Spot al vencimiento (mediana)` = sprintf("%.2f", tabla_fwd$spot_mediana),
`Probabilidad de protección` = sprintf("%.1f %%", tabla_fwd$prot_pct),
check.names = FALSE
)) %>% kable_styling(bootstrap_options = c("striped","hover"), full_width = TRUE)| Forward (año) | Forward pactado (mediana) | Spot al vencimiento (mediana) | Probabilidad de protección |
|---|---|---|---|
| 1 (año 7) | 4443.58 | 4318.99 | 39.3 % |
| 2 (año 8) | 4643.18 | 4510.14 | 39.0 % |
| 3 (año 9) | 4848.69 | 4713.09 | 39.4 % |
| 4 (año 10) | 5066.87 | 4923.72 | 39.2 % |
Los cuatro forwards dan resultados muy parecidos entre sí porque la deriva y la volatilidad anual no cambian de un año a otro; solo cambia el nivel acumulado del tipo de cambio.
Con el forward ya pactado (fijo, calculado sobre el spot mediano del año 6), el resultado de la cobertura depende de dónde termine el spot un año después. Mostramos tres posibilidades para ese spot de vencimiento, en los percentiles 5, 50 y 95 de su distribución:
sp_pactar1 <- S_n[, pactar_year[1]]
pactar_mediana <- median(sp_pactar1)
fwd_fijo <- pactar_mediana * factor_paridad
esc <- data.frame()
for (p in c(0.05, 0.50, 0.95)) {
ven <- pactar_mediana * exp(mu + sigma*qnorm(p))
esc <- rbind(esc, data.frame(p = p, pactar = pactar_mediana, forward = fwd_fijo, venc = ven, resultado = ven - fwd_fijo))
}
etiquetas <- c("Apreciación del peso (p5 del retorno anual)", "Central (mediana)", "Depreciación fuerte (p95 del retorno anual)")
lecturas <- ifelse(esc$resultado > 50, "protección", ifelse(esc$resultado < -50, "costo de oportunidad", "costo leve"))
kable(data.frame(
Escenario = etiquetas,
`Spot al pactar` = sprintf("%.2f", esc$pactar),
`Forward pactado` = sprintf("%.2f", esc$forward),
`Spot al vencimiento` = sprintf("%.2f", esc$venc),
`Resultado del forward` = sprintf("%+.2f (%s)", esc$resultado, lecturas),
check.names = FALSE
)) %>% kable_styling(bootstrap_options = c("striped","hover"), full_width = TRUE)| Escenario | Spot al pactar | Forward pactado | Spot al vencimiento | Resultado del forward |
|---|---|---|---|---|
| Apreciación del peso (p5 del retorno anual) | 4133.32 | 4443.58 | 3630.58 | -813.00 (costo de oportunidad) |
| Central (mediana) | 4133.32 | 4443.58 | 4317.66 | -125.92 (costo de oportunidad) |
| Depreciación fuerte (p95 del retorno anual) | 4133.32 | 4443.58 | 5134.77 | +691.19 (protección) |
El forward queda fijo en el momento de pactarlo; lo que varía es el spot al vencimiento. Si el peso se deprecia con fuerza (percentil 95 del retorno anual), el forward protege y ahorra dinero frente al spot; si el peso se aprecia (percentil 5), el forward termina costando más que si no se hubiera cubierto — ese es el costo de oportunidad de la cobertura.
df_band <- data.frame(
año = tabla_fwd$Año,
forward = tabla_fwd$fwd_mediana,
spot_p5 = tabla_fwd$fwd_p5 * NA, # placeholder, replaced below
spot_mediana = tabla_fwd$spot_mediana
)
# bandas p5/p95 del spot al vencimiento para cada forward
bandas <- sapply(forwards_idx, function(i) {
sp <- S_n[, venc_year[i]]
quantile(sp, c(.05,.95))
})
df_band$spot_p5 <- bandas[1,]
df_band$spot_p95 <- bandas[2,]
if (has_plotly) {
plot_ly(df_band, x = ~año) %>%
add_ribbons(ymin = ~spot_p5, ymax = ~spot_p95, name = "Rango 5–95 % del spot",
fillcolor = "rgba(24,188,156,0.25)", line = list(color = "transparent")) %>%
add_lines(y = ~spot_mediana, name = "Spot mediana al vencimiento", line = list(color = "#18BC9C")) %>%
add_lines(y = ~forward, name = "Forward pactado", line = list(color = "#2C3E50", dash = "dash")) %>%
layout(title = "Forward pactado vs. rango simulado del spot, años 7 a 10",
xaxis = list(title = "Año del crédito"), yaxis = list(title = "COP/USD"))
} else {
matplot(df_band$año, df_band[,c("forward","spot_mediana")], type = "l", lty = 1,
col = c("#2C3E50","#18BC9C"), xlab = "Año del crédito", ylab = "COP/USD",
main = "Forward pactado vs. spot mediana")
legend("topleft", legend = c("Forward pactado","Spot mediana"), col = c("#2C3E50","#18BC9C"), lty = 1)
}Ponderando 75 % al forward pactado y 25 % al spot en cada vencimiento, comparamos el costo total de los cuatro años cubiertos con y sin cobertura:
kable(data.frame(
Escenario = c("Sin cobertura (normal)", "Con cobertura 75 % años 7–10 (normal)",
"Sin cobertura (t-Student)", "Con cobertura 75 % años 7–10 (t-Student)"),
Media = sprintf("%.1f M", c(m_n$costo_sin_mean, m_n$costo_con_mean, m_t$costo_sin_mean, m_t$costo_con_mean)),
`Desv. estándar` = sprintf("%.1f M", c(m_n$costo_sin_sd, m_n$costo_con_sd, m_t$costo_sin_sd, m_t$costo_con_sd)),
check.names = FALSE
)) %>% kable_styling(bootstrap_options = c("striped","hover"), full_width = TRUE)| Escenario | Media | Desv. estándar |
|---|---|---|
| Sin cobertura (normal) | 311.6 M | 94.4 M |
| Con cobertura 75 % años 7–10 (normal) | 317.2 M | 90.9 M |
| Sin cobertura (t-Student) | 312.0 M | 97.1 M |
| Con cobertura 75 % años 7–10 (t-Student) | 317.5 M | 93.6 M |
Cifras en millones de pesos. La cobertura sube el costo esperado apenas un poco (la prima de B.1 aplicada sobre el 75 % de los años 7 a 10) y reduce levemente la desviación estándar: protege el tramo de un año de cada forward, no la incertidumbre acumulada de los seis años previos que determina en qué nivel se pacta cada contrato.
kable(data.frame(
`Tipo de escenario` = c("Protección (spot > forward)", "Costo de oportunidad (spot ≤ forward)",
"Pérdida cambiaria extrema sin cobertura (depreciación > 25 % en el año)",
"Fracaso de la cobertura (costo cubierto > forward + 5 %)",
"Para referencia: ese mismo exceso sin cobertura"),
Normal = sprintf("%.1f %%", c(m_n$prot_pct, 100-m_n$prot_pct, m_n$dep_extrema_pct, m_n$fail_hedge_pct, m_n$fail_nohedge_pct)),
`t de Student (5 g.l.)` = sprintf("%.1f %%", c(m_t$prot_pct, 100-m_t$prot_pct, m_t$dep_extrema_pct, m_t$fail_hedge_pct, m_t$fail_nohedge_pct)),
check.names = FALSE
)) %>% kable_styling(bootstrap_options = c("striped","hover"), full_width = TRUE)| Tipo de escenario | Normal | t de Student (5 g.l.) |
|---|---|---|
| Protección (spot > forward) | 39.2 % | 37.0 % |
| Costo de oportunidad (spot ≤ forward) | 60.8 % | 63.0 % |
| Pérdida cambiaria extrema sin cobertura (depreciación > 25 % en el año) | 4.4 % | 3.9 % |
| Fracaso de la cobertura (costo cubierto > forward + 5 %) | 2.3 % | 2.5 % |
| Para referencia: ese mismo exceso sin cobertura | 23.1 % | 19.3 % |
Pérdida del derivado frente a fracaso de la cobertura. El forward paga menos que el spot en la mayoría de los escenarios, y eso es simplemente el costo de la prima de B.1: ocurre siempre que el peso se deprecia menos de lo que implica la paridad. Pero el presupuesto solo se sale seriamente de control (el costo cubierto supera al forward en más de 5 %) en una fracción muy pequeña de los casos, frente a una fracción varias veces mayor sin cobertura: la cobertura no evita pagar la prima casi siempre, pero sí recorta la cola de depreciaciones grandes, que es el verdadero fracaso presupuestal. Esa es la función del forward, coherente con lo señalado en B.1: es un costo de financiación, no una apuesta direccional. Bajo t de Student la cola es más pesada en el costo cubierto, aunque las probabilidades centrales cambian poco.
Fuentes: Banco de la República (Datos Abiertos Colombia); U.S. Treasury vía Forbes Advisor; A.3 (percentiles del modelo de la Parte A); Hull, J. C. (11.ª ed.), Options, Futures, and Other Derivatives; diapositivas de clase “Forwards”. Simulación Monte Carlo propia (2e+05 trayectorias), calibrada a A.3 — señalada como supuesto porque sustituye el archivo de trayectorias de A.4.