Paquetes necesarios

library(quantmod)
library(ggplot2)
library(knitr)
library(dplyr)
library(lubridate)
library(gridExtra)
library(readr)
library(tidyr)

Parte 1: Proceso del crédito

1. Realice un análisis fundamental de la TRM y cuál es la expectativa de los precios hasta dentro de un año (busque informes de proyección de la divisa)

# Obtener datos históricos de la TRM (USD/COP)
getSymbols("USDCOP=X", src = "yahoo", from = "2022-01-01", to = Sys.Date())
## [1] "USDCOP=X"
trm_daily <- Cl(`USDCOP=X`)
colnames(trm_daily) <- "TRM"

# Convertir a data frame para ggplot
df_trm <- data.frame(
  date = index(trm_daily),
  TRM = as.numeric(trm_daily$TRM)
)

# Gráfico de la TRM
ggplot(df_trm, aes(x = date, y = TRM)) +
  geom_line(color = "steelblue", linewidth = 0.8)+
  labs(
    title = "Histórico TRM USD/COP",
    x = "Fecha",
    y = "COP por USD"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5),
    axis.title.y = element_text(margin = margin(r = 10)),
    axis.title.x = element_text(margin = margin(t = 10))
  )

Análisis fundamental

Al mirar el gráfico histórico de la TRM desde 2022 se nota que el dólar ha sido muy volátil. Hubo un pico muy alto en 2022-2023, cuando llegó incluso por encima de los $4.800. Desde entonces ha tenido subidas y bajadas, pero parece que en los últimos meses ha bajado un poco y se mantiene en un rango más estable.

Factores internacionales que inciden en la TRM

  • Política monetaria de EE. UU.: La Reserva Federal ha mantenido tasas de interés altas para controlar la inflación. Esto fortalece al dólar, lo que hace que la TRM en Colombia suba.
  • Desaceleración económica global: Un menor crecimiento mundial reduce la demanda de materias primas, afectando países exportadores como Colombia y debilitando el peso.
  • Precio del petróleo: Cuando el petróleo está caro, entran más dólares a Colombia por exportaciones y el peso se fortalece; cuando baja, ocurre lo contrario.

Factores internos en Colombia

  • Tasas del Banco de la República: Actualmente en 9,25%. Si las bajan demasiado rápido, el peso podría perder atractivo frente a inversionistas extranjeros, presionando la TRM al alza.
  • Inflación y crecimiento económico: Una inflación controlada y expectativas de crecimiento moderado dan estabilidad a la moneda.
  • Riesgo político y fiscal: Las decisiones de política pública y las finanzas del gobierno afectan la confianza de inversionistas y pueden mover la TRM.

Expectativas del precio de la divisa

library(ggplot2)

# Crear el data frame con las expectativas
expectativas_trm <- data.frame(
  Fecha = c("Sep-2025", "Dic-2025", "Sep-2026", "Dic-2026", "Sep-2027"),
  Promedio = c(3968, 4056, 4053, 4061, 4090),
  Minimo = c(3900, 3869, 3391, 3522, 3555),
  Maximo = c(4113, 4250, 4350, 4400, 4500)
)

# Convertir Fecha en factor y crear columna de énfasis sin dplyr
expectativas_trm$Fecha <- factor(expectativas_trm$Fecha, levels = expectativas_trm$Fecha)
expectativas_trm$Enfasis <- ifelse(expectativas_trm$Fecha == "Sep-2026", "Sí", "No")

# Gráfico
ggplot(expectativas_trm, aes(x = Fecha, y = Promedio, color = Enfasis)) +
  geom_point(size = 3) +
  geom_errorbar(aes(ymin = Minimo, ymax = Maximo),
                width = 0.2, linewidth = 0.8) +
  scale_color_manual(values = c("Sí" = "firebrick", "No" = "steelblue")) +
  labs(
    title = "Expectativas de la TRM (USD/COP)",
    x = "Fecha",
    y = "COP por USD",
    caption = "Fuente: Encuesta de Expectativas BanRep (2025)",
    color = NULL
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5),
    axis.title.y = element_text(margin = margin(r = 10)),
    axis.title.x = element_text(margin = margin(t = 10)),
    plot.caption = element_text(hjust = 0, face = "italic", size = 10),
    legend.position = "none"
  )

Según la Encuesta de Expectativas de Analistas Económicos del Banco de la República (2025), el mercado espera que la TRM se mantenga entre $3.391 y $4.350 para los próximos doce meses con un valor promedio de $4.053

2.Calcule los retornos mensuales y la desviación estándar mensual de la TRM histórica

# Calcular retornos logarítmicos diarios
retornos_log <- diff(log(trm_daily))
retornos_log <- na.omit(retornos_log)

# Data frame de retornos
retornos_df <- data.frame(
  Fecha = index(retornos_log),
  Retorno = as.numeric(retornos_log)
)

# Agregar columna Año-Mes
retornos_df <- retornos_df %>%
  mutate(AñoMes = format(Fecha, "%Y-%m"))

# Calcular retornos mensuales (compuestos)
retornos_mes <- retornos_df %>%
  group_by(AñoMes) %>%
  summarise(
    RetornoMensual = exp(sum(Retorno, na.rm = TRUE)) - 1
  )

#TABLA 1: Resumen estadístico
promedio_mensual <- mean(retornos_mes$RetornoMensual, na.rm = TRUE)
desv_mensual <- sd(retornos_mes$RetornoMensual, na.rm = TRUE)

tabla_resumen <- data.frame(
  Estadístico = c("Promedio mensual", "Desviación estándar mensual"),
  Valor = c(round(promedio_mensual * 100, 3), round(desv_mensual * 100, 3))
)

kable(tabla_resumen, caption = "Resumen estadístico de retornos mensuales TRM (%)")
Resumen estadístico de retornos mensuales TRM (%)
Estadístico Valor
Promedio mensual -0.039
Desviación estándar mensual 3.469
#GRÁFICO: Retornos mensuales
ggplot(retornos_mes, aes(x = AñoMes, y = RetornoMensual * 100)) +
  geom_col(fill = "steelblue") +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Retornos mensuales TRM (USD/COP)",
    x = "Periodo",
    y = "Retorno mensual (%)"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 8, margin = margin(t = 5)),
    plot.title = element_text(face = "bold", hjust = 0.5)
  )

3.Realice simulación MGB mensuales de la TRM

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

# Parámetros iniciales desde el punto 2
s0 <- as.numeric(tail(trm_daily, 1))   # Último valor de la TRM
mu <- mean(retornos_mes$RetornoMensual, na.rm = TRUE)
sigma <- sd(retornos_mes$RetornoMensual, na.rm = TRUE)

# Parámetros de simulación
N <- 5000   # Número de trayectorias
T <- 120     # Horizonte: 120 meses
mb <- matrix(ncol = N, nrow = T)
mb[1, ] <- rep(s0, N)

# Simulación MBG mensual
for (i in 1:N) {
  for (t in 2:T) {
    mb[t, i] <- mb[t - 1, i] * exp((mu - 0.5 * sigma^2) + sigma * rnorm(1))
  }
}

#Gráfico de trayectorias simuladas
matplot(
  mb,
  type = "l",
  lty = 1,
  main = "Simulación MBG mensual TRM (USD/COP)",
  xlab = "Mes",
  ylab = "TRM simulada",
  lwd = 1
)

# Trayectoria promedio en rojo
lines(rowMeans(mb), col = "red", lwd = 2)

# Cálculo de percentiles (intervalo de confianza)
q1 <- quantile(mb[T, ], 0.975)
q2 <- quantile(mb[T, ], 0.025)

#Distribución final
plot(
  density(mb[T, ]),
  main = "Distribución simulada de TRM al mes 120",
  xlab = "TRM simulada",
  lwd = 2
)
abline(v = q1, col = "blue", lwd = 2)
abline(v = q2, col = "green", lwd = 2)

  • Simulación MBG mensual (gráfico 1): muestra la alta dispersión de escenarios de TRM a 120 meses. Hay trayectorias que se aprecian bajistas e incluso algunas muy alcistas, evidenciando alta volatilidad en el largo plazo.

  • Distribución de TRM al mes 120 (gráfico 2): la distribución es asimétrica a la derecha, con mayor probabilidad de observar TRM en el rango 2.500–4.000, pero con cola larga que implica riesgo de escenarios devaluatorios muy extremos (> 7.000).

4.Establezca una simulación de crédito de la maquinaria con la tasa extranjera, con pago de inicial del 10% del crédito. (En dólares)

# Parametros iniciales
trm_spot <- as.numeric(last(trm_daily))  
monto_cop <- 500000000                       
inicial_pct <- 0.10                        
precio_usd <- monto_cop / trm_spot  

# Condiciones del crédito
tasa_anual <- 0.06 #TASA NOMINAL MES VENCIDO       
tasa_mensual <- tasa_anual / 12  #TASA MENSUAL
n_periodos <- 10 * 12  # 10 años, pagos mensuales

# Monto financiado en USD
loan_usd <- precio_usd * (1 - inicial_pct)

# Fórmula de cuota fija (francés)
pago_mensual_usd <- (tasa_mensual * loan_usd) / (1 - (1 + tasa_mensual)^(-n_periodos))

# Creación de tabla de amortización
amort <- data.frame(
  periodo = 1:n_periodos,
  saldo = numeric(n_periodos),
  interes = numeric(n_periodos),
  principal = numeric(n_periodos),
  pago = rep(pago_mensual_usd, n_periodos)
)

saldo <- loan_usd
for (t in 1:n_periodos) {
  interes_t <- saldo * tasa_mensual
  principal_t <- pago_mensual_usd - interes_t
  saldo <- saldo - principal_t
  amort$saldo[t] <- max(saldo, 0)
  amort$interes[t] <- interes_t
  amort$principal[t] <- principal_t
}

# Mostrar los primeros 10 periodos de la amortización
head(amort, 10) %>%
  knitr::kable(digits = 2, caption = "Amortización tipo francés — primeros 10 pagos")
Amortización tipo francés — primeros 10 pagos
periodo saldo interes principal pago
1 115838.9 582.75 711.19 1293.95
2 115124.2 579.19 714.75 1293.95
3 114405.9 575.62 718.32 1293.95
4 113683.9 572.03 721.92 1293.95
5 112958.4 568.42 725.53 1293.95
6 112229.2 564.79 729.15 1293.95
7 111496.4 561.15 732.80 1293.95
8 110760.0 557.48 736.46 1293.95
9 110019.8 553.80 740.15 1293.95
10 109276.0 550.10 743.85 1293.95
# Mostrar resumen total en tabla
total_pagado <- sum(amort$pago)
total_interes <- sum(amort$interes)

resumen_credito <- data.frame(
  Concepto = c("Monto financiado (USD)",
               "Pago mensual (USD)",
               "Total pagado en 10 años (USD)",
               "Interés total pagado (USD)"),
  Valor = c(round(loan_usd, 2),
            round(pago_mensual_usd, 2),
            round(total_pagado, 2),
            round(total_interes, 2))
)

knitr::kable(resumen_credito,
             digits = 2,
             caption = "Resumen del crédito en dólares — Sistema francés")
Resumen del crédito en dólares — Sistema francés
Concepto Valor
Monto financiado (USD) 116550.12
Pago mensual (USD) 1293.95
Total pagado en 10 años (USD) 155273.43
Interés total pagado (USD) 38723.31

5.Con los valores del punto 3 recree el crédito en pesos. Analice sobre el comportamiento del crédito transformado a pesos.

# Crear tabla de pagos en USD
pagos_usd <- amort$pago

# Convertir cada pago mensual a COP según cada trayectoria simulada
pagos_cop_sim <- matrix(nrow = nrow(amort), ncol = ncol(mb))
for (i in 1:ncol(mb)) {
  pagos_cop_sim[, i] <- pagos_usd * mb[, i]  # usa cada TRM simulada mes a mes
}

# Calcular métricas de interés
total_cop_sim <- colSums(pagos_cop_sim)  # total pagado en COP por simulación

# Resumen de percentiles y media
resumen_cop <- data.frame(
  Escenario = c("Pesimista (p95)", "Promedio", "Optimista (p5)"),
  TotalPagadoCOP = c(
    quantile(total_cop_sim, 0.95),
    mean(total_cop_sim),
    quantile(total_cop_sim, 0.05)
  )
)

knitr::kable(resumen_cop, digits = 0,
             caption = "Total pagado en COP bajo diferentes escenarios de TRM")
Total pagado en COP bajo diferentes escenarios de TRM
Escenario TotalPagadoCOP
95% Pesimista (p95) 829387334
Promedio 588301179
5% Optimista (p5) 407251655
# Histograma de los pagos totales en COP (valores completos)
hist(total_cop_sim,   
     breaks = 50,               
     col = "skyblue",           
     border = "white",          
     main = "Distribución de pagos totales en COP",  
     xlab = "Total pagado (COP)",          
     ylab = "Frecuencia")       
## Warning in breaks[-1L] + breaks[-nB]: NAs producidos por enteros excedidos
# Añadir líneas verticales para media y percentiles
media <- mean(total_cop_sim)
p5 <- quantile(total_cop_sim, 0.05)
p95 <- quantile(total_cop_sim, 0.95)

abline(v = media, col = "orange", lwd = 2, lty = 2)
abline(v = p5, col = "green", lwd = 2, lty = 2)
abline(v = p95, col = "red", lwd = 2, lty = 2)

legend("topright", 
       legend = c("Media", "Optimista (p5)", "Pesimista (p95)"),
       col = c("orange", "green", "red"), lwd = 2, lty = 2)

El histograma muestra la distribución de los pagos totales del crédito en pesos colombianos bajo diferentes trayectorias simuladas de la TRM. La línea naranja punteada indica el pago promedio que representa el valor esperado del total a pagar considerando la volatilidad histórica de la TRM. La línea verde (p5) marca un escenario optimista, en el que los pagos totales podrían ser más bajos que el valor promedio, mientras que la línea roja (p95) representa un escenario pesimista, donde los pagos podrían ascender más allá del valor promedio. Esto evidencia que existe un rango de incertidumbre significativo, reflejando la sensibilidad del crédito frente a la variación del tipo de cambio.

Parte 2: Proceso del futuro

Futuro elegido en BVC: TRXU25F

Tamaño del contrato (USD): 1.000

2.1 Use la información de la BVC de un futuro de su preferencia, use toda la información histórica presentada del producto para calcular los retornos y la desviación estándar mensual.

#Cargar datos
datos_futuro <- read_csv2(file.choose())  
## ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 37 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ";"
## chr (2): Fecha, Contrato
## num (1): Precio cierre
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
datos_futuro <- datos_futuro %>%
  mutate(
    Fecha = dmy(Fecha),
    Precio_cierre = as.numeric(gsub(",", ".", `Precio cierre`))
  ) %>%
  arrange(Fecha) 

#2. Gráfico histórico
ggplot(datos_futuro, aes(x = Fecha, y = Precio_cierre)) +
  geom_line(color = "darkgreen", linewidth = 1) +
  geom_point(color = "black", size = 1) +
  labs(
    title = "Histórico Precio Futuro sobre TRM TRXU25F",
    x = "Fecha",
    y = "Precio de Cierre"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5)
  )

#3. Cálculo de retornos mensuales y desviación estándar

# Crear columna de mes y año para agrupar
datos_futuro <- datos_futuro %>%
  mutate(
    ano_mes = floor_date(Fecha, "month")
  )

# Calcular retornos logarítmicos diarios
datos_futuro <- datos_futuro %>%
  mutate(
    retorno_diario = log(Precio_cierre / lag(Precio_cierre))
  )

# Calcular retornos mensuales (suma de log-retornos dentro de cada mes)
retornos_mensuales <- datos_futuro %>%
  group_by(ano_mes) %>%
  summarise(retorno_mensual = sum(retorno_diario, na.rm = TRUE))

# Calcular estadísticas
promedio_mensual <- mean(retornos_mensuales$retorno_mensual, na.rm = TRUE)
desviacion_mensual <- sd(retornos_mensuales$retorno_mensual, na.rm = TRUE)

# Mostrar resultados
cat("Promedio de retornos mensuales: ", round(promedio_mensual, 6), "\n")
## Promedio de retornos mensuales:  -0.009205
cat("Desviación estándar mensual: ", round(desviacion_mensual, 6), "\n")
## Desviación estándar mensual:  0.024115

2.2 Simule por medio de MBG mensual el comportamiento del futuro

set.seed(123)

# Variables de entrada
tasa_usd_anual <- 0.05   # rf usa
tasa_cop_anual <- 0.12   # rf colombia
horizonte_meses <- 120   # 10 años
n_caminos <- 5000
spot_ultimo <- tail(datos_futuro$Precio_cierre, 1)

#Calcular forward para el primer mes (T = 1/12 años)
T1 <- 1/12
forward_primer_mes <- spot_ultimo * exp((tasa_cop_anual - tasa_usd_anual) * T1)

# Simulación múltiple MBG pero: fijamos t = 1 (primer mes simulado) = forward_primer_mes
mu_mensual <- promedio_mensual
sigma_mensual <- desviacion_mensual
n_meses <- horizonte_meses

# Matriz de simulaciones
matriz_sim <- matrix(NA, nrow = n_meses + 1, ncol = n_caminos)
matriz_sim[1, ] <- spot_ultimo
matriz_sim[2, ] <- forward_primer_mes

# Generar los demás pasos a partir de t=2 -> t=n_meses
for (j in 1:n_caminos) {
  for (i in 3:(n_meses + 1)) {
    epsilon <- rnorm(1)
    matriz_sim[i, j] <- matriz_sim[i - 1, j] *
      exp((mu_mensual - 0.5 * sigma_mensual^2) + sigma_mensual * epsilon)
  }
}

# Data frame largo para ggplot
fechas_sim <- seq(from = max(datos_futuro$Fecha) %m+% months(1),
                  by = "1 month",
                  length.out = n_meses + 1)

df_sim <- data.frame(
  Fecha = rep(fechas_sim, times = n_caminos),
  Precio_sim = as.vector(matriz_sim),
  Camino = rep(1:n_caminos, each = n_meses + 1)
)

#Promedio por fecha (línea destacada)
df_prom <- df_sim %>%
  group_by(Fecha) %>%
  summarise(Promedio = mean(Precio_sim, na.rm = TRUE))

#Modelo simple de rollover mensual (precios de contrato near-month)
serie_promedio_spot <- df_prom$Promedio
n <- length(serie_promedio_spot)

# Calcular forwards 1m sobre la serie promedio de spot
forwards_1m <- serie_promedio_spot * exp((tasa_cop_anual - tasa_usd_anual) * (1/12))

# Modelación rollover
precio_apertura_contrato <- forwards_1m[1:(n-1)]    
precio_cierre_contrato  <- serie_promedio_spot[2:n]
pnl_rollover <- precio_cierre_contrato - precio_apertura_contrato

df_rollover <- data.frame(
  Fecha_apertura = fechas_sim[1:(n-1)],
  Apertura = precio_apertura_contrato,
  Cierre = precio_cierre_contrato,
  PnL = pnl_rollover
)

# Gráfico: todas las trayectorias (gris), promedio (azul) y bloque años 6-10 resaltado ---
inicio_rango <- fechas_sim[1] %m+% years(5)
fin_rango <- fechas_sim[1] %m+% years(10)

ggplot() +
  annotate("rect", xmin = inicio_rango, xmax = fin_rango, ymin = -Inf, ymax = Inf,
           alpha = 0.18, fill = "orange") +
  geom_line(data = df_sim, aes(x = Fecha, y = Precio_sim, group = Camino, color = "Trayectorias"),
            alpha = 0.28, linewidth = 0.4) +
  geom_line(data = df_prom, aes(x = Fecha, y = Promedio, color = "Promedio"), linewidth = 1.1) +
  geom_point(data = data.frame(Fecha = fechas_sim[2], Valor = forward_primer_mes),
             aes(x = Fecha, y = Valor, color = "Forward 1m (primer mes)"), size = 2) +
  scale_color_manual(values = c("Trayectorias" = "gray",
                                "Promedio" = "steelblue",
                                "Forward 1m (primer mes)" = "red")) +
  labs(title = "Simulación BMG del futuro TRM — 10 años (años 6–10 resaltados)",
       subtitle = "Forward 1er mes usado como insumo inicial; rollover mensual modelado sobre serie promedio",
       x = "Fecha", y = "Precio") +
  theme_minimal() +
  theme(legend.position = "top", plot.title = element_text(face = "bold", hjust = 0.5))

2.3 Establezca los criterios de exposición total, margen inicial, margen de mantenimiento según la información de la BVC.

# Supuestos iniciales (traídos de la parte 1)
trm_spot <- as.numeric(last(trm_daily))  # TRM spot al momento de la compra
monto_cop <- 500000000                  # 500 millones COP
inicial_pct <- 0.10                     # 10% de cuota inicial

# Valor de la maquinaria en USD
precio_usd <- monto_cop / trm_spot

# Monto financiado en USD (90% restante)
loan_usd <- precio_usd * (1 - inicial_pct)

# Cobertura: 75% del monto financiado
porc_cubierto <- 0.75
exposicion_usd <- loan_usd * porc_cubierto

# Parámetros del contrato de futuros
tamano_contrato_usd <- 1000             # Tamaño del contrato
margen_inicial_pct <- 0.08              # 8%
margen_mantenimiento_pct <- 0.03        # 3%

# Número de contratos (usando ceiling para cubrir >=75%)
numero_contratos <- ceiling(exposicion_usd / tamano_contrato_usd)

# Valor nocional de la posición
valor_nocional_usd <- numero_contratos * tamano_contrato_usd

# Márgenes en USD
margen_inicial_usd <- valor_nocional_usd * margen_inicial_pct
margen_mantenimiento_usd <- valor_nocional_usd * margen_mantenimiento_pct

# Mostrar resultados
cat("Valor total maquinaria: ", round(precio_usd, 2), "USD\n")
## Valor total maquinaria:  129500.1 USD
cat("Monto financiado (90%): ", round(loan_usd, 2), "USD\n")
## Monto financiado (90%):  116550.1 USD
cat("Exposición cubierta (75%): ", round(exposicion_usd, 2), "USD\n")
## Exposición cubierta (75%):  87412.59 USD
cat("Número de contratos necesarios: ", numero_contratos, "\n")
## Número de contratos necesarios:  88
cat("Valor nocional cubierto: ", round(valor_nocional_usd, 2), "USD\n")
## Valor nocional cubierto:  88000 USD
cat("Margen inicial requerido: ", round(margen_inicial_usd, 2), "USD\n")
## Margen inicial requerido:  7040 USD
cat("Margen de mantenimiento: ", round(margen_mantenimiento_usd, 2), "USD\n")
## Margen de mantenimiento:  2640 USD

2.4 Genere un flujo de caja de margen sobre los últimos 4 años de pago sobre la TRM y establezca procesos de rollover y en cada liquidación cambie de posición según su expectativa de mercado, analice y justifique sobre estos cambios.

#Selección de últimos 48 meses
n_total <- nrow(df_prom)
inicio <- n_total - 48
fin <- n_total

serie_precio <- df_prom$Promedio
fechas <- df_prom$Fecha

#Inicialización
saldo_margen <- margen_inicial_usd
contratos_actuales <- numero_contratos
valor_nocional_usd <- contratos_actuales * tamano_contrato_usd

#Data frame para almacenar resultados
registro <- data.frame(
  Fecha = as.Date(character()),
  Contratos = integer(),
  Precio_mes = numeric(),
  Precio_sig_mes = numeric(),
  PnL_USD = numeric(),
  Saldo_margen_antes = numeric(),
  Llamada_margen = numeric(),
  Ajuste_posicion = numeric(),
  Saldo_margen_despues = numeric(),
  stringsAsFactors = FALSE
)

# Medias móviles para decisión de rollover
media_corta <- stats::filter(serie_precio, rep(1/3, 3), sides = 1)
media_larga <- stats::filter(serie_precio, rep(1/12, 12), sides = 1)

for(i in inicio:(fin - 1)) {
  fecha <- fechas[i]
  precio_t <- serie_precio[i]
  precio_t1 <- serie_precio[i + 1]
  
  # P&L en COP y luego en USD
  pnl_cop_contrato <- (precio_t1 - precio_t) * tamano_contrato_usd
  pnl_usd_contrato <- pnl_cop_contrato / precio_t1
  pnl_total_usd <- pnl_usd_contrato * contratos_actuales
  
  saldo_antes <- saldo_margen
  saldo_margen <- saldo_margen + pnl_total_usd
  
  # Verificar llamada de margen
  llamada_margen <- 0
  if(saldo_margen < margen_mantenimiento_usd) {
    llamada_margen <- margen_inicial_usd - saldo_margen
    saldo_margen <- saldo_margen + llamada_margen
  }
  
  # Regla de cambio de posición
  ajuste_posicion <- 0
  ma_corta <- media_corta[i + 1]
  ma_larga <- media_larga[i + 1]
  
  if(!is.na(ma_corta) && !is.na(ma_larga)) {
    nuevos_contratos <- contratos_actuales
    if(ma_corta > ma_larga) {
      nuevos_contratos <- contratos_actuales + 1
    } else if(ma_corta < ma_larga) {
      nuevos_contratos <- max(0, contratos_actuales - 1)
    }
    
    if(nuevos_contratos != contratos_actuales) {
      delta_nocional <- (nuevos_contratos - contratos_actuales) * tamano_contrato_usd
      ajuste_posicion <- delta_nocional * 0.08
      saldo_margen <- saldo_margen - ajuste_posicion
      
      contratos_actuales <- nuevos_contratos
      valor_nocional_usd <- contratos_actuales * tamano_contrato_usd
      margen_inicial_usd <- valor_nocional_usd * 0.08
      margen_mantenimiento_usd <- valor_nocional_usd * 0.03
      
      if(saldo_margen < margen_mantenimiento_usd) {
        extra_call <- margen_inicial_usd - saldo_margen
        llamada_margen <- llamada_margen + extra_call
        saldo_margen <- saldo_margen + extra_call
      }
    }
  }
  
  # Registrar fila
  registro <- registro %>% add_row(
    Fecha = fecha,
    Contratos = contratos_actuales,
    Precio_mes = precio_t,
    Precio_sig_mes = precio_t1,
    PnL_USD = pnl_total_usd,
    Saldo_margen_antes = saldo_antes,
    Llamada_margen = llamada_margen,
    Ajuste_posicion = -ajuste_posicion, # negativo si es depósito
    Saldo_margen_despues = saldo_margen
  )
}

# Calcular flujo neto
registro <- registro %>%
  mutate(
    Flujo_PnL = PnL_USD,
    Flujo_Llamada = -Llamada_margen,
    Flujo_Ajuste = Ajuste_posicion,
    Flujo_Neto = Flujo_PnL + Flujo_Llamada + Flujo_Ajuste
  )

# Mostrar primeras filas
print(head(registro, 12))
##         Fecha Contratos Precio_mes Precio_sig_mes   PnL_USD Saldo_margen_antes
## 1  2031-10-09        87   2065.560       2047.227 -788.0342           7040.000
## 2  2031-11-09        86   2047.227       2028.193 -816.4531           6331.966
## 3  2031-12-09        85   2028.193       2008.171 -857.4497           5595.513
## 4  2032-01-09        84   2008.171       1988.154 -855.8240           4818.063
## 5  2032-02-09        83   1988.154       1969.458 -797.4087           4042.239
## 6  2032-03-09        82   1969.458       1951.143 -779.0900           3324.830
## 7  2032-04-09        81   1951.143       1933.258 -758.5774           2625.740
## 8  2032-05-09        80   1933.258       1916.551 -706.0930           6640.000
## 9  2032-06-09        79   1916.551       1898.805 -747.6892           6013.907
## 10 2032-07-09        78   1898.805       1880.345 -775.5687           5346.218
## 11 2032-08-09        77   1880.345       1863.365 -710.7726           4650.649
## 12 2032-09-09        76   1863.365       1846.478 -704.2104           4019.876
##    Llamada_margen Ajuste_posicion Saldo_margen_despues Flujo_PnL Flujo_Llamada
## 1           0.000              80             6331.966 -788.0342         0.000
## 2           0.000              80             5595.513 -816.4531         0.000
## 3           0.000              80             4818.063 -857.4497         0.000
## 4           0.000              80             4042.239 -855.8240         0.000
## 5           0.000              80             3324.830 -797.4087         0.000
## 6           0.000              80             2625.740 -779.0900         0.000
## 7        4692.837              80             6640.000 -758.5774     -4692.837
## 8           0.000              80             6013.907 -706.0930         0.000
## 9           0.000              80             5346.218 -747.6892         0.000
## 10          0.000              80             4650.649 -775.5687         0.000
## 11          0.000              80             4019.876 -710.7726         0.000
## 12          0.000              80             3395.666 -704.2104         0.000
##    Flujo_Ajuste Flujo_Neto
## 1            80  -708.0342
## 2            80  -736.4531
## 3            80  -777.4497
## 4            80  -775.8240
## 5            80  -717.4087
## 6            80  -699.0900
## 7            80 -5371.4145
## 8            80  -626.0930
## 9            80  -667.6892
## 10           80  -695.5687
## 11           80  -630.7726
## 12           80  -624.2104
# --- Gráficos ---
# 1. Saldo de margen
g1 <- ggplot(registro, aes(x = Fecha, y = Saldo_margen_despues)) +
  geom_line(color = "steelblue", size = 1) +
  geom_hline(yintercept = max(margen_inicial_usd, 0), linetype = "dashed", color = "darkgreen") +
  geom_hline(yintercept = max(margen_mantenimiento_usd, 0), linetype = "dotted", color = "red") +
  labs(title = "Saldo de la cuenta de margen (USD)",
       y = "Saldo en margen (USD)",
       x = "") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# 2. Flujo neto
g2 <- ggplot(registro, aes(x = Fecha, y = Flujo_Neto)) +
  geom_col(fill = "gray") +
  labs(title = "Flujos netos mensuales (USD)",
       y = "Flujo neto",
       x = "") +
  theme_minimal()

# 3. Número de contratos
g3 <- ggplot(registro, aes(x = Fecha, y = Contratos)) +
  geom_step(color = "orange", size = 1) +
  labs(title = "Número de contratos por mes",
       y = "Contratos",
       x = "") +
  theme_minimal()

library(gridExtra)
grid.arrange(g1, g2, g3, ncol = 1)

Comportamiento del saldo de margen

El gráfico superior muestra un patrón cíclico donde el saldo de la cuenta de margen va cayendo hasta niveles cercanos al margen de mantenimiento, lo que genera llamados de margen recurrentes para volver al margen inicial.La frecuencia de las llamadas al margen indica que el movimiento del subyacente ha estado en contra de la cobertura en varias ocasiones, pero no en un grado extremo que vacíe la cuenta por completo. Esto muestra que el tamaño de la posición y el margen inicial absorben la volatilidad sin disparar demasiadas llamadas de margen.

Flujos netos mensuales

Los flujos negativos se concentran en meses de fuertes movimientos adversos, lo que coincide con los eventos de ajuste de margen. La magnitud de estos flujos (algunos meses cercanos a -4.000 USD) es relevante porque impacta la liquidez de la empresa. Sin embargo, los meses intermedios muestran flujos pequeños o cercanos a cero, lo que indica que la estrategia de cobertura no genera pérdidas constantes sino que responde a episodios específicos de volatilidad.

Número de contratos y Rollover

La línea descendente en el gráfico inferior refleja una reducción gradual del número de contratos, lo que es consistente con la amortización del crédito subyacente.Cada rollover ajusta la posición para evitar sobrecobertura, ya que evita pagar margen innecesario sobre nocional que ya no corresponde a deuda viva.

2.5 Revise y compare en un flujo total sobre que tanto se cubre los precios del crédito vs el futuro. Analice y justifique si la inversión del futuro fue beneficioso o no sobre la inversión de la maquinaria amarilla.

# Asegurar formato fechas en registro
registro$Fecha <- as.Date(registro$Fecha)
registro <- registro %>%
  mutate(TRM_mes = Precio_sig_mes)

# Calcular flujo neto de margen en COP 
registro <- registro %>%
  mutate(
    FlujoNeto_COP = Flujo_Neto * TRM_mes 
  )

# Calcular pago del crédito en COP
registro <- registro %>%
  mutate(
    PagoCredito_USD = pago_mensual_usd,
    PagoCredito_COP = PagoCredito_USD * TRM_mes
  )

# Flujo mensual sin cobertura (solo pago del crédito en COP)
registro <- registro %>%
  mutate(
    Flujo_sin_cobertura_COP = -PagoCredito_COP
  )

# Flujo mensual con cobertura: pago del crédito menos efecto del futuro
registro <- registro %>%
  mutate(
    Flujo_con_cobertura_COP = -PagoCredito_COP - (-FlujoNeto_COP) 
  ) %>%
  mutate(
    Flujo_con_cobertura_COP = -PagoCredito_COP + FlujoNeto_COP
  )

# Resumen
registro <- registro %>%
  arrange(Fecha) %>%
  mutate(
    Acum_sin_cobertura = cumsum(Flujo_sin_cobertura_COP),
    Acum_con_cobertura = cumsum(Flujo_con_cobertura_COP),
    Dif_mensual = Flujo_con_cobertura_COP - Flujo_sin_cobertura_COP,
    Acum_ahorro = Acum_sin_cobertura - Acum_con_cobertura  # positivo si hedge ahorra COP
  )

# Tabla resumen: totales en período
total_sin <- last(registro$Acum_sin_cobertura)
total_con <- last(registro$Acum_con_cobertura)
ahorro_total <- total_sin - total_con
porc_ahorro <- ahorro_total / abs(total_sin) * 100

resumen <- data.frame(
  Item = c("Costo total sin cobertura (COP)", "Costo total con cobertura (COP)", "Ahorro neto (COP)", "Ahorro neto (%)"),
  Valor = c(total_sin, total_con, ahorro_total, porc_ahorro)
)

# Mostrar resumen con formato
resumen$Valor <- round(resumen$Valor, 0)
print(resumen)
##                              Item      Valor
## 1 Costo total sin cobertura (COP) -103210427
## 2 Costo total con cobertura (COP) -180390593
## 3               Ahorro neto (COP)   77180166
## 4                 Ahorro neto (%)         75
#Gráficos

# 1) Serie mensual: pago crédito (COP), efecto hedge (FlujoNeto_COP), y neto
g_mes <- ggplot(registro, aes(x = Fecha)) +
  geom_col(aes(y = -PagoCredito_COP, fill = "Pago crédito (COP)"), width = 25) +
  geom_line(aes(y = FlujoNeto_COP, color = "Flujos netos futuros (COP)"), size = 1) +
  geom_line(aes(y = Flujo_con_cobertura_COP, color = "Flujo neto con cobertura (COP)"), size = 1, linetype = "dashed") +
  scale_y_continuous(labels = scales::comma) +
  scale_color_manual(name = "", values = c("Flujos netos futuros (COP)" = "darkgreen", "Flujo neto con cobertura (COP)" = "steelblue")) +
  scale_fill_manual(name = "", values = c("Pago crédito (COP)" = "gray70")) +
  labs(title = "Comparación mensual: costo crédito vs flujos futuros (COP)",
       subtitle = "Columnas: pago crédito mensual en COP (negativo). Línea verde: flujos netos del futuro. Línea punteada: costo neto con cobertura.",
       x = "", y = "COP") +
  theme_minimal() +
  theme(legend.position = "top")

# 2) Acumulado comparativo
df_acum <- registro %>%
  select(Fecha, Acum_sin_cobertura, Acum_con_cobertura) %>%
  pivot_longer(cols = c(Acum_sin_cobertura, Acum_con_cobertura),
               names_to = "Escenario",
               values_to = "Acumulado") %>%
  mutate(Escenario = ifelse(Escenario == "Acum_sin_cobertura", "Sin cobertura", "Con cobertura"))

g_acum <- ggplot(df_acum, aes(x = Fecha, y = Acumulado, color = Escenario)) +
  geom_line(size = 1) +
  scale_y_continuous(labels = scales::comma) +
  scale_color_manual(values = c("Sin cobertura" = "firebrick", "Con cobertura" = "steelblue")) +
  labs(title = "Costo acumulado en COP: sin cobertura vs con cobertura",
       subtitle = paste0("Periodo: ", format(first(registro$Fecha), "%Y-%m"), " a ", format(last(registro$Fecha), "%Y-%m")),
       x = "", y = "COP") +
  theme_minimal() +
  theme(legend.position = "top")

# ostrar gráficos
print(g_mes)

print(g_acum)

#Resumen
cat("\nResumen numérico (últimos 48 meses):\n")
## 
## Resumen numérico (últimos 48 meses):
cat("Costo total SIN cobertura (COP): ", format(round(total_sin,0), big.mark = ","), "\n")
## Costo total SIN cobertura (COP):  -103,210,427
cat("Costo total CON cobertura (COP): ", format(round(total_con,0), big.mark = ","), "\n")
## Costo total CON cobertura (COP):  -180,390,593
cat("Ahorro neto aportado por la cobertura (COP): ", format(round(ahorro_total,0), big.mark = ","), "\n")
## Ahorro neto aportado por la cobertura (COP):  77,180,166
cat("Ahorro neto porcentual sobre costo sin cobertura: ", round(porc_ahorro,2), "%\n\n")
## Ahorro neto porcentual sobre costo sin cobertura:  74.78 %

Análisis:

El primer gráfico muestra que los flujos netos con cobertura (línea azul) son más negativos que los pagos del crédito y que los propios flujos de futuros (línea verde). Esto evidencia que las liquidaciones de los contratos generaron salidas de caja adicionales en la mayoría de los meses, especialmente en los de rollover, donde los picos se hacen más pronunciados. En términos prácticos, la cobertura no suavizó la serie de pagos, sino que aumentó su volatilidad y profundizó las pérdidas en varios momentos.

El segundo gráfico refuerza esta conclusión: el costo acumulado con cobertura (línea azul) cae a un ritmo más acelerado que el escenario sin cobertura (línea roja), ampliando la brecha entre ambos escenarios. Esto significa que, a lo largo del tiempo, la cobertura fue generando un efecto financiero adverso, sumando costo en lugar de compensar el riesgo de tipo de cambio.

Conclusiones

  • El cálculo de retornos y desviaciones mensuales permitió entender la volatilidad del futuro TRM y fue una base sólida para la simulación de escenarios.

  • Las trayectorias de MBG mostraron un rango amplio de posibles precios, y la línea promedio permitió ver la tendencia esperada

  • Exposición y márgenes claros: Se cuantificó la exposición necesaria para cubrir el 75% de la deuda en USD y se incorporaron márgenes iniciales y de mantenimiento, mostrando el impacto real en liquidez.

  • La simulación de la cuenta de margen evidenció la necesidad de capital adicional para mantener la posición y ajustar la cobertura según la expectativa de mercado.

  • Aunque la cobertura protegió contra escenarios adversos de TRM, el costo total fue mayor y generó flujos de caja más exigentes

Referencias

Banco de la República de Colombia. (2025). Resultados mensuales: Expectativas de analistas económicos. Recuperado de https://www.banrep.gov.co/es/resultados-mensuales-expectativas-analistas-economicos

Investing.com. (2025). U.S. 10-Year Bond Yield. Recuperado de https://www.investing.com/rates-bonds/u.s.-10-year-bond-yield-streaming-chart

Investing.com. (2025). Colombia 10-Year Bond Yield. Recuperado de https://www.investing.com/rates-bonds/colombia-10-year-bond-yield

Bolsa de Valores de Colombia. (2025). TRXU25F — resumen [Mercado de derivados]. Recuperado de https://www.bvc.com.co/derivados/trxu25f?tab=resumen