install.packages(“kableExtra”)

library(quantmod)
library(ggplot2)
library(dplyr)
library(scales)
library(moments)
library(MASS)
library(lubridate)
library(knitr)
library(kableExtra)

1. Parámetros Globales

murcielago lindo

set.seed(42)
options(scipen = 999)

TRM_spot      <- 3692.48
inversion_COP <- 350000000
inversion_USD <- inversion_COP / TRM_spot
pago_ini_pct  <- 0.10
cuota_ini_USD <- inversion_USD * pago_ini_pct
monto_USD     <- inversion_USD * (1 - pago_ini_pct)

tasa_anual_USD <- 0.085
tasa_trim_USD  <- (1 + tasa_anual_USD)^(1/4) - 1
n_trim         <- 40

i_COP <- 0.1025
i_USD <- 0.0700

cat(sprintf("TRM Spot (BANREP, 20-mar-26): $%s COP/USD\n", format(TRM_spot, big.mark=",")))
## TRM Spot (BANREP, 20-mar-26): $3,692.48 COP/USD
cat(sprintf("Inversión total:              $%s COP\n",      format(inversion_COP, big.mark=",")))
## Inversión total:              $350,000,000 COP
cat(sprintf("Inversión total:              $%.2f USD\n",    inversion_USD))
## Inversión total:              $94787.24 USD
cat(sprintf("Monto financiado (90%%):      $%.2f USD\n",   monto_USD))
## Monto financiado (90%):      $85308.52 USD
cat(sprintf("Tasa crédito Wells Fargo:     %.2f%% EA\n",    tasa_anual_USD*100))
## Tasa crédito Wells Fargo:     8.50% EA
cat(sprintf("Tasa comercial COP (i_COP):   %.2f%% EA\n",   i_COP*100))
## Tasa comercial COP (i_COP):   10.25% EA
cat(sprintf("Tasa comercial USD (i_USD):   %.2f%% EA\n",   i_USD*100))
## Tasa comercial USD (i_USD):   7.00% EA

2. Análisis Fundamental TRM

tryCatch({
  getSymbols("USDCOP=X", src = "yahoo", from = "2018-01-01",
             auto.assign = TRUE, warnings = FALSE)
  trm_xts <- na.omit(`USDCOP=X`[, 6])
  trm_df  <- data.frame(fecha = index(trm_xts), TRM = as.numeric(trm_xts))
  cat("TRM descargada desde Yahoo Finance.\n")
},
error = function(e) {
  stop("Error obteniendo datos de Yahoo Finance.\n")
})
## TRM descargada desde Yahoo Finance.
# ── Tema APA para todos los gráficos ──────────────────────────────────────
theme_apa <- function(base_size = 12) {
  theme_classic(base_size = base_size) +
    theme(
      text             = element_text(family = "serif", size = base_size),
      plot.title       = element_text(face = "bold", size = base_size,
                                      hjust = 0.5, margin = margin(b = 2)),
      plot.subtitle    = element_text(size = base_size - 1, hjust = 0.5,
                                      face = "italic", margin = margin(b = 8)),
      axis.title       = element_text(face = "bold", size = base_size),
      axis.text        = element_text(size = base_size - 1),
      legend.title     = element_text(face = "bold", size = base_size - 1),
      legend.text      = element_text(size = base_size - 1),
      legend.position  = "bottom",
      panel.grid.major = element_line(color = "grey85", linewidth = 0.4),
      panel.grid.minor = element_blank(),
      plot.background  = element_rect(fill = "white", color = NA),
      panel.background = element_rect(fill = "white", color = NA)
    )
}

p_trm_hist <- ggplot(trm_df, aes(x = fecha, y = TRM)) +
  geom_line(color = "#1565C0", linewidth = 0.8) +
  geom_hline(yintercept = TRM_spot, linetype = "dashed", color = "#B71C1C") +
  annotate("text", x = max(trm_df$fecha), y = TRM_spot + 100,
           label = paste0("Spot actual\n$", TRM_spot), color = "#B71C1C",
           hjust = 1, size = 3.5, family = "serif") +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(title    = "Figura 1",
       subtitle = "TRM Histórica USD/COP (2018–2026). Fuente: Yahoo Finance.",
       x = "Fecha", y = "COP por USD") +
  theme_apa()

print(p_trm_hist)

El comportamiento desde 2018 hasta hoy describe una curva de campana asimétrica: depreciación gradual 2018–2021, pico extremo en 2022 (~$5,100), y corrección pronunciada 2023–2026 de vuelta a niveles de ~$3,650–$3,700. La TRM pasó de ~$2,900 a ~$3,675 en 8 años, lo que representa una depreciación estructural del peso de aproximadamente 26%, aunque con un ciclo intermedio de enorme volatilidad. Los grandes conductores han sido el precio del petróleo, la política monetaria de la Fed, los choques políticos internos y los ciclos de aversión al riesgo global. El último año comenzó con una TRM de $4,409 el 1 de enero y cerró en $3,757 el 31 de diciembre — una revaluación del peso del 14.79%. 2025 Fue el año más favorable para el peso colombiano del período reciente, impulsado por la expectativa de recortes de la Fed, entrada de flujos de capital y relativa estabilidad política. Bancolombia (2025) afirma: “El tipo de cambio USDCOP seguiría una trayectoria de apreciación, con un promedio proyectado de $3.880 en 2026. Esta tendencia estará influenciada por la debilidad global del dólar y el apetito de inversionistas extranjeros por activos locales, gracias al atractivo diferencial de tasas de interés y las expectativas de cara al proceso de elecciones legislativas y presidenciales. Sin embargo, los riesgos fiscales seguirán siendo determinantes para la evolución futura de los activos locales” (párr. 7).


3. Retornos y Estadísticas TRM

trm_men <- trm_df %>%
  mutate(mes = format(fecha, "%Y-%m")) %>%
  group_by(mes) %>%
  summarise(TRM_m = mean(TRM, na.rm = TRUE), .groups = "drop") %>%
  mutate(r_log = log(TRM_m / lag(TRM_m))) %>%
  na.omit()

mu_m    <- mean(trm_men$r_log)
sigma_m <- sd(trm_men$r_log)
skew_v  <- skewness(trm_men$r_log)
kurt_v  <- kurtosis(trm_men$r_log)
nu_est  <- as.numeric(MASS::fitdistr(trm_men$r_log, "t")$estimate["df"])

estadisticas <- data.frame(
  Estadístico = c("Variación mensual promedio (μ)",
                  "Desviación estándar mensual (σ)",
                  "Asimetría (Skewness)",
                  "Curtosis",
                  "Grados de libertad t-Student (ν)"),
  Valor = c(sprintf("%.5f (%.4f%%)", mu_m, mu_m*100),
            sprintf("%.5f (%.4f%%)", sigma_m, sigma_m*100),
            sprintf("%.3f", skew_v),
            sprintf("%.3f  (colas pesadas, >3)", kurt_v),
            sprintf("%.2f", nu_est))
)

kable(estadisticas,
      caption  = "Tabla 1. Estadísticas de retornos logarítmicos mensuales TRM",
      align    = c("l","r"),
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12, position = "center") %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(estadisticas), extra_css = "border-bottom: 2px solid black;")
Tabla 1. Estadísticas de retornos logarítmicos mensuales TRM
Estadístico Valor
Variación mensual promedio (μ) 0.00270 (0.2700%)
Desviación estándar mensual (σ) 0.03135 (3.1350%)
Asimetría (Skewness) 0.941
Curtosis 5.739 (colas pesadas, >3)
Grados de libertad t-Student (ν) 4.71
r_seq  <- seq(min(trm_men$r_log), max(trm_men$r_log), length.out = 200)
dens_N <- dnorm(r_seq, mu_m, sigma_m)
dens_T <- dt((r_seq - mu_m) / (sigma_m / sqrt(nu_est/(nu_est-2))), df = nu_est) /
           (sigma_m / sqrt(nu_est/(nu_est-2)))

p_hist <- ggplot(trm_men, aes(x = r_log)) +
  geom_histogram(aes(y = after_stat(density)), bins = 35,
                 fill = "#90CAF9", color = "white", alpha = 0.8) +
  geom_line(data = data.frame(x = r_seq, y = dens_N),
            aes(x = x, y = y, color = "Normal"), linewidth = 1.2) +
  geom_line(data = data.frame(x = r_seq, y = dens_T),
            aes(x = x, y = y, color = "t-Student"), linewidth = 1.2, linetype = "dashed") +
  scale_color_manual(values = c("Normal" = "#1565C0", "t-Student" = "#B71C1C")) +
  labs(title    = "Figura 2",
       subtitle = sprintf("Distribución de Retornos Mensuales TRM. μ = %.4f%% | σ = %.4f%% | Curtosis = %.2f.",
                          mu_m*100, sigma_m*100, kurt_v),
       x = "Variación logarítmicoa mensual", y = "Densidad", color = "Distribución") +
  theme_apa()

print(p_hist)

La distribución t-Student ofrece un mejor ajuste que la normal, ya que el histograma evidencia colas más pesadas, es decir, una mayor frecuencia de valores extremos que la distribución normal no logra capturar adecuadamente. Esto se confirma con una curtosis de 5.739 (superior a 3), que indica colas gruesas, y una asimetría de 0.941, lo que muestra que los datos no son perfectamente simétricos. Además, los bajos grados de libertad (ν ≈ 4.71) refuerzan la presencia de colas más pesadas en la t-Student. En consecuencia, esta distribución representa de forma más precisa el comportamiento de los retornos y el riesgo asociado a eventos extremos, mientras que la normal tiende a subestimarlo.


4. Simulación MBG (Normal y t-Student)

murcielago lindo

n_sim  <- 5000
n_q    <- 40
mu_q   <- mu_m * 3
sig_q  <- sigma_m * sqrt(3)

sim_N <- matrix(NA, n_q, n_sim)
for (s in 1:n_sim)
  sim_N[, s] <- TRM_spot * exp(cumsum(
    (mu_q - 0.5*sig_q^2) + sig_q * rnorm(n_q)
  ))

scale_t <- sigma_m * sqrt(3) / sqrt(nu_est / (nu_est - 2))
sim_T   <- matrix(NA, n_q, n_sim)
for (s in 1:n_sim)
  sim_T[, s] <- TRM_spot * exp(cumsum(
    (mu_q - 0.5*sig_q^2) + scale_t * rt(n_q, df = nu_est)
  ))

sim_plot <- bind_rows(
  data.frame(Trim = 1:n_q,
             p05 = apply(sim_N, 1, quantile, 0.05),
             p25 = apply(sim_N, 1, quantile, 0.25),
             mediana = apply(sim_N, 1, median),
             p75 = apply(sim_N, 1, quantile, 0.75),
             p95 = apply(sim_N, 1, quantile, 0.95),
             dist = "Normal"),
  data.frame(Trim = 1:n_q,
             p05 = apply(sim_T, 1, quantile, 0.05),
             p25 = apply(sim_T, 1, quantile, 0.25),
             mediana = apply(sim_T, 1, median),
             p75 = apply(sim_T, 1, quantile, 0.75),
             p95 = apply(sim_T, 1, quantile, 0.95),
             dist = "t-Student")
)

p_bmg <- ggplot(sim_plot, aes(x = Trim)) +
  geom_ribbon(aes(ymin = p05, ymax = p95, fill = dist), alpha = 0.12) +
  geom_ribbon(aes(ymin = p25, ymax = p75, fill = dist), alpha = 0.25) +
  geom_line(aes(y = mediana, color = dist), linewidth = 1.3) +
  geom_hline(yintercept = TRM_spot, linetype = "dashed", color = "gray50") +
  scale_fill_manual(values  = c("Normal" = "#1565C0", "t-Student" = "#C62828")) +
  scale_color_manual(values = c("Normal" = "#1565C0", "t-Student" = "#C62828")) +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(title    = "Figura 3",
       subtitle = sprintf("Simulación MBG TRM — Normal vs t-Student (40 trimestres, n = %d trayectorias).", n_sim),
       x = "Trimestre", y = "TRM (COP/USD)", color = "Distribución", fill = "Distribución") +
  theme_apa()

print(p_bmg)

##Prueba


# Límites de la TRM
lim_inf <- TRM_spot - 1000
lim_sup <- TRM_spot + 1000

# ---------------------------
# Simulación Normal (con límites)
# ---------------------------
sim_N <- matrix(NA, n_q, n_sim)

for (s in 1:n_sim) {
  tray <- TRM_spot * exp(cumsum(
    (mu_q - 0.5 * sig_q^2) + sig_q * rnorm(n_q)
  ))
  
  sim_N[, s] <- pmin(pmax(tray, lim_inf), lim_sup)
}

# ---------------------------
# Simulación t-Student (con límites)
# ---------------------------
scale_t <- sigma_m * sqrt(3) / sqrt(nu_est / (nu_est - 2))

sim_T <- matrix(NA, n_q, n_sim)

for (s in 1:n_sim) {
  tray <- TRM_spot * exp(cumsum(
    (mu_q - 0.5 * sig_q^2) + scale_t * rt(n_q, df = nu_est)
  ))
  
  sim_T[, s] <- pmin(pmax(tray, lim_inf), lim_sup)
}

# ---------------------------
# Resumen por percentiles
# ---------------------------
sim_plot <- bind_rows(
  data.frame(
    Trim = 1:n_q,
    p05 = apply(sim_N, 1, quantile, 0.05),
    p25 = apply(sim_N, 1, quantile, 0.25),
    mediana = apply(sim_N, 1, median),
    p75 = apply(sim_N, 1, quantile, 0.75),
    p95 = apply(sim_N, 1, quantile, 0.95),
    dist = "Normal"
  ),
  data.frame(
    Trim = 1:n_q,
    p05 = apply(sim_T, 1, quantile, 0.05),
    p25 = apply(sim_T, 1, quantile, 0.25),
    mediana = apply(sim_T, 1, median),
    p75 = apply(sim_T, 1, quantile, 0.75),
    p95 = apply(sim_T, 1, quantile, 0.95),
    dist = "t-Student"
  )
)

# ---------------------------
# Gráfica
# ---------------------------
p_bmg <- ggplot(sim_plot, aes(x = Trim)) +
  geom_ribbon(aes(ymin = p05, ymax = p95, fill = dist), alpha = 0.12) +
  geom_ribbon(aes(ymin = p25, ymax = p75, fill = dist), alpha = 0.25) +
  geom_line(aes(y = mediana, color = dist), linewidth = 1.3) +
  geom_hline(yintercept = TRM_spot, linetype = "dashed", color = "gray50") +
  scale_fill_manual(values  = c("Normal" = "#1565C0", "t-Student" = "#C62828")) +
  scale_color_manual(values = c("Normal" = "#1565C0", "t-Student" = "#C62828")) +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(
    title    = "Figura 3",
    subtitle = sprintf(
      "Simulación MBG TRM (acotada ±1000) — Normal vs t-Student (40 trimestres, n = %d trayectorias).",
      n_sim
    ),
    x = "Trimestre",
    y = "TRM (COP/USD)",
    color = "Distribución",
    fill = "Distribución"
  ) +
  theme_apa()

print(p_bmg)

#Prueba

murcielago lindo


41. Simulación TRM (t-Student)

murcielago lindo

tabla_trm <- bind_rows(
  data.frame(
    Trim = 1:n_q,
    Escenario_bajo  = apply(sim_T, 1, quantile, 0.05),
    Escenario_base  = apply(sim_T, 1, median),
    Escenario_alto  = apply(sim_T, 1, quantile, 0.95),
    Distribucion = "t-Student"
  )
)

kable(
  tabla_trm,
  digits = 0,
  format.args = list(big.mark = ","),
  caption = "Tabla: Escenarios proyectados de la TRM por trimestre (bajo, base y alto)",
  align = c("c","r","r","r","c"),
  booktabs = TRUE
) %>%
  kable_styling(full_width = TRUE, font_size = 12)
Tabla: Escenarios proyectados de la TRM por trimestre (bajo, base y alto)
Trim Escenario_bajo Escenario_base Escenario_alto Distribucion
1 3,428 3,716 4,036 t-Student
2 3,311 3,740 4,224 t-Student
3 3,236 3,763 4,378 t-Student
4 3,201 3,790 4,485 t-Student
5 3,143 3,819 4,627 t-Student
6 3,123 3,853 4,692 t-Student
7 3,092 3,865 4,692 t-Student
8 3,054 3,886 4,692 t-Student
9 3,019 3,921 4,692 t-Student
10 3,005 3,948 4,692 t-Student
11 2,989 3,975 4,692 t-Student
12 2,985 4,005 4,692 t-Student
13 2,942 4,037 4,692 t-Student
14 2,920 4,063 4,692 t-Student
15 2,906 4,088 4,692 t-Student
16 2,880 4,109 4,692 t-Student
17 2,899 4,137 4,692 t-Student
18 2,881 4,159 4,692 t-Student
19 2,860 4,184 4,692 t-Student
20 2,827 4,220 4,692 t-Student
21 2,832 4,242 4,692 t-Student
22 2,835 4,276 4,692 t-Student
23 2,841 4,300 4,692 t-Student
24 2,835 4,342 4,692 t-Student
25 2,822 4,370 4,692 t-Student
26 2,845 4,392 4,692 t-Student
27 2,822 4,412 4,692 t-Student
28 2,812 4,434 4,692 t-Student
29 2,795 4,476 4,692 t-Student
30 2,788 4,506 4,692 t-Student
31 2,775 4,539 4,692 t-Student
32 2,770 4,557 4,692 t-Student
33 2,775 4,615 4,692 t-Student
34 2,774 4,635 4,692 t-Student
35 2,778 4,665 4,692 t-Student
36 2,793 4,692 4,692 t-Student
37 2,818 4,692 4,692 t-Student
38 2,817 4,692 4,692 t-Student
39 2,817 4,692 4,692 t-Student
40 2,799 4,692 4,692 t-Student

murcielago lindo

5. Crédito en USD — Sistema Francés

murcielago lindo

cuota_USD <- monto_USD * (tasa_trim_USD * (1 + tasa_trim_USD)^n_trim) /
             ((1 + tasa_trim_USD)^n_trim - 1)

tabla <- data.frame(
  Trim = 1:n_trim, Año = ceiling((1:n_trim) / 4),
  Saldo_ini = NA, Interes = NA, Amort = NA, Cuota = NA, Saldo_fin = NA
)
saldo <- monto_USD
for (i in 1:n_trim) {
  int <- saldo * tasa_trim_USD
  am  <- cuota_USD - int
  tabla[i, c("Saldo_ini","Interes","Amort","Cuota","Saldo_fin")] <-
    c(saldo, int, am, cuota_USD, max(saldo - am, 0))
  saldo <- saldo - am
}

kable(
  rbind(
    head(tabla[, c("Trim","Año","Saldo_ini","Interes","Amort","Cuota","Saldo_fin")], 5),
    tail(tabla[, c("Trim","Año","Saldo_ini","Interes","Amort","Cuota","Saldo_fin")], 5)
  ),
  digits      = 2,
  format.args = list(big.mark = ","),
  caption     = sprintf("Tabla 2. Amortización en USD (Sistema Francés) — Cuota fija: $%.2f USD/trimestre", cuota_USD),
  align       = c("c","c","r","r","r","r","r"),
  booktabs    = TRUE
) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(10, extra_css = "border-bottom: 2px solid black;")
Tabla 2. Amortización en USD (Sistema Francés) — Cuota fija: $3151.67 USD/trimestre
Trim Año Saldo_ini Interes Amort Cuota Saldo_fin
1 1 1 85,308.52 1,757.73 1,393.94 3,151.67 83,914.58
2 2 1 83,914.58 1,729.01 1,422.66 3,151.67 82,491.93
3 3 1 82,491.93 1,699.70 1,451.97 3,151.67 81,039.95
4 4 1 81,039.95 1,669.78 1,481.89 3,151.67 79,558.07
5 5 2 79,558.07 1,639.25 1,512.42 3,151.67 78,045.65
36 36 9 14,829.23 305.55 2,846.12 3,151.67 11,983.11
37 37 10 11,983.11 246.90 2,904.76 3,151.67 9,078.35
38 38 10 9,078.35 187.05 2,964.61 3,151.67 6,113.74
39 39 10 6,113.74 125.97 3,025.70 3,151.67 3,088.04
40 40 10 3,088.04 63.63 3,088.04 3,151.67 0.00

murcielago lindo

p_amort <- ggplot(tabla, aes(x = Trim)) +
  geom_col(aes(y = Amort, fill = "Amortización"), alpha = 0.85) +
  geom_col(aes(y = Interes, fill = "Interés"), alpha = 0.85) +
  scale_fill_manual(values = c("Amortización" = "#1565C0", "Interés" = "#EF5350")) +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " USD", big.mark = ",")) +
  labs(title    = "Figura 4",
       subtitle = sprintf("Composición de la cuota — Sistema Francés (%.1f%% EA, 10 años, %d cuotas trimestrales).",
                          tasa_anual_USD*100, n_trim),
       x = "Trimestre", y = "USD", fill = "") +
  theme_apa()

print(p_amort)

murcielago lindo


6. Crédito Transformado a COP

murcielago lindo

deprec_anual <- i_COP - i_USD
deprec_trim  <- (1 + deprec_anual)^(1/4) - 1
TRM_proy     <- TRM_spot * (1 + deprec_trim)^(0:39)

tabla_cop <- tabla %>%
  mutate(TRM_proy      = TRM_proy,
         Cuota_COP     = Cuota * TRM_proy,
         Interes_COP   = Interes * TRM_proy,
         Amort_COP     = Amort * TRM_proy,
         Saldo_ini_COP = Saldo_ini * TRM_proy)

p_cop <- ggplot(tabla_cop, aes(x = Trim)) +
  geom_col(aes(y = Amort_COP / 1e6, fill = "Amortización"), alpha = 0.85) +
  geom_col(aes(y = Interes_COP / 1e6, fill = "Interés"), alpha = 0.85) +
  scale_fill_manual(values = c("Amortización" = "#1565C0", "Interés" = "#EF5350")) +
  scale_y_continuous(labels = function(x) paste0("$", x, "M")) +
  labs(title    = "Figura 5",
       subtitle = sprintf("Crédito transformado a COP con depreciación diferencial PCI. TRM inicial: $%s | Depreciación: %.2f%% anual.",
                          format(TRM_spot, big.mark=","), deprec_anual*100),
       x = "Trimestre", y = "Millones COP", fill = "") +
  theme_apa()

print(p_cop)

murcielago lindo


7. Ítem 1 — SET-FX: Curva Forward USD/COP

murcielago lindo

setfx <- data.frame(
  Plazo        = c("Spot Next", "3 Semanas", "21 Meses", "2 Años", "3 Años"),
  Meses        = c(0, 0.75, 21, 24, 36),
  Bid          = c(0.00, 3.00, 159.00, 195.00, 300.00),
  Ask          = c(0.042, 4.90, 176.83, 215.00, 350.00),
  Mid          = c(0.021, 3.95, 167.92, 205.00, 325.00)
)
setfx$TRM_Forward <- TRM_spot + setfx$Mid

kable(setfx,
      digits      = 3,
      format.args = list(big.mark = ","),
      caption     = "Tabla 3. Curva Forward USD/COP — Datos SET-FX (Investing.com, marzo 2026)",
      align       = c("l","c","r","r","r","r"),
      booktabs    = TRUE) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(setfx), extra_css = "border-bottom: 2px solid black;")
Tabla 3. Curva Forward USD/COP — Datos SET-FX (Investing.com, marzo 2026)
Plazo Meses Bid Ask Mid TRM_Forward
Spot Next 0.00 0 0.042 0.021 3,692.501
3 Semanas 0.75 3 4.900 3.950 3,696.430
21 Meses 21.00 159 176.830 167.920 3,860.400
2 Años 24.00 195 215.000 205.000 3,897.480
3 Años 36.00 300 350.000 325.000 4,017.480

murcielago lindo

TRM_fw_21m   <- TRM_spot + 167.92
dev_impl_21m <- (TRM_fw_21m / TRM_spot)^(12/21) - 1

cat("INSTRUMENTO SELECCIONADO: USD/COP 21M FWD\n")
## INSTRUMENTO SELECCIONADO: USD/COP 21M FWD
cat("-------------------------------------------\n")
## -------------------------------------------
cat(sprintf("  Plazo:                    21 meses (> 6 meses)\n"))
##   Plazo:                    21 meses (> 6 meses)
cat(sprintf("  TRM Spot:                $%s COP/USD\n", format(TRM_spot, big.mark=",")))
##   TRM Spot:                $3,692.48 COP/USD
cat(sprintf("  Puntos forward (Mid):     %.2f\n", 167.92))
##   Puntos forward (Mid):     167.92
cat(sprintf("  TRM Forward implícita:   $%.2f COP/USD\n", TRM_fw_21m))
##   TRM Forward implícita:   $3860.40 COP/USD
cat(sprintf("  Devaluación impl. anual:  %.2f%%\n", dev_impl_21m*100))
##   Devaluación impl. anual:  2.57%

murcielago lindo

setfx_plot <- setfx[setfx$Meses > 0, ]

p_setfx <- ggplot(setfx_plot, aes(x = Meses, y = TRM_Forward)) +
  geom_ribbon(aes(ymin = TRM_spot + Bid, ymax = TRM_spot + Ask),
              fill = "#1565C0", alpha = 0.18) +
  geom_line(color = "#1565C0", linewidth = 1.5) +
  geom_point(color = "#1565C0", size = 3.5) +
  geom_point(data = setfx_plot[setfx_plot$Meses == 21, ],
             color = "#F44336", size = 7, shape = 18) +
  geom_hline(yintercept = TRM_spot, linetype = "dashed", color = "gray50") +
  annotate("text", x = 22, y = TRM_fw_21m + 40,
           label = sprintf("Seleccionado: 21M\n$%.0f COP/USD", TRM_fw_21m),
           color = "#F44336", size = 3.5, hjust = 0, family = "serif") +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(title    = "Figura 6",
       subtitle = "Curva Forward USD/COP — SET-FX. Banda = spread Bid/Ask. Punto rojo = instrumento seleccionado (21 meses).",
       x = "Plazo (meses)", y = "TRM Forward (COP/USD)") +
  theme_apa()

print(p_setfx)

murcielago lindo


8. Tasas Forward PCI (4 Contratos de 1 Año)

murcielago lindo

forwards <- data.frame(
  FW_No      = 1:4,
  Periodo    = paste0("Año ", 5+(1:4), " → ", 6+(1:4)),
  Trimestres = paste0("T", c(21,25,29,33), "-T", c(24,28,32,36)),
  n_inicio   = 5 + (0:3),
  n_fin      = 6 + (0:3)
)
forwards$TRM_FW   <- TRM_spot * ((1 + i_COP)^forwards$n_fin /
                                  (1 + i_USD)^forwards$n_fin)
forwards$Dev_impl <- forwards$TRM_FW / TRM_spot - 1
forwards$Pts_fwd  <- forwards$TRM_FW - TRM_spot

kable(
  forwards[, c("FW_No","Periodo","Trimestres","TRM_FW","Dev_impl","Pts_fwd")],
  digits      = 2,
  format.args = list(big.mark = ","),
  col.names   = c("Forward","Período","Trimestres","TRM FW (COP)","Devalu. impl.","Puntos FW"),
  caption     = "Tabla 4. Tasas Forward calculadas por Paridad de Tasas de Interés (PCI)",
  align       = c("c","l","c","r","r","r"),
  booktabs    = TRUE
) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(forwards), extra_css = "border-bottom: 2px solid black;")
Tabla 4. Tasas Forward calculadas por Paridad de Tasas de Interés (PCI)
Forward Período Trimestres TRM FW (COP) Devalu. impl. Puntos FW
1 Año 6 → 7 T21-T24 4,418.62 0.20 726.14
2 Año 7 → 8 T25-T28 4,552.83 0.23 860.35
3 Año 8 → 9 T29-T32 4,691.12 0.27 998.64
4 Año 9 → 10 T33-T36 4,833.61 0.31 1,141.13
fw_por_trim <- rep(forwards$TRM_FW, each = 4)
fw_por_trim <- c(fw_por_trim, rep(tail(forwards$TRM_FW, 1), 4))
idx_fw      <- 21:40

murcielago lindo


9. Ítem 2 — Análisis Sistemático de Protección

murcielago lindo

cuota_cub_USD <- tabla$Cuota[idx_fw] * 0.75

prot_N <- sapply(seq_along(idx_fw), function(k)
  mean(sim_N[idx_fw[k], ] > fw_por_trim[k]))
prot_T <- sapply(seq_along(idx_fw), function(k)
  mean(sim_T[idx_fw[k], ] > fw_por_trim[k]))
gan_N  <- sapply(seq_along(idx_fw), function(k)
  mean((sim_N[idx_fw[k], ] - fw_por_trim[k]) * cuota_cub_USD[k]))
gan_T  <- sapply(seq_along(idx_fw), function(k)
  mean((sim_T[idx_fw[k], ] - fw_por_trim[k]) * cuota_cub_USD[k]))

analisis <- data.frame(
  Trimestre  = idx_fw,
  TRM_FW     = round(fw_por_trim, 0),
  Spot_med_N = round(apply(sim_N[idx_fw, ], 1, median), 0),
  Spot_med_T = round(apply(sim_T[idx_fw, ], 1, median), 0),
  pct_prot_N = round(prot_N * 100, 1),
  pct_prot_T = round(prot_T * 100, 1),
  gan_MM_N   = round(gan_N / 1e6, 3),
  gan_MM_T   = round(gan_T / 1e6, 3)
)

kable(analisis,
      col.names = c("Trim.","TRM FW (COP)","Spot Med (N)",
                    "Spot Med (T)","% Prot. N","% Prot. T",
                    "Gan. MM (N)","Gan. MM (T)"),
      caption   = "Tabla 5. Análisis sistemático de protección por trimestre — Normal vs t-Student",
      align     = c("c","r","r","r","r","r","r","r"),
      booktabs  = TRUE) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(analisis), extra_css = "border-bottom: 2px solid black;")
Tabla 5. Análisis sistemático de protección por trimestre — Normal vs t-Student
Trim. TRM FW (COP) Spot Med (N) Spot Med (T) % Prot. N % Prot. T Gan. MM (N) Gan. MM (T)
21 4419 4240 4242 44.2 43.0 -0.801 -0.787
22 4419 4293 4276 45.7 44.6 -0.771 -0.751
23 4419 4314 4300 46.0 45.6 -0.754 -0.726
24 4419 4345 4342 47.4 47.1 -0.739 -0.696
25 4553 4387 4370 44.4 43.9 -1.024 -0.994
26 4553 4418 4392 45.9 45.0 -0.998 -0.978
27 4553 4453 4412 46.5 45.3 -0.969 -0.967
28 4553 4465 4434 47.0 46.2 -0.953 -0.946
29 4691 4515 4476 44.5 43.6 -1.257 -1.256
30 4691 4549 4506 45.6 44.3 -1.246 -1.236
31 4691 4541 4539 46.2 45.4 -1.233 -1.218
32 4691 4586 4557 46.9 46.0 -1.216 -1.202
33 4834 4607 4615 0.0 0.0 -1.535 -1.513
34 4834 4649 4635 0.0 0.0 -1.514 -1.494
35 4834 4686 4665 0.0 0.0 -1.497 -1.482
36 4834 4692 4692 0.0 0.0 -1.478 -1.465
37 4834 4692 4692 0.0 0.0 -1.465 -1.450
38 4834 4692 4692 0.0 0.0 -1.452 -1.436
39 4834 4692 4692 0.0 0.0 -1.438 -1.415
40 4834 4692 4692 0.0 0.0 -1.423 -1.401

murcielago lindo

p_prot <- ggplot(analisis, aes(x = Trimestre)) +
  geom_col(aes(y = pct_prot_N, fill = "Normal"),
           position = position_dodge(width = 0.6), width = 0.5, alpha = 0.8) +
  geom_col(aes(y = pct_prot_T, fill = "t-Student"),
           position = position_dodge(width = 0.6), width = 0.5, alpha = 0.8) +
  geom_hline(yintercept = 50, linetype = "dashed", color = "#B71C1C", linewidth = 1) +
  annotate("text", x = 21.5, y = 53, label = "Umbral 50%",
           color = "#B71C1C", size = 3.5, family = "serif") +
  scale_fill_manual(values = c("Normal" = "#1565C0", "t-Student" = "#2E7D32")) +
  scale_y_continuous(limits = c(0, 100), labels = function(x) paste0(x, "%")) +
  labs(title    = "Figura 7",
       subtitle = sprintf("Porcentaje de escenarios donde el forward protege. n = 5,000 simulaciones. Promedio: Normal = %.1f%%, t-Student = %.1f%%.",
                          mean(prot_N)*100, mean(prot_T)*100),
       x = "Trimestre", y = "% escenarios protegidos", fill = "Distribución") +
  theme_apa()

print(p_prot)

murcielago lindo

sim_df <- bind_rows(
  data.frame(Trim = idx_fw,
             p05 = apply(sim_N[idx_fw,], 1, quantile, 0.05),
             p25 = apply(sim_N[idx_fw,], 1, quantile, 0.25),
             med = apply(sim_N[idx_fw,], 1, median),
             p75 = apply(sim_N[idx_fw,], 1, quantile, 0.75),
             p95 = apply(sim_N[idx_fw,], 1, quantile, 0.95),
             dist = "Normal"),
  data.frame(Trim = idx_fw,
             p05 = apply(sim_T[idx_fw,], 1, quantile, 0.05),
             p25 = apply(sim_T[idx_fw,], 1, quantile, 0.25),
             med = apply(sim_T[idx_fw,], 1, median),
             p75 = apply(sim_T[idx_fw,], 1, quantile, 0.75),
             p95 = apply(sim_T[idx_fw,], 1, quantile, 0.95),
             dist = "t-Student")
)
fw_line <- data.frame(Trim = idx_fw, TRM_FW = fw_por_trim)

p_spot_fw <- ggplot(sim_df[sim_df$dist == "t-Student", ], aes(x = Trim)) +
  geom_ribbon(aes(ymin = p05, ymax = p95, fill = "Rango 5%-95%"), alpha = 0.15) +
  geom_ribbon(aes(ymin = p25, ymax = p75, fill = "Rango 25%-75%"), alpha = 0.30) +
  geom_line(aes(y = med, color = "Spot mediana (t-Student)"), linewidth = 1.2) +
  geom_step(data = fw_line, aes(y = TRM_FW, color = "TRM Forward pactada"),
            linewidth = 1.6, linetype = "dashed") +
  scale_fill_manual(values = c("Rango 5%-95%"  = "#EF9A9A",
                                "Rango 25%-75%" = "#EF5350")) +
  scale_color_manual(values = c("Spot mediana (t-Student)" = "#B71C1C",
                                 "TRM Forward pactada"      = "#1A237E")) +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(title    = "Figura 8",
       subtitle = "TRM Spot simulada (t-Student) vs. tasa forward pactada. El forward protege cuando Spot > línea azul (T21–T40).",
       x = "Trimestre", y = "COP/USD", color = "", fill = "") +
  theme_apa()

print(p_spot_fw)

murcielago lindo


10. Ítem 3 — Flujo Total y Juicio de Conveniencia

murcielago lindo

flujo_sin <- tabla$Cuota * TRM_proy

flujo_con <- numeric(n_trim)
for (i in 1:n_trim) {
  if (i <= 20) {
    flujo_con[i] <- tabla$Cuota[i] * TRM_proy[i]
  } else {
    k            <- i - 20
    flujo_con[i] <- tabla$Cuota[i] * 0.75 * fw_por_trim[k] +
                    tabla$Cuota[i] * 0.25 * TRM_proy[i]
  }
}

delta <- flujo_sin - flujo_con

costo_sin  <- sum(flujo_sin) + cuota_ini_USD * TRM_spot
costo_con  <- sum(flujo_con) + cuota_ini_USD * TRM_spot
beneficio  <- costo_sin - costo_con

tasa_desc_trim <- (1 + i_COP)^(1/4) - 1
vp_ahorro      <- sum(delta[21:40] / (1 + tasa_desc_trim)^(21:40))

resumen_flujo <- data.frame(
  Concepto = c("Total pagado SIN forward (COP)",
               "Total pagado CON forward (COP)",
               "Beneficio neto del forward (COP)",
               "Beneficio como % del costo total",
               "VP del beneficio (i_COP = 10.25%)"),
  Valor    = c(format(round(costo_sin), big.mark = ","),
               format(round(costo_con), big.mark = ","),
               format(round(beneficio), big.mark = ","),
               paste0(round(beneficio / costo_sin * 100, 2), "%"),
               format(round(vp_ahorro), big.mark = ","))
)

kable(resumen_flujo,
      caption  = "Tabla 6. Resumen financiero: Costo del crédito con y sin cobertura forward",
      align    = c("l","r"),
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(resumen_flujo), extra_css = "border-bottom: 2px solid black;")
Tabla 6. Resumen financiero: Costo del crédito con y sin cobertura forward
Concepto Valor
Total pagado SIN forward (COP) 581,362,353
Total pagado CON forward (COP) 580,712,363
Beneficio neto del forward (COP) 649,990
Beneficio como % del costo total 0.11%
VP del beneficio (i_COP = 10.25%) 122,580

murcielago lindo

flujo_long <- bind_rows(
  data.frame(Trim = 1:n_trim, MM_COP = flujo_sin / 1e6, Tipo = "Sin Forward"),
  data.frame(Trim = 1:n_trim, MM_COP = flujo_con / 1e6, Tipo = "Con Forward (75%)")
)

p_flujo <- ggplot(flujo_long, aes(x = Trim, y = MM_COP, color = Tipo)) +
  geom_line(linewidth = 1.2) +
  geom_vline(xintercept = 20.5, linetype = "dotted", color = "gray40", linewidth = 1) +
  geom_ribbon(
    data = data.frame(
      Trim = 21:n_trim,
      ymin = pmin(flujo_sin[21:n_trim], flujo_con[21:n_trim]) / 1e6,
      ymax = pmax(flujo_sin[21:n_trim], flujo_con[21:n_trim]) / 1e6),
    aes(x = Trim, ymin = ymin, ymax = ymax),
    inherit.aes = FALSE, fill = "#A5D6A7", alpha = 0.4
  ) +
  annotate("text", x = 21.5, y = max(flujo_sin / 1e6) * 0.96,
           label = "Forward activo (75% cubierto)",
           hjust = 0, size = 3.5, color = "#2E7D32", family = "serif") +
  scale_color_manual(values = c("Sin Forward" = "#B71C1C", "Con Forward (75%)" = "#1565C0")) +
  scale_y_continuous(labels = function(x) paste0("$", round(x, 1), "M")) +
  labs(title    = "Figura 9",
       subtitle = "Flujo total de cuotas con y sin cobertura forward. Zona verde = ahorro generado por el forward.",
       x = "Trimestre", y = "Cuota (millones COP)", color = "") +
  theme_apa()

print(p_flujo)

murcielago lindo

flujo_anual <- data.frame(
  Año   = 1:10,
  Delta = tapply(delta, ceiling((1:n_trim) / 4), sum) / 1e6
)
flujo_anual$Acum <- cumsum(flujo_anual$Delta)

p_delta <- ggplot(flujo_anual, aes(x = Año)) +
  geom_col(aes(y = Delta, fill = Delta > 0), alpha = 0.85, width = 0.6) +
  geom_line(aes(y = Acum, color = "Ahorro acumulado"), linewidth = 1.3) +
  geom_point(aes(y = Acum, color = "Ahorro acumulado"), size = 3) +
  geom_hline(yintercept = 0, linewidth = 0.8) +
  scale_fill_manual(values = c("FALSE" = "#EF5350", "TRUE" = "#66BB6A"),
                    labels  = c("Costo adicional", "Ahorro"),
                    name    = "Impacto forward") +
  scale_color_manual(values = c("Ahorro acumulado" = "#1A237E")) +
  scale_x_continuous(breaks = 1:10) +
  scale_y_continuous(labels = function(x) paste0("$", x, "M COP")) +
  labs(title    = "Figura 10",
       subtitle = "Ahorro anual y acumulado del forward (millones COP). Verde = ahorro; Rojo = costo adicional.",
       x = "Año del crédito", y = "Millones COP", color = "") +
  theme_apa()

print(p_delta)

murcielago lindo


11. Veredicto Final

murcielago lindo

var_95 <- quantile(
  (sim_T[21, ] - fw_por_trim[1]) * tabla$Cuota[21] * 0.75,
  probs = 0.05
)

veredicto <- data.frame(
  Aspecto = c("Cobertura promedio (Normal)",
              "Cobertura promedio (t-Student)",
              "VaR 95% diferencial T21",
              "Beneficio neto forward",
              "VP del beneficio",
              "¿Fue conveniente?"),
  Resultado = c(
    paste0(round(mean(prot_N)*100, 1), "% de escenarios protegidos"),
    paste0(round(mean(prot_T)*100, 1), "% de escenarios protegidos"),
    paste0("$", format(round(var_95), big.mark=","), " COP/trimestre"),
    paste0("$", format(round(beneficio), big.mark=","), " COP"),
    paste0("$", format(round(vp_ahorro), big.mark=","), " COP"),
    ifelse(beneficio > 0,
           "SÍ — El forward reduce costo y riesgo cambiario",
           "Costo neto, pero reduce incertidumbre del flujo")
  )
)

kable(veredicto,
      caption  = "Tabla 7. Resumen ejecutivo — Juicio de conveniencia del forward",
      align    = c("l","l"),
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(veredicto), bold = TRUE,
           extra_css = "border-bottom: 2px solid black; background-color: #f5f5f5;")
Tabla 7. Resumen ejecutivo — Juicio de conveniencia del forward
Aspecto Resultado
Cobertura promedio (Normal) 27.5% de escenarios protegidos
Cobertura promedio (t-Student) 27% de escenarios protegidos
VaR 95% diferencial T21 $-3,751,545 COP/trimestre
Beneficio neto forward $649,990 COP
VP del beneficio $122,580 COP
¿Fue conveniente? SÍ — El forward reduce costo y riesgo cambiario

murcielago lindo

Bancolombia. (2025). Proyecciones económicas Colombia 2026. https://www.bancolombia.com/acerca-de/sala-prensa/noticias/economia-finanzas/proyecciones-economicas-colombia-2026