formatear_cop <- function(x) {
scales::label_number(big.mark = ".", decimal.mark = ",", prefix = "$", accuracy = 1)(x)
}
formatear_usd <- function(x) {
scales::label_dollar(prefix = "USD ", big.mark = ",", decimal.mark = ".", accuracy = 0.01)(x)
}
formatear_pct <- function(x) {
scales::label_percent(accuracy = 0.01, decimal.mark = ",")(x)
}
formatear_trm <- function(x) {
scales::label_number(big.mark = ".", decimal.mark = ",", accuracy = 0.01)(x)
}
calcular_credito_frances <- function(monto_inicial_usd, tasa_periodica, numero_periodos) {
cuota_usd <- monto_inicial_usd * tasa_periodica / (1 - (1 + tasa_periodica)^(-numero_periodos))
saldo <- monto_inicial_usd
tabla <- vector("list", numero_periodos)
for (periodo in seq_len(numero_periodos)) {
interes_usd <- saldo * tasa_periodica
abono_capital_usd <- cuota_usd - interes_usd
saldo_final_usd <- max(saldo - abono_capital_usd, 0)
tabla[[periodo]] <- tibble::tibble(
periodo = periodo,
cuota_usd = cuota_usd,
interes_usd = interes_usd,
abono_capital_usd = abono_capital_usd,
saldo_inicial_usd = saldo,
saldo_final_usd = saldo_final_usd
)
saldo <- saldo_final_usd
}
dplyr::bind_rows(tabla)
}
obtener_trm_mensual_fred <- function() {
url_fred <- "https://fred.stlouisfed.org/graph/fredgraph.csv?id=COLCCUSMA02STM"
datos <- readr::read_csv(url_fred, show_col_types = FALSE) |>
janitor::clean_names()
names(datos) <- c("fecha", "trm_mensual")
datos <- datos |>
dplyr::mutate(
fecha = suppressWarnings(lubridate::ymd(fecha)),
trm_mensual = suppressWarnings(as.numeric(trm_mensual))
) |>
dplyr::filter(!is.na(fecha), !is.na(trm_mensual)) |>
dplyr::arrange(fecha)
datos
}
obtener_forward_publico_banrep <- function(ruta_archivo, url_descarga) {
if (!file.exists(ruta_archivo)) {
dir.create(dirname(ruta_archivo), recursive = TRUE, showWarnings = FALSE)
download.file(url_descarga, destfile = ruta_archivo, mode = "wb")
}
readxl::read_excel(
ruta_archivo,
sheet = "5. DevaluacionesSector",
skip = 7,
na = c("#N/A", "NA", "")
) |>
janitor::clean_names() |>
dplyr::mutate(fecha = as.Date(fecha))
}
simular_bmg_normal <- function(spot_inicial, media_log_mensual, volatilidad_mensual, meses, simulaciones) {
choques <- matrix(rnorm(meses * simulaciones), nrow = meses, ncol = simulaciones)
rendimientos <- (media_log_mensual - 0.5 * volatilidad_mensual^2) + volatilidad_mensual * choques
trayectorias <- apply(rendimientos, 2, cumsum)
trayectorias <- exp(trayectorias) * spot_inicial
rbind(rep(spot_inicial, simulaciones), trayectorias)
}
simular_bmg_t_student <- function(spot_inicial, media_log_mensual, volatilidad_mensual, meses, simulaciones, grados_libertad) {
choques_crudos <- matrix(rt(meses * simulaciones, df = grados_libertad), nrow = meses, ncol = simulaciones)
choques_ajustados <- choques_crudos / sqrt(grados_libertad / (grados_libertad - 2))
rendimientos <- (media_log_mensual - 0.5 * volatilidad_mensual^2) + volatilidad_mensual * choques_ajustados
trayectorias <- apply(rendimientos, 2, cumsum)
trayectorias <- exp(trayectorias) * spot_inicial
rbind(rep(spot_inicial, simulaciones), trayectorias)
}
resumen_vector <- function(x) {
tibble::tibble(
promedio = mean(x, na.rm = TRUE),
mediana = median(x, na.rm = TRUE),
minimo = min(x, na.rm = TRUE),
p5 = quantile(x, 0.05, na.rm = TRUE),
p95 = quantile(x, 0.95, na.rm = TRUE),
maximo = max(x, na.rm = TRUE)
)
}
obtener_resumen_trayectorias <- function(matriz_trm) {
tibble::tibble(
mes = 0:(nrow(matriz_trm) - 1),
p5 = apply(matriz_trm, 1, quantile, probs = 0.05, na.rm = TRUE),
p50 = apply(matriz_trm, 1, quantile, probs = 0.50, na.rm = TRUE),
p95 = apply(matriz_trm, 1, quantile, probs = 0.95, na.rm = TRUE)
)
}
construir_flujos_cobertura <- function(tabla_credito_usd, matriz_trm, tasa_forward_anual, porcentaje_cobertura, pagos_por_anio) {
meses_pago <- seq(3, nrow(tabla_credito_usd) * 3, by = 3)
trm_en_pagos <- matriz_trm[meses_pago + 1, , drop = FALSE]
cuotas_usd <- tabla_credito_usd$cuota_usd
costos_sin_cobertura <- colSums(trm_en_pagos * cuotas_usd)
tabla_credito_anual <- tabla_credito_usd |>
dplyr::mutate(anio = ceiling(periodo / pagos_por_anio)) |>
dplyr::group_by(anio) |>
dplyr::summarise(cuota_anual_usd = sum(cuota_usd), .groups = "drop")
anios_cubiertos <- 6:9
meses_inicio_forward <- c(60, 72, 84, 96)
forward_por_anio <- purrr::map2_dfc(
meses_inicio_forward,
anios_cubiertos,
function(mes_inicio, anio_actual) {
spot_inicio <- matriz_trm[mes_inicio + 1, ]
tasa_forward <- spot_inicio * (1 + tasa_forward_anual)
tibble::tibble(!!paste0("anio_", anio_actual) := tasa_forward)
}
)
costos_con_cobertura <- costos_sin_cobertura
eventos_protegidos <- list()
for (indice in seq_along(anios_cubiertos)) {
anio_actual <- anios_cubiertos[indice]
periodos_anio <- which(ceiling(tabla_credito_usd$periodo / pagos_por_anio) == anio_actual)
meses_anio <- periodos_anio * 3
spot_promedio_anio <- colMeans(matriz_trm[meses_anio + 1, , drop = FALSE])
forward_anio <- forward_por_anio[[indice]]
cuota_anual_usd <- tabla_credito_anual$cuota_anual_usd[tabla_credito_anual$anio == anio_actual]
costo_sin_cobertura_anio <- cuota_anual_usd * spot_promedio_anio
costo_con_cobertura_anio <- cuota_anual_usd * (
porcentaje_cobertura * forward_anio +
(1 - porcentaje_cobertura) * spot_promedio_anio
)
costos_con_cobertura <- costos_con_cobertura - costo_sin_cobertura_anio + costo_con_cobertura_anio
eventos_protegidos[[indice]] <- tibble::tibble(
anio = anio_actual,
ahorro_cobertura_cop = costo_sin_cobertura_anio - costo_con_cobertura_anio,
protegido = (costo_con_cobertura_anio <= costo_sin_cobertura_anio)
)
}
list(
costos_sin_cobertura = costos_sin_cobertura,
costos_con_cobertura = costos_con_cobertura,
forward_por_anio = forward_por_anio,
eventos_protegidos = dplyr::bind_rows(eventos_protegidos)
)
}
Esta practica desarrolla una estrategia integral para financiar la compra de maquinaria amarilla por COP 350 millones, mediante un credito denominado en dolares, y analizar una cobertura cambiaria con forwards de divisas sobre el 75% de la exposicion, a partir del sexto ano.
La estructura que se adopta es la siguiente:
Supuesto central de trabajo: para el credito en USD se usa una tasa efectiva anual de 9,75%. Para el forward teorico se usan tasas comerciales de referencia de Estados Unidos y Colombia. Para el componente de mercado se emplea la serie publica del mercado forward USD-COP de BanRep/SET-FX para el rango mayor a 180 dias.
datos_trm_mensual <- obtener_trm_mensual_fred() |>
dplyr::filter(fecha >= as.Date(params$fecha_inicio_historia))
datos_forward_publico <- obtener_forward_publico_banrep(
ruta_archivo = params$ruta_forward_banrep,
url_descarga = params$url_forward_banrep
)
resumen_forward_largo <- datos_forward_publico |>
dplyr::filter(stringr::str_to_lower(rango) == "mayor a 180") |>
dplyr::group_by(fecha) |>
dplyr::summarise(
devaluacion_mercado = mean(mercado, na.rm = TRUE),
devaluacion_total = mean(total, na.rm = TRUE),
.groups = "drop"
) |>
dplyr::filter(!is.na(devaluacion_mercado)) |>
dplyr::arrange(fecha)
fecha_forward_referencia <- max(resumen_forward_largo$fecha, na.rm = TRUE)
devaluacion_implicita_mercado <- resumen_forward_largo |>
dplyr::filter(fecha == fecha_forward_referencia) |>
dplyr::pull(devaluacion_mercado)
devaluacion_implicita_total <- resumen_forward_largo |>
dplyr::filter(fecha == fecha_forward_referencia) |>
dplyr::pull(devaluacion_total)
forward_teorico_1_anio <- params$trm_actual *
((1 + params$tasa_comercial_cop_forward) / (1 + params$tasa_comercial_usd_forward))
forward_mercado_1_anio <- params$trm_actual * (1 + devaluacion_implicita_mercado)
supuestos_trabajo <- tibble::tibble(
supuesto = c(
"Valor de la maquinaria en COP",
"TRM spot de referencia",
"TRM esperada a 12 meses",
"Tasa credito USD efectiva anual",
"Tasa comercial USD para paridad forward",
"Tasa comercial COP para paridad forward",
"Cobertura forward",
"Plazo del credito",
"Frecuencia de pago",
"Ultima fecha del archivo publico forward",
"Devaluacion implicita de mercado (>180 dias)",
"Forward teorico a 1 ano",
"Forward de mercado a 1 ano"
),
valor = c(
formatear_cop(params$monto_maquinaria_cop),
paste0(formatear_trm(params$trm_actual), " COP/USD"),
paste0(formatear_trm(params$trm_esperada_12m), " COP/USD"),
formatear_pct(params$tasa_credito_usd_efectiva_anual),
formatear_pct(params$tasa_comercial_usd_forward),
formatear_pct(params$tasa_comercial_cop_forward),
formatear_pct(params$porcentaje_cobertura_forward),
paste(params$plazo_credito_anios, "anos"),
"Trimestral",
as.character(fecha_forward_referencia),
formatear_pct(devaluacion_implicita_mercado),
paste0(formatear_trm(forward_teorico_1_anio), " COP/USD"),
paste0(formatear_trm(forward_mercado_1_anio), " COP/USD")
)
)
knitr::kable(supuestos_trabajo, align = c("l", "r"), caption = "Supuestos principales del ejercicio") |>
kableExtra::kable_styling(full_width = FALSE)
| supuesto | valor |
|---|---|
| Valor de la maquinaria en COP | $350.000.000 |
| TRM spot de referencia | 3.668,89 COP/USD |
| TRM esperada a 12 meses | 3.808,50 COP/USD |
| Tasa credito USD efectiva anual | 9,75% |
| Tasa comercial USD para paridad forward | 6,75% |
| Tasa comercial COP para paridad forward | 9,87% |
| Cobertura forward | 75,00% |
| Plazo del credito | 10 anos |
| Frecuencia de pago | Trimestral |
| Ultima fecha del archivo publico forward | 2025-02-04 |
| Devaluacion implicita de mercado (>180 dias) | 5,14% |
| Forward teorico a 1 ano | 3.776,12 COP/USD |
| Forward de mercado a 1 ano | 3.857,53 COP/USD |
El punto de partida del ejercicio es una TRM de referencia de COP 3.669 por dolar. El escenario base de expectativas a 12 meses se fija en COP 3.808 por dolar, lo que implica una depreciacion esperada cercana a:
depreciacion_esperada_12m <- params$trm_esperada_12m / params$trm_actual - 1
formatear_pct(depreciacion_esperada_12m)
## [1] "3,81%"
comparacion_referentes <- tibble::tibble(
referencia = c("Spot actual", "Encuesta a 12 meses", "Forward teorico 1 ano", "Forward mercado 1 ano"),
valor_cop_usd = c(params$trm_actual, params$trm_esperada_12m, forward_teorico_1_anio, forward_mercado_1_anio)
)
knitr::kable(
comparacion_referentes |>
dplyr::mutate(valor_cop_usd = paste0(formatear_trm(valor_cop_usd), " COP/USD")),
align = c("l", "r"),
caption = "Comparacion entre spot, expectativa y forwards"
) |>
kableExtra::kable_styling(full_width = FALSE)
| referencia | valor_cop_usd |
|---|---|
| Spot actual | 3.668,89 COP/USD |
| Encuesta a 12 meses | 3.808,50 COP/USD |
| Forward teorico 1 ano | 3.776,12 COP/USD |
| Forward mercado 1 ano | 3.857,53 COP/USD |
Para concretar el ejercicio se adopta una simulacion academica de credito comercial en USD referenciado al programa SBA 7(a), que permite financiar maquinaria y equipo en Estados Unidos. Como tasa del ejercicio se usa Prime + 300 pb, equivalente a 9,75% efectiva anual, lo cual deja una base publica y defendible para la simulacion.
valor_maquinaria_usd <- params$monto_maquinaria_cop / params$trm_actual
cuota_inicial_usd <- valor_maquinaria_usd * params$porcentaje_cuota_inicial
monto_financiado_usd <- valor_maquinaria_usd - cuota_inicial_usd
numero_periodos <- params$plazo_credito_anios * params$pagos_por_anio
tasa_trimestral_credito <- (1 + params$tasa_credito_usd_efectiva_anual)^(1 / params$pagos_por_anio) - 1
tabla_credito_usd <- calcular_credito_frances(
monto_inicial_usd = monto_financiado_usd,
tasa_periodica = tasa_trimestral_credito,
numero_periodos = numero_periodos
) |>
dplyr::mutate(
anio = ceiling(periodo / params$pagos_por_anio),
cuota_inicial_usd = cuota_inicial_usd
)
resumen_credito_usd <- tibble::tibble(
concepto = c(
"Valor total de la maquinaria en USD",
"Cuota inicial en USD",
"Monto financiado en USD",
"Tasa trimestral del credito",
"Cuota trimestral fija en USD",
"Pago total del credito en USD"
),
valor = c(
formatear_usd(valor_maquinaria_usd),
formatear_usd(cuota_inicial_usd),
formatear_usd(monto_financiado_usd),
formatear_pct(tasa_trimestral_credito),
formatear_usd(unique(tabla_credito_usd$cuota_usd)),
formatear_usd(sum(tabla_credito_usd$cuota_usd))
)
)
knitr::kable(resumen_credito_usd, align = c("l", "r"), caption = "Resumen del credito en dolares") |>
kableExtra::kable_styling(full_width = FALSE)
| concepto | valor |
|---|---|
| Valor total de la maquinaria en USD | USD 95,396.70 |
| Cuota inicial en USD | USD 9,539.67 |
| Monto financiado en USD | USD 85,857.03 |
| Tasa trimestral del credito | 2,35% |
| Cuota trimestral fija en USD | USD 3,336.17 |
| Pago total del credito en USD | USD 133,446.65 |
tabla_grafica_amortizacion <- tabla_credito_usd |>
dplyr::select(periodo, interes_usd, abono_capital_usd) |>
tidyr::pivot_longer(-periodo, names_to = "componente", values_to = "valor_usd")
ggplot(tabla_grafica_amortizacion, aes(x = periodo, y = valor_usd, color = componente)) +
geom_line(linewidth = 1.1) +
labs(
title = "Comportamiento del credito en USD",
subtitle = "Sistema frances con cuotas trimestrales",
x = "Periodo trimestral",
y = "USD",
color = "Componente"
) +
scale_y_continuous(labels = scales::label_dollar(prefix = "USD ")) +
theme_minimal()
tabla_credito_cop <- tabla_credito_usd |>
dplyr::mutate(
cuota_cop_spot = cuota_usd * params$trm_actual,
cuota_cop_encuesta_12m = cuota_usd * params$trm_esperada_12m,
interes_cop_spot = interes_usd * params$trm_actual,
saldo_final_cop_spot = saldo_final_usd * params$trm_actual
)
resumen_credito_cop <- tibble::tibble(
concepto = c(
"Cuota trimestral en COP con spot actual",
"Cuota trimestral en COP con TRM esperada a 12 meses",
"Diferencia por cuota entre ambos escenarios",
"Monto financiado equivalente en COP"
),
valor = c(
formatear_cop(unique(tabla_credito_cop$cuota_cop_spot)),
formatear_cop(unique(tabla_credito_cop$cuota_cop_encuesta_12m)),
formatear_cop(unique(tabla_credito_cop$cuota_cop_encuesta_12m - tabla_credito_cop$cuota_cop_spot)),
formatear_cop(monto_financiado_usd * params$trm_actual)
)
)
knitr::kable(resumen_credito_cop, align = c("l", "r"), caption = "Credito transformado a pesos") |>
kableExtra::kable_styling(full_width = FALSE)
| concepto | valor |
|---|---|
| Cuota trimestral en COP con spot actual | $12.240.027 |
| Cuota trimestral en COP con TRM esperada a 12 meses | $12.705.789 |
| Diferencia por cuota entre ambos escenarios | $465.762 |
| Monto financiado equivalente en COP | $315.000.000 |
El credito en USD tiene cuota fija en dolares, pero en pesos presenta un comportamiento incierto porque cada pago depende de la TRM vigente. Por tanto:
datos_retornos <- datos_trm_mensual |>
dplyr::mutate(
retorno_simple = trm_mensual / dplyr::lag(trm_mensual) - 1,
retorno_logaritmico = log(trm_mensual / dplyr::lag(trm_mensual))
) |>
dplyr::filter(!is.na(retorno_logaritmico))
media_log_mensual <- mean(datos_retornos$retorno_logaritmico)
desviacion_mensual <- sd(datos_retornos$retorno_logaritmico)
resumen_retornos <- tibble::tibble(
metrica = c("Numero de observaciones", "Media logaritmica mensual", "Desviacion estandar mensual"),
valor = c(
nrow(datos_retornos),
formatear_pct(media_log_mensual),
formatear_pct(desviacion_mensual)
)
)
knitr::kable(resumen_retornos, align = c("l", "r"), caption = "Resumen de retornos mensuales de la TRM proxy") |>
kableExtra::kable_styling(full_width = FALSE)
| metrica | valor |
|---|---|
| Numero de observaciones | 193 |
| Media logaritmica mensual | 0,32% |
| Desviacion estandar mensual | 3,09% |
ggplot(datos_retornos, aes(x = fecha, y = retorno_simple)) +
geom_line(linewidth = 0.8) +
geom_hline(yintercept = 0, linetype = "dashed") +
labs(
title = "Retornos mensuales de la TRM proxy",
subtitle = "Serie mensual COP/USD utilizada para la simulacion",
x = "Fecha",
y = "Retorno mensual"
) +
scale_y_continuous(labels = scales::label_percent(decimal.mark = ",")) +
theme_minimal()
meses_simulacion <- params$plazo_credito_anios * 12
matriz_trm_normal <- simular_bmg_normal(
spot_inicial = params$trm_actual,
media_log_mensual = media_log_mensual,
volatilidad_mensual = desviacion_mensual,
meses = meses_simulacion,
simulaciones = params$numero_simulaciones
)
matriz_trm_t <- simular_bmg_t_student(
spot_inicial = params$trm_actual,
media_log_mensual = media_log_mensual,
volatilidad_mensual = desviacion_mensual,
meses = meses_simulacion,
simulaciones = params$numero_simulaciones,
grados_libertad = params$grados_libertad_t
)
resumen_trayectorias_normal <- obtener_resumen_trayectorias(matriz_trm_normal)
resumen_trayectorias_t <- obtener_resumen_trayectorias(matriz_trm_t)
resumen_terminal <- tibble::tibble(
distribucion = c("Normal", "T-Student"),
promedio_terminal = c(mean(matriz_trm_normal[nrow(matriz_trm_normal), ]), mean(matriz_trm_t[nrow(matriz_trm_t), ])),
mediana_terminal = c(median(matriz_trm_normal[nrow(matriz_trm_normal), ]), median(matriz_trm_t[nrow(matriz_trm_t), ])),
p95_terminal = c(quantile(matriz_trm_normal[nrow(matriz_trm_normal), ], 0.95), quantile(matriz_trm_t[nrow(matriz_trm_t), ], 0.95))
)
knitr::kable(
resumen_terminal |>
dplyr::mutate(
promedio_terminal = formatear_cop(promedio_terminal),
mediana_terminal = formatear_cop(mediana_terminal),
p95_terminal = formatear_cop(p95_terminal)
),
align = c("l", "r", "r", "r"),
caption = "Resumen de la TRM terminal simulada"
) |>
kableExtra::kable_styling(full_width = FALSE)
| distribucion | promedio_terminal | mediana_terminal | p95_terminal |
|---|---|---|---|
| Normal | $5.365 | $5.075 | $8.703 |
| T-Student | $5.399 | $5.105 | $8.899 |
datos_bandas_normal <- resumen_trayectorias_normal |>
dplyr::mutate(modelo = "Normal")
ggplot(datos_bandas_normal, aes(x = mes)) +
geom_ribbon(aes(ymin = p5, ymax = p95), alpha = 0.20) +
geom_line(aes(y = p50), linewidth = 1) +
labs(
title = "Bandas de simulacion BMG - innovaciones normales",
x = "Mes",
y = "COP por USD"
) +
scale_y_continuous(labels = scales::label_number(big.mark = ".", decimal.mark = ",")) +
theme_minimal()
datos_bandas_t <- resumen_trayectorias_t |>
dplyr::mutate(modelo = "T-Student")
ggplot(datos_bandas_t, aes(x = mes)) +
geom_ribbon(aes(ymin = p5, ymax = p95), alpha = 0.20) +
geom_line(aes(y = p50), linewidth = 1) +
labs(
title = "Bandas de simulacion BMG - innovaciones T-Student",
subtitle = "La distribucion T-Student capta colas pesadas",
x = "Mes",
y = "COP por USD"
) +
scale_y_continuous(labels = scales::label_number(big.mark = ".", decimal.mark = ",")) +
theme_minimal()
En el archivo publico de BanRep/SET-FX se usa el rango “mayor a 180” como aproximacion observable del mercado forward superior a 6 meses. Para llevarlo a un esquema operativo de cobertura anual se toma la devaluacion implicita anualizada de mercado y se aproxima un forward de 1 ano.
tabla_forward_referencia <- tibble::tibble(
concepto = c(
"Fecha de referencia del archivo publico",
"Devaluacion implicita anualizada - Mercado",
"Devaluacion implicita anualizada - Total",
"Forward anual de mercado estimado desde el spot",
"Forward anual teorico por paridad simple"
),
valor = c(
as.character(fecha_forward_referencia),
formatear_pct(devaluacion_implicita_mercado),
formatear_pct(devaluacion_implicita_total),
paste0(formatear_trm(forward_mercado_1_anio), " COP/USD"),
paste0(formatear_trm(forward_teorico_1_anio), " COP/USD")
)
)
knitr::kable(
tabla_forward_referencia,
align = c("l", "r"),
caption = "Calibracion del forward usando informacion publica BanRep/SET-FX"
) |>
kableExtra::kable_styling(full_width = FALSE)
| concepto | valor |
|---|---|
| Fecha de referencia del archivo publico | 2025-02-04 |
| Devaluacion implicita anualizada - Mercado | 5,14% |
| Devaluacion implicita anualizada - Total | 5,15% |
| Forward anual de mercado estimado desde el spot | 3.857,53 COP/USD |
| Forward anual teorico por paridad simple | 3.776,12 COP/USD |
resultado_cobertura_normal <- construir_flujos_cobertura(
tabla_credito_usd = tabla_credito_usd,
matriz_trm = matriz_trm_normal,
tasa_forward_anual = devaluacion_implicita_mercado,
porcentaje_cobertura = params$porcentaje_cobertura_forward,
pagos_por_anio = params$pagos_por_anio
)
resultado_cobertura_t <- construir_flujos_cobertura(
tabla_credito_usd = tabla_credito_usd,
matriz_trm = matriz_trm_t,
tasa_forward_anual = devaluacion_implicita_mercado,
porcentaje_cobertura = params$porcentaje_cobertura_forward,
pagos_por_anio = params$pagos_por_anio
)
probabilidades_proteccion_normal <- resultado_cobertura_normal$eventos_protegidos |>
dplyr::group_by(anio) |>
dplyr::summarise(
probabilidad_protegido = mean(protegido),
ahorro_promedio_cop = mean(ahorro_cobertura_cop),
.groups = "drop"
) |>
dplyr::mutate(distribucion = "Normal")
probabilidades_proteccion_t <- resultado_cobertura_t$eventos_protegidos |>
dplyr::group_by(anio) |>
dplyr::summarise(
probabilidad_protegido = mean(protegido),
ahorro_promedio_cop = mean(ahorro_cobertura_cop),
.groups = "drop"
) |>
dplyr::mutate(distribucion = "T-Student")
probabilidades_proteccion <- dplyr::bind_rows(
probabilidades_proteccion_normal,
probabilidades_proteccion_t
)
knitr::kable(
probabilidades_proteccion |>
dplyr::mutate(
probabilidad_protegido = formatear_pct(probabilidad_protegido),
ahorro_promedio_cop = formatear_cop(ahorro_promedio_cop)
),
align = c("r", "r", "r", "l"),
caption = "Eventos protegidos por ano y por distribucion"
) |>
kableExtra::kable_styling(full_width = FALSE)
| anio | probabilidad_protegido | ahorro_promedio_cop | distribucion |
|---|---|---|---|
| 6 | 34,91% | -$1.172.501 | Normal |
| 7 | 35,34% | -$1.238.839 | Normal |
| 8 | 34,84% | -$1.298.472 | Normal |
| 9 | 34,44% | -$1.342.967 | Normal |
| 6 | 34,71% | -$1.143.268 | T-Student |
| 7 | 34,06% | -$1.228.805 | T-Student |
| 8 | 33,69% | -$1.301.925 | T-Student |
| 9 | 33,29% | -$1.371.423 | T-Student |
ggplot(probabilidades_proteccion, aes(x = factor(anio), y = probabilidad_protegido, fill = distribucion)) +
geom_col(position = "dodge") +
labs(
title = "Probabilidad de que la cobertura resulte favorable",
subtitle = "Un evento se considera protegido cuando el costo con forward es menor o igual al costo sin cobertura",
x = "Ano cubierto",
y = "Probabilidad"
) +
scale_y_continuous(labels = scales::label_percent(decimal.mark = ",")) +
theme_minimal()
flujo_total_sin_cobertura_normal <- cuota_inicial_usd * params$trm_actual + resultado_cobertura_normal$costos_sin_cobertura
flujo_total_con_cobertura_normal <- cuota_inicial_usd * params$trm_actual + resultado_cobertura_normal$costos_con_cobertura
flujo_total_sin_cobertura_t <- cuota_inicial_usd * params$trm_actual + resultado_cobertura_t$costos_sin_cobertura
flujo_total_con_cobertura_t <- cuota_inicial_usd * params$trm_actual + resultado_cobertura_t$costos_con_cobertura
comparacion_flujos <- tibble::tibble(
distribucion = c("Normal", "Normal", "T-Student", "T-Student"),
estrategia = c("Sin cobertura", "Con cobertura", "Sin cobertura", "Con cobertura"),
promedio = c(
mean(flujo_total_sin_cobertura_normal), mean(flujo_total_con_cobertura_normal),
mean(flujo_total_sin_cobertura_t), mean(flujo_total_con_cobertura_t)
),
mediana = c(
median(flujo_total_sin_cobertura_normal), median(flujo_total_con_cobertura_normal),
median(flujo_total_sin_cobertura_t), median(flujo_total_con_cobertura_t)
),
p95 = c(
quantile(flujo_total_sin_cobertura_normal, 0.95), quantile(flujo_total_con_cobertura_normal, 0.95),
quantile(flujo_total_sin_cobertura_t, 0.95), quantile(flujo_total_con_cobertura_t, 0.95)
)
)
resumen_ahorros <- tibble::tibble(
distribucion = c("Normal", "T-Student"),
probabilidad_de_ahorro = c(
mean(flujo_total_con_cobertura_normal <= flujo_total_sin_cobertura_normal),
mean(flujo_total_con_cobertura_t <= flujo_total_sin_cobertura_t)
),
ahorro_promedio_cop = c(
mean(flujo_total_sin_cobertura_normal - flujo_total_con_cobertura_normal),
mean(flujo_total_sin_cobertura_t - flujo_total_con_cobertura_t)
)
)
knitr::kable(
comparacion_flujos |>
dplyr::mutate(
promedio = formatear_cop(promedio),
mediana = formatear_cop(mediana),
p95 = formatear_cop(p95)
),
align = c("l", "l", "r", "r", "r"),
caption = "Comparacion del flujo total de inversion"
) |>
kableExtra::kable_styling(full_width = FALSE)
| distribucion | estrategia | promedio | mediana | p95 |
|---|---|---|---|---|
| Normal | Sin cobertura | $632.876.410 | $617.436.673 | $855.739.177 |
| Normal | Con cobertura | $637.929.188 | $623.428.942 | $858.268.109 |
| T-Student | Sin cobertura | $635.368.340 | $619.919.442 | $870.981.116 |
| T-Student | Con cobertura | $640.413.761 | $625.278.594 | $874.264.911 |
knitr::kable(
resumen_ahorros |>
dplyr::mutate(
probabilidad_de_ahorro = formatear_pct(probabilidad_de_ahorro),
ahorro_promedio_cop = formatear_cop(ahorro_promedio_cop)
),
align = c("l", "r", "r"),
caption = "Efectividad agregada de la cobertura"
) |>
kableExtra::kable_styling(full_width = FALSE)
| distribucion | probabilidad_de_ahorro | ahorro_promedio_cop |
|---|---|---|
| Normal | 21,44% | -$5.052.778 |
| T-Student | 21,02% | -$5.045.421 |
datos_cajas <- tibble::tibble(
distribucion = c(
rep("Normal", length(flujo_total_sin_cobertura_normal) + length(flujo_total_con_cobertura_normal)),
rep("T-Student", length(flujo_total_sin_cobertura_t) + length(flujo_total_con_cobertura_t))
),
estrategia = c(
rep("Sin cobertura", length(flujo_total_sin_cobertura_normal)),
rep("Con cobertura", length(flujo_total_con_cobertura_normal)),
rep("Sin cobertura", length(flujo_total_sin_cobertura_t)),
rep("Con cobertura", length(flujo_total_con_cobertura_t))
),
flujo_total_cop = c(
flujo_total_sin_cobertura_normal,
flujo_total_con_cobertura_normal,
flujo_total_sin_cobertura_t,
flujo_total_con_cobertura_t
)
)
ggplot(datos_cajas, aes(x = estrategia, y = flujo_total_cop, fill = estrategia)) +
geom_boxplot(alpha = 0.75) +
facet_wrap(~ distribucion, scales = "free_y") +
labs(
title = "Distribucion del flujo total de inversion",
subtitle = "Comparacion entre la estrategia cubierta y no cubierta",
x = NULL,
y = "COP"
) +
scale_y_continuous(labels = scales::label_number(big.mark = ".", decimal.mark = ",", prefix = "$")) +
theme_minimal() +
theme(legend.position = "none")
prob_ahorro_normal <- resumen_ahorros$probabilidad_de_ahorro[resumen_ahorros$distribucion == "Normal"]
prob_ahorro_t <- resumen_ahorros$probabilidad_de_ahorro[resumen_ahorros$distribucion == "T-Student"]
ahorro_normal <- resumen_ahorros$ahorro_promedio_cop[resumen_ahorros$distribucion == "Normal"]
ahorro_t <- resumen_ahorros$ahorro_promedio_cop[resumen_ahorros$distribucion == "T-Student"]
cat(glue::glue(
"<div class='caja-resumen'>\n",
"<p><strong>Lectura financiera:</strong> el credito en USD permite financiar la maquinaria, pero deja la empresa expuesta a la volatilidad del dolar. En el escenario modelado, la cobertura con forwards sobre el 75% de la exposicion entre los anos 6 y 9 reduce la incertidumbre del flujo en COP y transfiere parte del riesgo cambiario al precio pactado del forward.</p>\n",
"<p><strong>Hallazgo bajo normalidad:</strong> la probabilidad de que la cobertura reduzca el flujo total es de <strong>{formatear_pct(prob_ahorro_normal)}</strong>, con un ahorro promedio de <strong>{formatear_cop(ahorro_normal)}</strong>.</p>\n",
"<p><strong>Hallazgo con colas pesadas:</strong> la probabilidad de mejora es de <strong>{formatear_pct(prob_ahorro_t)}</strong>, con un ahorro promedio de <strong>{formatear_cop(ahorro_t)}</strong>. Este escenario suele capturar mejor episodios extremos de depreciacion.</p>\n",
"<p><strong>Juicio final:</strong> si la empresa prioriza estabilidad del flujo en pesos y quiere limitar el riesgo de depreciaciones fuertes, la estrategia con forward es defendible y tecnicamente consistente. Si el objetivo principal fuera minimizar el costo esperado en un entorno de apreciacion del peso, la cobertura parcial podria parecer menos conveniente en algunos escenarios. Por ello, la recomendacion mas robusta es mantener una <strong>cobertura parcial, no total</strong>, tal como se hizo en el ejercicio.</p>\n",
"</div>"
))
Lectura financiera: el credito en USD permite financiar la maquinaria, pero deja la empresa expuesta a la volatilidad del dolar. En el escenario modelado, la cobertura con forwards sobre el 75% de la exposicion entre los anos 6 y 9 reduce la incertidumbre del flujo en COP y transfiere parte del riesgo cambiario al precio pactado del forward.
Hallazgo bajo normalidad: la probabilidad de que la cobertura reduzca el flujo total es de 21,44%, con un ahorro promedio de -$5.052.778.
Hallazgo con colas pesadas: la probabilidad de mejora es de 21,02%, con un ahorro promedio de -$5.045.421. Este escenario suele capturar mejor episodios extremos de depreciacion.
Juicio final: si la empresa prioriza estabilidad del flujo en pesos y quiere limitar el riesgo de depreciaciones fuertes, la estrategia con forward es defendible y tecnicamente consistente. Si el objetivo principal fuera minimizar el costo esperado en un entorno de apreciacion del peso, la cobertura parcial podria parecer menos conveniente en algunos escenarios. Por ello, la recomendacion mas robusta es mantener una cobertura parcial, no total, tal como se hizo en el ejercicio.
Las siguientes fuentes sustentan el documento y deben citarse en la entrega final: