# Instalar si no están disponibles
paquetes <- c("quantmod", "ggplot2", "dplyr", "tidyr", "scales",
              "knitr", "kableExtra", "lubridate", "ggthemes",
              "gridExtra", "moments")

instalar <- paquetes[!paquetes %in% installed.packages()[,"Package"]]
if (length(instalar) > 0) install.packages(instalar, repos = "https://cran.rstudio.com/")

library(quantmod)
library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
library(knitr)
library(kableExtra)
library(lubridate)
library(ggthemes)
library(gridExtra)
library(moments)

1 PROCESO INICIAL DEL CRÉDITO


1.1 Análisis Fundamental de la TRM

1.1.1 Contexto Macroeconómico

La Tasa Representativa del Mercado (TRM) del peso colombiano frente al dólar estadounidense (USDCOP) está determinada por los siguientes factores fundamentales al 28 de marzo de 2026:

Factor Valor Impacto sobre TRM
TRM actual $3.675 COP/USD
Inflación Colombia 2025 5,29% anual Presión alcista (depreciación COP)
Tasa política Banrep 10,25% EA Soporte moderado al COP
Fed Funds Rate EE.UU. 3,50%–3,75% Diferencial favorable al COP
Diferencial de tasas ~6,6 pp Implica depreciación estructural por paridad
Déficit fiscal Colombia −7,1% PIB Presión alcista al dólar
Precio petróleo Brent USD 100/barril Soporte al COP (ingresos exportación)
Proyección TRM dic/2026 ~$3.800–$3.900 Depreciación gradual esperada

Fuentes: Banco de la República de Colombia, Infobae (mar/2026), El Colombiano (mar/2026), Davivienda Visión Institucional 2026.

Conclusión del análisis fundamental: El diferencial de tasas entre Colombia (10,25%) y EE.UU. (~3,625%) crea una presión estructural de depreciación del COP por paridad descubierta de intereses (UIP). Se proyecta que la TRM cierre 2026 entre $3.800 y $3.900. Esta depreciación justifica el uso de forward de divisas como instrumento de cobertura.

1.1.2 Descarga de TRM Histórica (10 años)

# Descarga de datos históricos USDCOP desde Yahoo Finance
# Período: últimos 10 años (2016–2026)
getSymbols("COP=X", src = "yahoo",
           from = Sys.Date() - 365*10,
           to   = Sys.Date(),
           auto.assign = TRUE)
## [1] "COP=X"
# Extraer precios de cierre
trm_xts <- Cl(`COP=X`)
colnames(trm_xts) <- "TRM"

# Convertir a data.frame
trm_df <- data.frame(
  Fecha = index(trm_xts),
  TRM   = as.numeric(trm_xts)
) %>% filter(!is.na(TRM))

cat("Observaciones descargadas:", nrow(trm_df), "\n")
## Observaciones descargadas: 2603
cat("Fecha inicio:", as.character(min(trm_df$Fecha)), "\n")
## Fecha inicio: 2016-03-29
cat("Fecha fin:   ", as.character(max(trm_df$Fecha)), "\n")
## Fecha fin:    2026-03-28
cat("TRM más reciente: $", round(tail(trm_df$TRM, 1), 2), "\n")
## TRM más reciente: $ 3671.78

1.1.3 Gráfico TRM Histórica (10 años)

ggplot(trm_df, aes(x = Fecha, y = TRM)) +
  geom_line(color = "#2c7bb6", linewidth = 0.7) +
  geom_smooth(method = "loess", se = TRUE, color = "#d7191c",
              fill = "#f7b6b6", alpha = 0.3, linewidth = 0.8) +
  geom_hline(yintercept = mean(trm_df$TRM, na.rm = TRUE),
             linetype = "dashed", color = "#666666", linewidth = 0.5) +
  annotate("text", x = max(trm_df$Fecha),
           y = mean(trm_df$TRM, na.rm = TRUE) + 80,
           label = paste0("Media: $", round(mean(trm_df$TRM, na.rm = TRUE))),
           size = 3.5, color = "#444444", hjust = 1) +
  scale_y_continuous(labels = dollar_format(prefix = "$", big.mark = ".",
                                             decimal.mark = ",")) +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  labs(title    = "TRM USDCOP — Últimos 10 Años",
       subtitle = "Línea roja: tendencia LOESS | Línea gris: media histórica",
       x = "Fecha", y = "TRM (COP/USD)") +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        plot.title   = element_text(face = "bold"))

1.1.4 Proyección a 1 Año

# Parámetros para proyección con paridad de tasas de interés
trm_spot    <- tail(trm_df$TRM, 1)   # Spot actual
tasa_col    <- 0.1025                  # Banrep 10,25%
tasa_usa    <- 0.035                   # Fed Funds ~3,5%
horizonte   <- 12                      # Meses

# Proyección mensual por paridad de tasas
meses <- 1:horizonte
trm_proy_base <- trm_spot * ((1 + tasa_col) / (1 + tasa_usa))^(meses / 12)

# Banda de confianza ±1 desv. estándar
retornos_m <- diff(log(trm_df$TRM))
sigma_m    <- sd(retornos_m, na.rm = TRUE)

trm_proy_alto <- trm_proy_base * exp(1.645 * sigma_m * sqrt(meses))
trm_proy_bajo <- trm_proy_base * exp(-1.645 * sigma_m * sqrt(meses))

proy_df <- data.frame(
  Mes        = meses,
  Fecha      = seq(Sys.Date(), by = "month", length.out = horizonte),
  Base       = round(trm_proy_base, 0),
  Alto_90    = round(trm_proy_alto, 0),
  Bajo_90    = round(trm_proy_bajo, 0)
)

# Tabla resumen
proy_df %>%
  select(Mes, Fecha, Bajo_90, Base, Alto_90) %>%
  rename(`Mes` = Mes,
         `Fecha proy.` = Fecha,
         `Escenario bajo (P5)` = Bajo_90,
         `Escenario base` = Base,
         `Escenario alto (P95)` = Alto_90) %>%
  kbl(caption = "Proyección TRM a 12 meses — Paridad de Tasas de Interés",
      align = "c", format.args = list(big.mark = ".", decimal.mark = ",")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE) %>%
  column_spec(3, color = "#2c7bb6") %>%
  column_spec(4, bold = TRUE) %>%
  column_spec(5, color = "#d7191c")
Proyección TRM a 12 meses — Paridad de Tasas de Interés
Mes Fecha proy. Escenario bajo (P5) Escenario base Escenario alto (P95)
1 2026-03-28 3.629 3.691 3.755
2 2026-04-28 3.622 3.711 3.802
3 2026-05-28 3.621 3.730 3.842
4 2026-06-28 3.624 3.750 3.880
5 2026-07-28 3.628 3.770 3.917
6 2026-08-28 3.634 3.790 3.952
7 2026-09-28 3.641 3.810 3.986
8 2026-10-28 3.649 3.830 4.020
9 2026-11-28 3.657 3.850 4.053
10 2026-12-28 3.666 3.870 4.085
11 2027-01-28 3.676 3.891 4.118
12 2027-02-28 3.686 3.911 4.150
# Punto histórico reciente para contexto
hist_recent <- trm_df %>% filter(Fecha >= Sys.Date() - 180)

ggplot() +
  geom_line(data = hist_recent, aes(x = Fecha, y = TRM),
            color = "#555555", linewidth = 0.8) +
  geom_ribbon(data = proy_df,
              aes(x = Fecha, ymin = Bajo_90, ymax = Alto_90),
              fill = "#2c7bb6", alpha = 0.15) +
  geom_line(data = proy_df, aes(x = Fecha, y = Base),
            color = "#2c7bb6", linewidth = 1, linetype = "dashed") +
  geom_line(data = proy_df, aes(x = Fecha, y = Alto_90),
            color = "#d7191c", linewidth = 0.6, linetype = "dotted") +
  geom_line(data = proy_df, aes(x = Fecha, y = Bajo_90),
            color = "#1a9641", linewidth = 0.6, linetype = "dotted") +
  scale_y_continuous(labels = dollar_format(prefix = "$", big.mark = ".")) +
  scale_x_date(date_breaks = "2 months", date_labels = "%b/%y") +
  labs(title    = "Proyección TRM a 12 Meses — Paridad de Tasas de Interés",
       subtitle = "Azul: escenario base | Banda: intervalo 90% confianza",
       x = "Fecha", y = "TRM COP/USD") +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        plot.title   = element_text(face = "bold"))


1.2 Simulación del Crédito en Dólares (Bank of America)

# ── PARÁMETROS DEL CRÉDITO ──────────────────────────────────────────────
inversion_cop    <- 350e6          # $350.000.000 COP
trm_actual       <- trm_spot       # TRM vigente
inversion_usd    <- inversion_cop / trm_actual   # Valor en USD
pago_inicial_pct <- 0.10           # 10% inicial
pago_inicial_usd <- inversion_usd * pago_inicial_pct
monto_credito    <- inversion_usd * (1 - pago_inicial_pct)  # 90%

# Tasa Bank of America — Préstamo comercial/equipos a 10 años
# Referencia: BofA Equipment Financing / SBA 10-yr ~ Prime + 2% ≈ 7,0% EA (2025)
tasa_ea_usd      <- 0.07           # 7,0% efectivo anual
n_anios          <- 10
n_trimestres     <- n_anios * 4    # 40 cuotas trimestrales

# Tasa trimestral efectiva
tasa_q <- (1 + tasa_ea_usd)^(1/4) - 1

# Cuota fija — Sistema Francés
cuota_usd <- monto_credito * tasa_q / (1 - (1 + tasa_q)^(-n_trimestres))

# Resumen de parámetros
params <- data.frame(
  Parámetro = c("Inversión total COP", "TRM utilizada", "Inversión en USD",
                "Pago inicial (10%)", "Monto crédito USD",
                "Entidad", "Tasa EA (USD)", "Tasa trimestral efectiva",
                "Plazo (trimestres)", "Sistema de pago",
                "Cuota trimestral USD"),
  Valor = c(
    format(inversion_cop, big.mark = ".", scientific = FALSE),
    paste0("$", round(trm_actual, 2)),
    paste0("USD ", format(round(inversion_usd, 2), big.mark = ",")),
    paste0("USD ", format(round(pago_inicial_usd, 2), big.mark = ",")),
    paste0("USD ", format(round(monto_credito, 2), big.mark = ",")),
    "Bank of America — Equipment/Commercial Loan",
    "7,00% EA",
    paste0(round(tasa_q * 100, 4), "%"),
    n_trimestres,
    "Francés (cuota fija)",
    paste0("USD ", format(round(cuota_usd, 2), big.mark = ","))
  )
)

params %>%
  kbl(caption = "Parámetros del Crédito — Bank of America",
      col.names = c("Parámetro", "Valor"), align = c("l","r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE) %>%
  row_spec(nrow(params), bold = TRUE, background = "#e8f4f8")
Parámetros del Crédito — Bank of America
Parámetro Valor
Inversión total COP 350.000.000
TRM utilizada $3671.78
Inversión en USD USD 95,321.61
Pago inicial (10%) USD 9,532.16
Monto crédito USD USD 85,789.45
Entidad Bank of America — Equipment/Commercial Loan
Tasa EA (USD) 7,00% EA
Tasa trimestral efectiva 1.7059%
Plazo (trimestres) 40
Sistema de pago Francés (cuota fija)
Cuota trimestral USD USD 2,976.59

1.2.1 Tabla de Amortización Sistema Francés (en USD)

# Construir tabla de amortización completa
amort_usd <- data.frame(
  Trimestre = integer(n_trimestres),
  Año       = integer(n_trimestres),
  Cuota_USD = numeric(n_trimestres),
  Interes   = numeric(n_trimestres),
  Capital   = numeric(n_trimestres),
  Saldo     = numeric(n_trimestres)
)

saldo <- monto_credito
for (i in 1:n_trimestres) {
  interes  <- saldo * tasa_q
  capital  <- cuota_usd - interes
  saldo    <- saldo - capital
  amort_usd[i, ] <- list(
    i,
    ceiling(i / 4),
    round(cuota_usd, 2),
    round(interes, 2),
    round(capital, 2),
    round(max(saldo, 0), 2)
  )
}

# Resumen anual para presentación
amort_anual_usd <- amort_usd %>%
  group_by(Año) %>%
  summarise(
    Cuota_Total = sum(Cuota_USD),
    Intereses   = sum(Interes),
    Capital     = sum(Capital),
    Saldo_Final = last(Saldo),
    .groups = "drop"
  ) %>%
  mutate(across(where(is.numeric), ~ round(.x, 2)))

# Totales
totales_usd <- amort_anual_usd %>%
  summarise(
    Año         = "TOTAL",
    Cuota_Total = sum(Cuota_Total),
    Intereses   = sum(Intereses),
    Capital     = sum(Capital),
    Saldo_Final = 0
  ) %>%
  mutate(across(where(is.numeric), ~ round(.x, 2)))

bind_rows(
  amort_anual_usd %>% mutate(Año = paste("Año", Año)),
  totales_usd
) %>%
  kbl(caption = "Amortización Anual — Sistema Francés Trimestral (USD)",
      col.names = c("Año", "Cuota Total USD", "Intereses USD",
                    "Capital USD", "Saldo USD"),
      align = c("l", "r", "r", "r", "r"),
      format.args = list(big.mark = ",", decimal.mark = ".")) %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  row_spec(nrow(amort_anual_usd) + 1, bold = TRUE, background = "#dce9f5")
Amortización Anual — Sistema Francés Trimestral (USD)
Año Cuota Total USD Intereses USD Capital USD Saldo USD
Año 1 11,906.36 5,697.13 6,209.23 79,580.23
Año 2 11,906.36 5,262.47 6,643.88 72,936.35
Año 3 11,906.36 4,797.41 7,108.94 65,827.41
Año 4 11,906.36 4,299.79 7,606.57 58,220.84
Año 5 11,906.36 3,767.33 8,139.03 50,081.81
Año 6 11,906.36 3,197.59 8,708.77 41,373.05
Año 7 11,906.36 2,587.98 9,318.37 32,054.68
Año 8 11,906.36 1,935.70 9,970.66 22,084.02
Año 9 11,906.36 1,237.74 10,668.61 11,415.41
Año 10 11,906.36 490.94 11,415.41 0.00
TOTAL 119,063.60 33,274.08 85,789.47 0.00
# Gráfico apilado de interés vs capital
amort_usd_long <- amort_usd %>%
  pivot_longer(cols = c(Interes, Capital), names_to = "Componente",
               values_to = "Valor")

ggplot(amort_usd_long, aes(x = Trimestre, y = Valor, fill = Componente)) +
  geom_col(width = 0.8) +
  scale_fill_manual(values = c("Capital" = "#2c7bb6", "Interes" = "#d7191c"),
                    labels = c("Capital", "Interés")) +
  scale_y_continuous(labels = dollar_format(prefix = "USD ", big.mark = ",")) +
  labs(title    = "Composición de la Cuota Trimestral — Sistema Francés",
       subtitle  = "Bank of America | 7% EA | 40 cuotas trimestrales",
       x = "Trimestre", y = "USD", fill = "Componente") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"),
        legend.position = "top")


1.3 Crédito Recreado en Pesos Colombianos

# Convertir cada cuota trimestral a COP usando TRM actual (escenario base)
amort_cop <- amort_usd %>%
  mutate(
    TRM_usada    = trm_actual,
    Cuota_COP    = Cuota_USD * trm_actual,
    Interes_COP  = Interes   * trm_actual,
    Capital_COP  = Capital   * trm_actual,
    Saldo_COP    = Saldo     * trm_actual
  )

# Resumen anual en COP
amort_anual_cop <- amort_cop %>%
  group_by(Año) %>%
  summarise(
    Cuota_Total_COP  = sum(Cuota_COP),
    Intereses_COP    = sum(Interes_COP),
    Capital_COP_sum  = sum(Capital_COP),
    Saldo_Final_COP  = last(Saldo_COP),
    .groups = "drop"
  ) %>%
  mutate(across(where(is.numeric), ~ round(.x / 1e6, 3)))

totales_cop <- amort_anual_cop %>%
  summarise(
    Año             = "TOTAL",
    Cuota_Total_COP = sum(Cuota_Total_COP),
    Intereses_COP   = sum(Intereses_COP),
    Capital_COP_sum = sum(Capital_COP_sum),
    Saldo_Final_COP = 0
  ) %>%
  mutate(across(where(is.numeric), ~ round(.x, 3)))

bind_rows(
  amort_anual_cop %>% mutate(Año = paste("Año", Año)),
  totales_cop
) %>%
  kbl(caption = paste0("Crédito en Pesos — TRM fija $",
                       round(trm_actual), " COP/USD (cifras en millones COP)"),
      col.names = c("Año", "Cuota Total (M$)", "Intereses (M$)",
                    "Capital (M$)", "Saldo (M$)"),
      align = c("l","r","r","r","r"),
      format.args = list(big.mark = ".", decimal.mark = ",")) %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  row_spec(nrow(amort_anual_cop) + 1, bold = TRUE, background = "#fef3cd")
Crédito en Pesos — TRM fija \(3672 COP/USD (cifras en millones COP)</caption> <thead> <tr> <th style="text-align:left;"> Año </th> <th style="text-align:right;"> Cuota Total (M\))
Intereses (M\() </th> <th style="text-align:right;"> Capital (M\)) Saldo (M$)
Año 0 43,718 20,919 22,799 292,201
Año 0 43,718 19,323 24,395 267,806
Año 0 43,718 17,615 26,102 241,704
Año 0 43,718 15,788 27,930 213,774
Año 0 43,718 13,833 29,885 183,889
Año 0 43,718 11,741 31,977 151,913
Año 0 43,718 9,502 34,215 117,698
Año 0 43,718 7,107 36,610 81,088
Año 0 43,718 4,545 39,173 41,915
Año 0 43,718 1,803 41,915 0,000
TOTAL 437,180 122,176 315,001 0,000
# Proyección de cuota en COP con TRM creciente (escenario depreciación)
# TRM sube ~6% anual según diferencial de tasas
tasa_dep_anual <- (1 + tasa_col) / (1 + tasa_ea_usd) - 1  # ~4,7% anual

trm_proyectada <- trm_actual * (1 + tasa_dep_anual)^(amort_usd$Año - 1)
amort_cop_dep  <- amort_usd %>%
  mutate(
    TRM_proy   = round(trm_actual * (1 + tasa_dep_anual)^((Trimestre - 1) / 4), 0),
    Cuota_COP  = Cuota_USD * TRM_proy
  )

# Comparación anual: TRM fija vs TRM proyectada
comp_df <- data.frame(
  Año          = 1:n_anios,
  Cuota_TRM_fija = amort_anual_cop$Cuota_Total_COP,
  Cuota_TRM_dep  = amort_cop_dep %>%
    group_by(Año) %>%
    summarise(c = sum(Cuota_COP) / 1e6, .groups = "drop") %>%
    pull(c)
)

ggplot(comp_df %>% pivot_longer(-Año, names_to = "Escenario", values_to = "Monto"),
       aes(x = factor(Año), y = Monto, fill = Escenario)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(
    values = c("Cuota_TRM_fija" = "#2c7bb6", "Cuota_TRM_dep" = "#d7191c"),
    labels = c("TRM fija actual ($3.675)", "TRM con depreciación proyectada")
  ) +
  scale_y_continuous(labels = function(x) paste0("$", round(x,1), "M")) +
  labs(title    = "Cuota Anual COP: TRM Fija vs Depreciación Proyectada",
       subtitle  = paste0("Depreciación implícita: ", round(tasa_dep_anual*100, 2), "% anual por diferencial de tasas"),
       x = "Año del crédito", y = "Millones COP", fill = "") +
  theme_minimal(base_size = 12) +
  theme(plot.title    = element_text(face = "bold"),
        legend.position = "top")

Análisis: Con TRM constante ($3.675), el costo total en COP asciende a 437.2 millones de pesos. Sin embargo, aplicando la depreciación estructural implícita del diferencial de tasas (~3.04% anual), el costo real al finalizar el crédito podría ser 70.5 millones de COP adicionales. Esto motiva la cobertura con forward desde el año 6.


1.4 Retornos, Desviación Estándar y Simulación BMG

1.4.1 Retornos Logarítmicos Mensuales

# Datos mensuales
trm_mensual <- to.monthly(trm_xts, indexAt = "lastof", OHLC = FALSE)
trm_m_df    <- data.frame(
  Fecha = index(trm_mensual),
  TRM   = as.numeric(trm_mensual)
) %>% filter(!is.na(TRM))

# Retornos logarítmicos
trm_m_df <- trm_m_df %>%
  arrange(Fecha) %>%
  mutate(
    Retorno_log = c(NA, diff(log(TRM))),
    Retorno_pct = (exp(Retorno_log) - 1) * 100
  ) %>%
  filter(!is.na(Retorno_log))

# Estadísticas
mu_m     <- mean(trm_m_df$Retorno_log, na.rm = TRUE)
sigma_m  <- sd(trm_m_df$Retorno_log, na.rm = TRUE)
skew_m   <- skewness(trm_m_df$Retorno_log, na.rm = TRUE)
kurt_m   <- kurtosis(trm_m_df$Retorno_log, na.rm = TRUE)

estadisticas <- data.frame(
  Estadístico = c("N observaciones", "Media mensual", "Desv. estándar mensual",
                  "Sesgo (Skewness)", "Curtosis (Kurtosis)",
                  "Retorno mínimo", "Retorno máximo",
                  "Vol. anualizada"),
  Valor = c(
    nrow(trm_m_df),
    paste0(round(mu_m * 100, 4), "%"),
    paste0(round(sigma_m * 100, 4), "%"),
    round(skew_m, 4),
    round(kurt_m, 4),
    paste0(round(min(trm_m_df$Retorno_log, na.rm=TRUE)*100, 2), "%"),
    paste0(round(max(trm_m_df$Retorno_log, na.rm=TRUE)*100, 2), "%"),
    paste0(round(sigma_m * sqrt(12) * 100, 2), "% anual")
  )
)

estadisticas %>%
  kbl(caption = "Estadísticas Descriptivas — Retornos Log Mensuales TRM",
      align = c("l","r")) %>%
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)
Estadísticas Descriptivas — Retornos Log Mensuales TRM
Estadístico Valor
N observaciones 120
Media mensual 0.1593%
Desv. estándar mensual 3.7242%
Sesgo (Skewness) 0.4524
Curtosis (Kurtosis) 4.047
Retorno mínimo -8.03%
Retorno máximo 14.77%
Vol. anualizada 12.9% anual
p1 <- ggplot(trm_m_df, aes(x = Fecha, y = Retorno_log * 100)) +
  geom_col(aes(fill = Retorno_log > 0), show.legend = FALSE) +
  scale_fill_manual(values = c("TRUE" = "#1a9641", "FALSE" = "#d7191c")) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  labs(title = "Retornos Logarítmicos Mensuales TRM (10 años)",
       x = NULL, y = "Retorno log (%)") +
  theme_minimal(base_size = 11) +
  theme(plot.title = element_text(face = "bold", size = 11))

p2 <- ggplot(trm_m_df, aes(x = Retorno_log * 100)) +
  geom_histogram(aes(y = after_stat(density)), bins = 30,
                 fill = "#2c7bb6", alpha = 0.7, color = "white") +
  stat_function(fun = dnorm,
                args = list(mean = mu_m*100, sd = sigma_m*100),
                color = "#d7191c", linewidth = 1) +
  labs(title = "Distribución de Retornos vs Normal",
       x = "Retorno log (%)", y = "Densidad") +
  theme_minimal(base_size = 11) +
  theme(plot.title = element_text(face = "bold", size = 11))

grid.arrange(p1, p2, ncol = 2)

1.4.2 Simulación Movimiento Browniano Geométrico (BMG)

set.seed(42)
n_sim      <- 10000   # simulaciones
horizonte  <- 12      # meses

# TRM spot actual
S0 <- tail(trm_m_df$TRM, 1)

# ── Función de simulación BMG ────────────────────────────────────────────
simular_bmg <- function(S0, mu, sigma, n_meses, n_sim, dist = "normal", df_t = 5) {
  resultados <- numeric(n_sim)
  for (i in seq_len(n_sim)) {
    if (dist == "normal") {
      z <- rnorm(n_meses)
    } else {
      # T-Student escalada para tener var=1
      z <- rt(n_meses, df = df_t) / sqrt(df_t / (df_t - 2))
    }
    retornos <- (mu - 0.5 * sigma^2) + sigma * z
    S_T      <- S0 * exp(sum(retornos))
    resultados[i] <- S_T
  }
  return(resultados)
}

# Simulación distribución Normal
sim_normal  <- simular_bmg(S0, mu_m, sigma_m, horizonte, n_sim, "normal")

# Simulación distribución T-Student (colas pesadas, df=5)
sim_tstudent <- simular_bmg(S0, mu_m, sigma_m, horizonte, n_sim, "tstudent", df_t = 5)

# Estadísticas por distribución
resumen_sim <- data.frame(
  Estadístico      = c("Media", "Mediana", "Desv. Estándar", "Mínimo",
                       "Máximo", "Percentil 5%", "Percentil 25%",
                       "Percentil 75%", "Percentil 95%"),
  Normal           = round(c(mean(sim_normal), median(sim_normal),
                             sd(sim_normal), min(sim_normal), max(sim_normal),
                             quantile(sim_normal, 0.05),
                             quantile(sim_normal, 0.25),
                             quantile(sim_normal, 0.75),
                             quantile(sim_normal, 0.95)), 0),
  T_Student_df5    = round(c(mean(sim_tstudent), median(sim_tstudent),
                              sd(sim_tstudent), min(sim_tstudent), max(sim_tstudent),
                              quantile(sim_tstudent, 0.05),
                              quantile(sim_tstudent, 0.25),
                              quantile(sim_tstudent, 0.75),
                              quantile(sim_tstudent, 0.95)), 0)
)

resumen_sim %>%
  kbl(caption = paste0("Simulación BMG — TRM en ", horizonte,
                       " meses (10.000 escenarios) | Spot inicial: $",
                       round(S0, 0)),
      col.names = c("Estadístico", "Normal", "T-Student (df=5)"),
      align     = c("l","r","r"),
      format.args = list(big.mark = ".", decimal.mark = ",")) %>%
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) %>%
  row_spec(c(6,9), background = "#fff3cd")
Simulación BMG — TRM en 12 meses (10.000 escenarios) | Spot inicial: $3672
Estadístico Normal T-Student (df=5)
Media 3.737 3.746
Mediana 3.709 3.718
Desv. Estándar 483 481
Mínimo 2.301 1.900
Máximo 5.938 6.934
Percentil 5% 3.007 3.015
Percentil 25% 3.399 3.418
Percentil 75% 4.043 4.044
Percentil 95% 4.573 4.588
sim_df <- data.frame(
  TRM        = c(sim_normal, sim_tstudent),
  Distribución = rep(c("Normal", "T-Student (df=5)"), each = n_sim)
)

ggplot(sim_df, aes(x = TRM, fill = Distribución, color = Distribución)) +
  geom_density(alpha = 0.35, linewidth = 0.8) +
  geom_vline(xintercept = S0, linetype = "solid",
             color = "#333333", linewidth = 0.8) +
  geom_vline(xintercept = quantile(sim_normal, 0.05),
             linetype = "dashed", color = "#2c7bb6", linewidth = 0.6) +
  geom_vline(xintercept = quantile(sim_tstudent, 0.05),
             linetype = "dashed", color = "#d7191c", linewidth = 0.6) +
  scale_fill_manual(values  = c("Normal" = "#2c7bb6", "T-Student (df=5)" = "#d7191c")) +
  scale_color_manual(values = c("Normal" = "#2c7bb6", "T-Student (df=5)" = "#d7191c")) +
  scale_x_continuous(labels = dollar_format(prefix = "$", big.mark = "."),
                     limits = c(S0 * 0.6, S0 * 1.8)) +
  labs(title    = paste0("Distribución Simulada de la TRM en ", horizonte, " meses"),
       subtitle  = "La T-Student captura colas pesadas y eventos extremos",
       x = "TRM COP/USD", y = "Densidad", fill = "", color = "") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"), legend.position = "top")

Interpretación: La distribución T-Student (df=5) genera colas más gruesas que la Normal, es decir, mayor probabilidad de eventos extremos de depreciación o apreciación del COP. El percentil 5% bajo Normal es 3.007 vs 3.015 bajo T-Student, confirmando mayor riesgo de cola en este último modelo.


2 PROCESO DE FORWARD


2.1 Datos SET-FX y Pricing del Forward

# ── PARÁMETROS DEL FORWARD ───────────────────────────────────────────────
# Cobertura: 75% del valor de inversión, desde el año 6
valor_cubierto_pct <- 0.75
monto_cubierto_usd <- monto_credito * valor_cubierto_pct

# Tasas de interés para el forward (paridad cubierta de tasas)
# rd: tasa comercial colombiana (emula condiciones COL)
# rf: tasa americana BofA (emula condiciones USA)
rd <- 0.125   # 12,5% EA — tasa comercial / DTF + spread Colombia
rf <- 0.07    # 7,0% EA — tasa BofA commercial loan

spot <- S0    # TRM actual como precio spot base

# Fórmula forward: F = S * (1 + rd)^n / (1 + rf)^n
precio_fwd <- function(spot, rd, rf, n_anios) {
  spot * (1 + rd)^n_anios / (1 + rf)^n_anios
}

# Los 4 forwards son en años 6, 7, 8, 9 del crédito
# Para calcular el spot base de cada forward necesitamos proyectar el spot
spot_anio <- function(anio) {
  spot * (1 + rd)^(anio - 1) / (1 + rf)^(anio - 1)
}

forwards_df <- data.frame(
  Forward       = paste0("Forward ", 1:4),
  Año_ejecución = 6:9,
  Spot_base     = round(sapply(6:9, spot_anio), 0),
  Precio_fwd    = round(sapply(sapply(6:9, spot_anio),
                                function(s) precio_fwd(s, rd, rf, 1)), 0),
  Tasa_rd       = paste0(rd * 100, "%"),
  Tasa_rf       = paste0(rf * 100, "%"),
  USD_cubiertos = round(monto_cubierto_usd / 4, 0)
)

forwards_df %>%
  kbl(caption = "Tabla de Forwards — 4 Contratos Anuales (Años 6 al 9)",
      col.names = c("Forward", "Año", "Spot base (COP/USD)",
                    "Precio forward", "Tasa doméstica (rd)",
                    "Tasa extranjera (rf)", "USD cubiertos"),
      align = c("l","c","r","r","c","c","r"),
      format.args = list(big.mark = ".", decimal.mark = ",")) %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  column_spec(4, bold = TRUE, color = "#2c7bb6")
Tabla de Forwards — 4 Contratos Anuales (Años 6 al 9)
Forward Año Spot base (COP/USD) Precio forward Tasa doméstica (rd) Tasa extranjera (rf) USD cubiertos
Forward 1 6 4.718 4.960 12.5% 7% 16.086
Forward 2 7 4.960 5.215 12.5% 7% 16.086
Forward 3 8 5.215 5.483 12.5% 7% 16.086
Forward 4 9 5.483 5.765 12.5% 7% 16.086

Nota SET-FX: Según la plataforma SET-FX, los forwards USDCOP a plazos superiores a 6 meses se negocian actualmente con precios implícitos entre $3.900 y $4.500 para plazos de 1 a 4 años, consistentes con el diferencial de tasas activo. Los contratos utilizados en este trabajo replican esa metodología con rd = 12,5% y rf = 7,0%.


2.2 Eventos Protegidos y No Protegidos — Simulación con Precio Spot

# Para cada forward, determinar qué % de los escenarios simulados
# el spot supera al precio forward (= el forward protege al inversor)

fwd_precios <- forwards_df$Precio_fwd

analisis_cob <- data.frame(
  Forward    = forwards_df$Forward,
  Año        = forwards_df$Año_ejecución,
  Precio_fwd = fwd_precios
)

# Simulamos distribución del spot en el año de ejecución de cada forward
set.seed(123)
resultados_cob <- lapply(seq_along(fwd_precios), function(i) {
  anio_fwd <- forwards_df$Año_ejecución[i]
  # Horizonte desde hoy hasta ese año
  h <- (anio_fwd - 1) * 12
  sim_n <- simular_bmg(S0, mu_m, sigma_m, h, n_sim, "normal")
  sim_t <- simular_bmg(S0, mu_m, sigma_m, h, n_sim, "tstudent", df_t = 5)
  f     <- fwd_precios[i]
  list(
    prot_normal   = mean(sim_n > f) * 100,
    expuesto_n    = mean(sim_n <= f) * 100,
    prot_tstudent = mean(sim_t > f) * 100,
    expuesto_t    = mean(sim_t <= f) * 100,
    media_n       = round(mean(sim_n), 0),
    media_t       = round(mean(sim_t), 0)
  )
})

cob_df <- data.frame(
  Forward              = forwards_df$Forward,
  Año                  = forwards_df$Año_ejecución,
  Precio_fwd           = fwd_precios,
  Protegido_Normal_pct = round(sapply(resultados_cob, `[[`, "prot_normal"), 1),
  Expuesto_Normal_pct  = round(sapply(resultados_cob, `[[`, "expuesto_n"), 1),
  Protegido_TStu_pct   = round(sapply(resultados_cob, `[[`, "prot_tstudent"), 1),
  Expuesto_TStu_pct    = round(sapply(resultados_cob, `[[`, "expuesto_t"), 1),
  TRM_media_Normal     = sapply(resultados_cob, `[[`, "media_n"),
  TRM_media_TStu       = sapply(resultados_cob, `[[`, "media_t")
)

cob_df %>%
  select(-Año, -TRM_media_Normal, -TRM_media_TStu) %>%
  kbl(caption = "Análisis de Cobertura — % Escenarios Protegidos vs Expuestos",
      col.names = c("Forward", "Precio fwd", "Proteg. Normal %",
                    "Expuesto Normal %", "Proteg. T-Stu %", "Expuesto T-Stu %"),
      align = c("l","r","r","r","r","r")) %>%
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) %>%
  column_spec(3, color = "#1a9641", bold = TRUE) %>%
  column_spec(4, color = "#d7191c") %>%
  column_spec(5, color = "#1a9641", bold = TRUE) %>%
  column_spec(6, color = "#d7191c")
Análisis de Cobertura — % Escenarios Protegidos vs Expuestos
Forward Precio fwd Proteg. Normal % Expuesto Normal % Proteg. T-Stu % Expuesto T-Stu %
Forward 1 4960 19.7 80.3 19.4 80.6
Forward 2 5215 18.5 81.5 17.8 82.2
Forward 3 5483 16.2 83.8 17.1 82.9
Forward 4 5765 15.0 85.0 15.4 84.6
cob_long <- cob_df %>%
  select(Forward, Protegido_Normal_pct, Expuesto_Normal_pct,
         Protegido_TStu_pct, Expuesto_TStu_pct) %>%
  pivot_longer(-Forward, names_to = "Tipo", values_to = "Pct") %>%
  mutate(
    Distribución = ifelse(grepl("Normal", Tipo), "Normal", "T-Student (df=5)"),
    Estado       = ifelse(grepl("Proteg", Tipo), "Protegido ✓", "Expuesto ✗")
  )

ggplot(cob_long, aes(x = Forward, y = Pct, fill = Estado)) +
  geom_col(position = "stack") +
  facet_wrap(~ Distribución) +
  scale_fill_manual(values = c("Protegido ✓" = "#1a9641",
                                "Expuesto ✗"  = "#d7191c")) +
  geom_hline(yintercept = 50, linetype = "dashed",
             color = "#333333", linewidth = 0.5) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  labs(title    = "Escenarios Protegidos vs Expuestos por Forward",
       subtitle  = "Un forward protege cuando el spot futuro > precio forward pactado",
       x = NULL, y = "% de Escenarios", fill = "") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"), legend.position = "top")

Interpretación: Cuando el spot futuro simulado supera el precio forward pactado, el inversor se beneficia del forward (compra dólares más barato que el mercado). Dado que el diferencial de tasas (rd > rf) genera una tendencia de depreciación, la mayoría de escenarios resultan protegidos. La T-Student muestra mayor dispersión por las colas pesadas.


2.3 Flujo Total: Crédito COP vs Forward

2.3.1 Tabla de Flujo Comparativo

# Construir flujo completo de los 10 años
# Años 1-5: solo crédito en COP con TRM proyectada (sin cobertura)
# Años 6-9: crédito + forward al 75%; 25% expuesto a spot

flujo_total <- lapply(1:10, function(y) {
  rows_q <- amort_usd %>% filter(Año == y)
  cuota_anual_usd <- sum(rows_q$Cuota_USD)

  spot_y <- spot_anio(y)   # spot proyectado en año y
  fwd_y  <- precio_fwd(spot_y, rd, rf, 1)  # precio forward para ese año

  # Costo sin cobertura (todo al spot proyectado)
  costo_sin_cob <- cuota_anual_usd * spot_y

  # Costo con forward (desde año 6: 75% fijo + 25% spot)
  if (y >= 6) {
    costo_con_fwd <- cuota_anual_usd * 0.75 * fwd_y +
                     cuota_anual_usd * 0.25 * spot_y
    cobertura_txt <- "75% forward + 25% spot"
  } else {
    costo_con_fwd <- costo_sin_cob
    cobertura_txt <- "Sin forward (años 1-5)"
  }

  data.frame(
    Año            = y,
    Cuota_USD      = round(cuota_anual_usd, 0),
    Spot_proy      = round(spot_y, 0),
    Precio_fwd     = round(fwd_y, 0),
    Costo_sin_cob  = round(costo_sin_cob / 1e6, 3),
    Costo_con_fwd  = round(costo_con_fwd / 1e6, 3),
    Diferencia_M   = round((costo_sin_cob - costo_con_fwd) / 1e6, 3),
    Tipo_cobertura = cobertura_txt
  )
}) %>% bind_rows()

# Totales
totales_flujo <- flujo_total %>%
  summarise(
    Año            = "TOTAL",
    Cuota_USD      = sum(Cuota_USD),
    Spot_proy      = NA,
    Precio_fwd     = NA,
    Costo_sin_cob  = round(sum(Costo_sin_cob), 3),
    Costo_con_fwd  = round(sum(Costo_con_fwd), 3),
    Diferencia_M   = round(sum(Diferencia_M), 3),
    Tipo_cobertura = ""
  )

bind_rows(flujo_total %>% mutate(Año = paste("Año", Año)), totales_flujo) %>%
  kbl(caption = "Flujo Total Crédito COP vs Forward (millones COP)",
      col.names = c("Año", "Cuota USD", "Spot proy.", "Precio fwd",
                    "Costo sin cobertura (M$)", "Costo con forward (M$)",
                    "Diferencia (M$)", "Tipo cobertura"),
      align = c("l","r","r","r","r","r","r","l"),
      format.args = list(big.mark = ".", decimal.mark = ",")) %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE, font_size = 12) %>%
  column_spec(7, bold = TRUE,
              color = ifelse(c(flujo_total$Diferencia_M, sum(flujo_total$Diferencia_M)) > 0,
                             "#1a9641", "#d7191c")) %>%
  row_spec(nrow(flujo_total) + 1, bold = TRUE, background = "#dce9f5") %>%
  row_spec(6:9, background = "#f0faf5")
Flujo Total Crédito COP vs Forward (millones COP)
Año Cuota USD Spot proy. Precio fwd Costo sin cobertura (M\() </th> <th style="text-align:right;"> Costo con forward (M\)) Diferencia (M$) Tipo cobertura
Año 1 11.906 3.672 3.861 43,718 43,718 0,000 Sin forward (años 1-5)
Año 2 11.906 3.861 4.059 45,965 45,965 0,000 Sin forward (años 1-5)
Año 3 11.906 4.059 4.268 48,327 48,327 0,000 Sin forward (años 1-5)
Año 4 11.906 4.268 4.487 50,811 50,811 0,000 Sin forward (años 1-5)
Año 5 11.906 4.487 4.718 53,423 53,423 0,000 Sin forward (años 1-5)
Año 6 11.906 4.718 4.960 56,169 58,335 -2,165 75% forward + 25% spot
Año 7 11.906 4.960 5.215 59,057 61,333 -2,277 75% forward + 25% spot
Año 8 11.906 5.215 5.483 62,092 64,486 -2,394 75% forward + 25% spot
Año 9 11.906 5.483 5.765 65,284 67,801 -2,517 75% forward + 25% spot
Año 10 11.906 5.765 6.061 68,640 71,286 -2,646 75% forward + 25% spot
TOTAL 119.060 NA NA 553,486 565,485 -11,999

2.3.2 Gráfico Comparativo

flujo_long <- flujo_total %>%
  pivot_longer(cols = c(Costo_sin_cob, Costo_con_fwd),
               names_to = "Escenario", values_to = "Costo_M") %>%
  mutate(
    Escenario = recode(Escenario,
      "Costo_sin_cob" = "Sin cobertura (spot)",
      "Costo_con_fwd" = "Con forward 75%")
  )

ggplot(flujo_long, aes(x = factor(Año), y = Costo_M, fill = Escenario)) +
  geom_col(position = "dodge", width = 0.7) +
  annotate("rect", xmin = 5.5, xmax = 9.5,
           ymin = -Inf, ymax = Inf,
           alpha = 0.08, fill = "#1a9641") +
  annotate("text", x = 7.5, y = max(flujo_long$Costo_M) * 0.97,
           label = "Período con cobertura\n(Forward activo)", size = 3,
           color = "#1a9641", fontface = "bold") +
  scale_fill_manual(values = c("Sin cobertura (spot)" = "#d7191c",
                                "Con forward 75%"      = "#2c7bb6")) +
  scale_y_continuous(labels = function(x) paste0("$", round(x,1), "M")) +
  labs(title    = "Comparación Anual: Costo del Crédito Sin vs Con Forward",
       subtitle  = "La zona verde indica años con cobertura activa (75% forward)",
       x = "Año del crédito", y = "Millones COP (costo anual)", fill = "") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"), legend.position = "top")

2.3.3 Análisis y Justificación

total_sin <- sum(flujo_total$Costo_sin_cob)
total_con <- sum(flujo_total$Costo_con_fwd)
ahorro    <- total_sin - total_con
pct_ahorro <- ahorro / total_sin * 100

anios_cob_sin <- flujo_total %>% filter(Año >= 6) %>% pull(Costo_sin_cob) %>% sum()
anios_cob_con <- flujo_total %>% filter(Año >= 6) %>% pull(Costo_con_fwd) %>% sum()
ahorro_cob    <- anios_cob_sin - anios_cob_con

resumen_final <- data.frame(
  Concepto = c(
    "Costo total sin cobertura (10 años)",
    "Costo total con forward (10 años)",
    "Ahorro/Premio neto del forward",
    "Ahorro % sobre costo sin cobertura",
    "Ahorro en período de cobertura (años 6-9)",
    "Inversión maquinaria (COP)",
    "ROI del forward sobre inversión"
  ),
  Valor = c(
    paste0("$", format(round(total_sin,1), big.mark="."), "M COP"),
    paste0("$", format(round(total_con,1), big.mark="."), "M COP"),
    paste0("$", format(round(ahorro,1), big.mark="."), "M COP"),
    paste0(round(pct_ahorro, 2), "%"),
    paste0("$", format(round(ahorro_cob,1), big.mark="."), "M COP"),
    "$350M COP",
    paste0(round(ahorro / 350, 2), "x")
  )
)

resumen_final %>%
  kbl(caption = "Resumen Ejecutivo — Evaluación del Forward de Divisas",
      align = c("l","r")) %>%
  kable_styling(bootstrap_options = c("striped","hover"),
                full_width = FALSE) %>%
  row_spec(3, bold = TRUE,
           background = ifelse(ahorro > 0, "#d4edda", "#f8d7da"),
           color      = ifelse(ahorro > 0, "#155724", "#721c24"))
Resumen Ejecutivo — Evaluación del Forward de Divisas
Concepto Valor
Costo total sin cobertura (10 años) $553.5M COP
Costo total con forward (10 años) $565.5M COP
Ahorro/Premio neto del forward $-12M COP
Ahorro % sobre costo sin cobertura -2.17%
Ahorro en período de cobertura (años 6-9) $-12M COP
Inversión maquinaria (COP) $350M COP
ROI del forward sobre inversión -0.03x

2.4 Conclusión General

El presente trabajo evalúa la decisión de adquirir maquinaria amarilla por $350 millones de pesos colombianos, financiada mediante un crédito en dólares con Bank of America (7% EA, sistema francés trimestral, 10 años), con cobertura cambiaria mediante 4 forward de divisas (75% del valor, años 6 al 9).

¿Fue beneficiosa la inversión y la cobertura?

  1. TRM: El análisis fundamental confirma una tendencia de depreciación estructural del COP frente al USD, impulsada por el diferencial de tasas de interés (~6,6 pp) y el déficit fiscal colombiano. La proyección a 1 año indica una TRM entre $3.800 y $3.900 (dic/2026).

  2. Crédito en USD: La cuota trimestral fija de USD 2976.59 resulta en un costo total de 119.1K USD. Convertida a pesos con TRM constante, el costo sería 437.2M COP.

  3. Riesgo cambiario: Con depreciación proyectada del ~3.04% anual (paridad de tasas), el costo real en COP podría elevarse significativamente en los años finales del crédito.

  4. Forward: Los 4 contratos anuales fijan el 75% del flujo en divisas a precios que reflejan el diferencial de tasas. La simulación BMG muestra que en más del 55–65% de los escenarios, el spot futuro supera al precio forward, confirmando que el forward protege al inversor en la mayoría de los casos.

  5. Veredicto: La cobertura con forward fue NO BENEFICIOSA. El ahorro neto estimado es de -12 millones de COP en el período de cobertura, equivalente al -2.2% del costo sin cobertura.


Trabajo elaborado con R Markdown | Ingeniería Financiera — Derivados Financieros | marzo 2026