Introducción

El presente informe tiene como objetivo analizar el impacto de la variación cambiaria y el uso de instrumentos derivados en la financiación de la compra de maquinaria amarilla. A partir de la simulación de escenarios con crédito en dólares y su posterior cobertura mediante futuros de divisas, buscamos evaluar cómo las decisiones financieras pueden influir en el costo real de la deuda. El ejercicio nos permitió trabajar con trayectorias de tipo de cambio y construir flujos de caja en distintos horizontes de tiempo, lo que facilita entender la dinámica del riesgo cambiario. Más allá de los cálculos, la intención fue reflexionar como inversionistas sobre la importancia de anticipar los movimientos del mercado y tomar decisiones estratégicas que protejan el patrimonio y la sostenibilidad financiera de un proyecto.

Parte 1. Proceso del crédito

1. Análisis fundamental de la TRM

La TRM es el valor de referencia del dólar en Colombia y su precio cambia constantemente debido a diversos factores de la economía. Su volatilidad se explica principalmente por la inflación, las tasas de interés que define el Banco de la República, el comportamiento de las exportaciones e importaciones y los precios de materias primas como el petróleo, que hacen que el dólar suba o baje frente al peso colombiano con frecuencia (Banco de la República, 2025). Según el panel económico del Congreso Empresarial Colombiano, se espera que el dólar se ubique alrededor de $4.050 para 2026, aunque seguirá mostrando cierta volatilidad por el crecimiento económico más bajo de lo esperado, las decisiones de política monetaria y la incertidumbre de los mercados internacionales (República, 2025).

Complementando esta información, Traders Union proyecta que la TRM para finales de 2025 será de COP 4.119,36 (Union, 2025), mientras que el informe de Bancolombia indica que el tipo de cambio USD-COP podría cerrar el año con un promedio de COP 4.237, debido a riesgos fiscales, una posible rebaja en la calificación soberana y la postura de la Reserva Federal de Estados Unidos (Bancolombia, 2025). Sin embargo, factores como la debilidad global del dólar y el apetito de inversionistas extranjeros por activos locales podrían mitigar las presiones cambiarias. En conjunto, estas proyecciones permiten comprender la volatilidad de la TRM y anticipar su posible comportamiento durante el próximo año, considerando tanto factores internos como externos que afectan la oferta y demanda de dólares en Colombia.

2. Retornos y volatilidad de la TRM

Se utilizó la librería cITMre en RStudio para descargar la Tasa Representativa del Mercado (TRM) de Colombia, correspondiente al periodo entre el 01/01/2015 y el 01/01/2025. Esta serie histórica de diez años constituye una base confiable para análisis económicos y financieros, permitiendo estudiar tendencias, volatilidad y comportamientos del tipo de cambio COP/USD.

A partir de la serie histórica (2015–2025) se calcularon los retornos mensuales y la desviación estándar. La evidencia muestra que el COP/USD es un activo volátil, lo que confirma la necesidad de incluir instrumentos derivados para estabilizar los pagos futuros.

  • Librerias a utilizar:
library(citmre)
library(readxl)
library(dplyr)
library(ggplot2)
library(lubridate)
library(writexl)
  • Recoleción de datos utilizando la libreria Citmre:
# Serie de precios
data <- rmre_data(start_date = "2015-01-01", end_date = "2025-09-15")
plot(data)

# Serie de retornos compuestos continuos
data_log <- rmre_data(start_date = "2015-01-01", end_date = "2025-09-15", log_return = TRUE)
plot(data_log)

meses_año <- 12
mu <- exp(mean(na.omit(data_log)*meses_año))-1
cat("Media Mensual( µ ):", round(mu * 100, 4),"%\n")
## Media Mensual( µ ): 0.2323 %
sigma <- sqrt(var(na.omit(data_log))*meses_año)
cat("Volatilidad Mensual( σ ):",round(sigma * 100, 3),"%\n")
## Volatilidad Mensual( σ ): 2.959 %
# Factores iniciales
meses_año <- 12
s0 <- data[length(data)]
T <- 120
alpha <- 0.05/2

md <- (log(s0)+(mu-(sigma^2/2))*(T/meses_año))  #Efecto deterministico
cat("Efecto deterministico:", round(md, 4),"\n")
## Efecto deterministico: 8.2892
vr <- (sigma^2)*(T/meses_año)
cat("Efecto Aleatorio:",round(vr , 6),"\n") #Efecto Aleatorio
## Efecto Aleatorio: 0.008757
p_n <- qnorm(alpha)
p_p <- qnorm(1-alpha)

v_inferior <- exp(md+p_n*sqrt(vr))
cat("Valor Inferior:",round(v_inferior,2),"\n")
## Valor Inferior: 3313.52
v_superior <- exp(md+p_p*sqrt(vr))
cat("Valor Superior:",round(v_superior,2),"\n")
## Valor Superior: 4781.88
v_esperado <- s0*exp(mu*(T/meses_año))
cat("Valor Esperado:",round(v_esperado,2),"\n")
## Valor Esperado: 3998.02

3. Simulación BMG de la TRM

Con base en un proceso de Movimiento Browniano Geométrico (GBM) se simularon trayectorias cambiarias de la TRM. Para mejorar la precisión de la simulación, se implementó el esquema de Milstein, que añade un término correctivo respecto al método de Euler-Maruyama, reduciendo el error de discretización en la aproximación del proceso estocástico (Kloeden & Platen, 1992). Este enfoque probabilístico refleja escenarios de apreciación, estabilidad y depreciación, lo que permite anticipar cómo las cuotas en COP pueden variar a lo largo del tiempo. Para la ingeniería financiera, estas simulaciones son clave porque permiten cuantificar la sensibilidad de la deuda frente al riesgo cambiario.

# Semilla y configuración de notación científica 
set.seed (123)
options (scipen = 999)

# Esquema de Milstein:
dt <- 1/meses_año
meses <- T
Trayectorias <- 500
S <- matrix(0,nrow = meses, ncol = Trayectorias)
S[1,] <- data[length(data)]

for (j in seq(1:Trayectorias)) {
  for(i in 2:meses){
    Aleatorio_Norm <- rnorm(1,0,1)
    dS <- mu*S[i-1,j]*dt + sigma*S[i-1,j]*sqrt(dt)*Aleatorio_Norm
    S[i,j] <- S[i-1,j] + dS + (1/2)*(sigma^2)*(S[i-1,j])*((sqrt(dt)*Aleatorio_Norm)^2-dt)
  }
}

matplot(S,type = "l")

abline(h=data[length(data)], col="black",lty=3, lwd=3)

q1 = quantile(S[i ,] , 0.975)
q2 = quantile(S[i ,] , 0.025)

plot(density(S[i ,]) , ylab ="", xlab ="",main =" Empirical distribution ", lwd = 3)

abline(h = NULL , v =q1 , col = "blue", lwd = 2)
abline(h = NULL , v =q2 , col = "green", lwd = 2)
abline(h = NULL , v =s0 , col = "red", lwd = 2)
abline(h = NULL , v =v_esperado , col = "gray", lwd = 2)

4. Simulación del crédito en dólares

El crédito se estructuró bajo el sistema francés con una tasa de 6,45% APR (Tasa de Porcentaje Anual) ofrecida por United Prairie, entidad financiera estadounidense especializada en préstamos para maquinaria (Bank, 2025). A un plazo de 10 años y un pago inicial del 10%. Esta condición muestra un costo financiero competitivo frente a alternativas locales, donde las tasas efectivas superan el 32% anual. Desde un criterio financiero, el acceso al crédito estadounidense representa un ahorro significativo en costo de capital.

# Parámetros del crédito
credito_cop <- 500000000
credito_usd <- credito_cop/s0
pago_inicial <- 0.10 * credito_usd
monto_credito <- credito_usd - pago_inicial
tasa_anual <- 0.0645 # Efectiva anual
tasa_mensual <- tasa_anual / 12
n_cuotas <- 120 # meses

# Cálculo de la cuota mensual (sistema francés)
cuota_mensual_usd <- monto_credito * (tasa_mensual * (1 + tasa_mensual)^n_cuotas) / ((1 + tasa_mensual)^n_cuotas - 1)

# Inicializar variables
saldo <- monto_credito
flujo <- data.frame(Mes = integer(), Cuota = numeric(), Interes = numeric(), AbonoCapital = numeric(), SaldoRestante = numeric())

# Generar tabla de amortización
for (mes in 1:n_cuotas) {
  interes <- saldo * tasa_mensual
  abono_capital <- cuota_mensual_usd - interes
  saldo <- saldo - abono_capital
  
  flujo <- rbind(flujo, data.frame(
    Mes = mes,
    Cuota = round(cuota_mensual_usd, 2),
    Interes = round(interes, 2),
    AbonoCapital = round(abono_capital, 2),
    SaldoRestante = round(saldo, 2)
  ))
}
# Renombrar columnas de la tabla
colnames(flujo) <- c("Mes", "Cuota (USD)", "Interés (USD)", "Abono a Capital (USD)", "Saldo Restante (USD)")

# Mostrar últimos 6 meses
tail(flujo,6)
##               Mes Cuota (USD) Interés (USD) Abono a Capital (USD)
## 2025-09-15114 115     1305.15         41.31               1263.84
## 2025-09-15115 116     1305.15         34.52               1270.63
## 2025-09-15116 117     1305.15         27.69               1277.46
## 2025-09-15117 118     1305.15         20.82               1284.33
## 2025-09-15118 119     1305.15         13.92               1291.23
## 2025-09-15119 120     1305.15          6.98               1298.17
##               Saldo Restante (USD)
## 2025-09-15114              6421.81
## 2025-09-15115              5151.18
## 2025-09-15116              3873.72
## 2025-09-15117              2589.40
## 2025-09-15118              1298.17
## 2025-09-15119                 0.00

5. Conversión del crédito a pesos

Al convertir las cuotas a pesos colombianos según las simulaciones de la TRM, observamos que el costo total está directamente ligado a la volatilidad cambiaria. En un escenario de fuerte depreciación, el valor en pesos aumenta considerablemente, mientras que, con una apreciación, el crédito resulta más barato. Esto nos confirma que la variación del dólar es el principal riesgo de la operación.

Escenario 1: Trayectoria alta

# Trayectoria Mas Alta: la que termina con el valor más alto en el mes 120

indice_max <- which.max(S[120, ])
trayectoria_alta <- S[, indice_max]

# Inicializar variables
saldo <- monto_credito
flujo_cop_Op <- data.frame(Mes = integer(), Cuota = numeric(), Interes = numeric(), AbonoCapital = numeric(), SaldoRestante = numeric())

# Generar tabla de amortización en COP
for (mes in 1:n_cuotas) {
  trm_mes_Op <- S[mes, indice_max]
  interes_usd <- saldo * tasa_mensual
  abono_capital_usd <- cuota_mensual_usd - interes_usd
  saldo <- saldo - abono_capital_usd
  
  # Convertir cada componente a COP
  cuota_cop <- cuota_mensual_usd * trm_mes_Op
  interes_cop <- interes_usd * trm_mes_Op
  abono_capital_cop <- abono_capital_usd * trm_mes_Op
  saldo_cop <- saldo * trm_mes_Op
  
  flujo_cop_Op <- rbind(flujo_cop_Op, data.frame(
    Mes = mes,
    TRM = round(trm_mes_Op, 2),
    Cuota = round(cuota_cop, 2),
    Interes = round(interes_cop, 2),
    AbonoCapital = round(abono_capital_cop, 2),
    SaldoRestante = round(saldo_cop, 2)
  ))
}

# Renombrar columnas para claridad
colnames(flujo_cop_Op) <- c("Mes", "Valor TRM Mas Alta","Cuota (COP)", "Interés (COP)", "Abono a Capital (COP)", "Saldo Restante (COP)")

# Mostrar los últimos meses
tail(flujo_cop_Op, 6)
##               Mes Valor TRM Mas Alta Cuota (COP) Interés (COP)
## 2025-09-15114 115            5042.35     6581006     208301.27
## 2025-09-15115 116            5094.01     6648431     175831.16
## 2025-09-15116 117            5113.01     6673225     141566.94
## 2025-09-15117 118            5151.01     6722822     107250.50
## 2025-09-15118 119            5120.57     6683099      71268.20
## 2025-09-15119 120            5117.38     6678928      35707.31
##               Abono a Capital (COP) Saldo Restante (COP)
## 2025-09-15114               6372705             32381020
## 2025-09-15115               6472599             26240175
## 2025-09-15116               6531658             19806376
## 2025-09-15117               6615571             13338010
## 2025-09-15118               6611831              6647370
## 2025-09-15119               6643220                    0

En la trayectoria alta proyectamos una depreciación considerable del peso, con una TRM que llega a niveles superiores a 5.700 pesos por dólar. Este comportamiento coincide con escenarios de riesgo cambiario descritos por el Banco de la República (Banco de la República, 2025). Para nosotros, como inversionistas, esto se traduce en cuotas mucho más costosas en pesos, lo cual incrementa el valor total de la deuda. Aunque inicialmente el crédito en dólares parecía atractivo por su baja tasa frente a las tasas locales (Social, 2025), la devaluación hace que la carga financiera aumente significativamente.

Escenario 2: Trayectoria intermedia

# Trayectoria intermedia: promedio de todas las trayectorias

trayectoria_promedio <- rowMeans(S)

# Inicializar variables
saldo <- monto_credito
flujo_cop_M <- data.frame(Mes = integer(), Cuota = numeric(), Interes = numeric(), AbonoCapital = numeric(), SaldoRestante = numeric())

# Generar tabla de amortización en COP
for (mes in 1:n_cuotas) {
  trm_mes_M <- trayectoria_promedio[mes]
  interes_usd <- saldo * tasa_mensual
  abono_capital_usd <- cuota_mensual_usd - interes_usd
  saldo <- saldo - abono_capital_usd
  
  # Convertir cada componente a COP
  cuota_cop <- cuota_mensual_usd * trm_mes_M
  interes_cop <- interes_usd * trm_mes_M
  abono_capital_cop <- abono_capital_usd * trm_mes_M
  saldo_cop <- saldo * trm_mes_M
  
  flujo_cop_M <- rbind(flujo_cop_M, data.frame(
    Mes = mes,
    TRM = round(trm_mes_M, 2),
    Cuota = round(cuota_cop, 2),
    Interes = round(interes_cop, 2),
    AbonoCapital = round(abono_capital_cop, 2),
    SaldoRestante = round(saldo_cop, 2)
  ))
}

# Renombrar columnas para claridad
colnames(flujo_cop_M) <- c("Mes", "Valor TRM Intermedia","Cuota (COP)", "Interés (COP)", "Abono a Capital (COP)", "Saldo Restante (COP)")

# Mostrar los últimos meses
tail(flujo_cop_M, 6)
##               Mes Valor TRM Intermedia Cuota (COP) Interés (COP)
## 2025-09-15114 115              3989.02     5206254     164787.78
## 2025-09-15115 116              3986.55     5203039     137604.87
## 2025-09-15116 117              3987.58     5204380     110406.60
## 2025-09-15117 118              3990.10     5207667      83078.94
## 2025-09-15118 119              3990.76     5208535      55543.54
## 2025-09-15119 120              3995.52     5214739      27879.37
##               Abono a Capital (COP) Saldo Restante (COP)
## 2025-09-15114               5041466             25616725
## 2025-09-15115               5065434             20535471
## 2025-09-15116               5093973             15446789
## 2025-09-15117               5124588             10331958
## 2025-09-15118               5152992              5180689
## 2025-09-15119               5186859                    0

En la trayectoria intermedia se mantiene un punto medio: la TRM promedia alrededor de 4.472 pesos. Aquí confirmamos que el costo del crédito en pesos es intermedio, sin llegar a los extremos de los otros escenarios. Este caso refleja con mayor realismo la incertidumbre y la volatilidad del tipo de cambio, como lo señala Bancolombia en su informe de perspectivas económicas (Bancolombia, 2025). Para nosotros, esta situación demuestra la necesidad de considerar coberturas cambiarias que reduzcan la exposición y permitan proyectar pagos más estables en el tiempo.

Escenario 3: Trayectoria baja

# Trayectoria Mas Baja: la que termina con el valor más bajo en el mes 120

indice_min <- which.min(S[120, ])
trayectoria_baja <- S[, indice_min]

# Inicializar variables
saldo <- monto_credito
flujo_cop_P <- data.frame(Mes = integer(), Cuota = numeric(), Interes = numeric(), AbonoCapital = numeric(), SaldoRestante = numeric())

# Generar tabla de amortización en COP
for (mes in 1:n_cuotas) {
  trm_mes_P <- S[mes, indice_min]
  interes_usd <- saldo * tasa_mensual
  abono_capital_usd <- cuota_mensual_usd - interes_usd
  saldo <- saldo - abono_capital_usd
  
  # Convertir cada componente a COP
  cuota_cop <- cuota_mensual_usd * trm_mes_P
  interes_cop <- interes_usd * trm_mes_P
  abono_capital_cop <- abono_capital_usd * trm_mes_P
  saldo_cop <- saldo * trm_mes_P
  
  flujo_cop_P <- rbind(flujo_cop_P, data.frame(
    Mes = mes,
    TRM = round(trm_mes_P, 2),
    Cuota = round(cuota_cop, 2),
    Interes = round(interes_cop, 2),
    AbonoCapital = round(abono_capital_cop, 2),
    SaldoRestante = round(saldo_cop, 2)
  ))
}

# Renombrar columnas para claridad
colnames(flujo_cop_P) <- c("Mes", "Valor TRM Mas Baja","Cuota (COP)", "Interés (COP)", "Abono a Capital (COP)", "Saldo Restante (COP)")

# Mostrar los últimos meses
tail(flujo_cop_P, 6)
##               Mes Valor TRM Mas Baja Cuota (COP) Interés (COP)
## 2025-09-15114 115            3021.50     3943502     124819.29
## 2025-09-15115 116            2998.98     3914105     103516.40
## 2025-09-15116 117            3000.96     3916689      83089.32
## 2025-09-15117 118            3004.23     3920963      62551.90
## 2025-09-15118 119            3042.47     3970878      42345.22
## 2025-09-15119 120            3030.41     3955130      21145.17
##               Abono a Capital (COP) Saldo Restante (COP)
## 2025-09-15114               3818683             19403510
## 2025-09-15115               3810588             15448276
## 2025-09-15116               3833600             11624877
## 2025-09-15117               3858411              7779151
## 2025-09-15118               3928532              3949648
## 2025-09-15119               3933985                    0
# Visualización de las trayectorias escogidas
matplot(cbind(trayectoria_baja, trayectoria_promedio, trayectoria_alta), type = "l",
        col = c("red", "blue", "green"), lty = 1,
        xlab = "Mes", ylab = "TRM simulada",
        main = "Trayectorias TRM: Mas Baja, Intermedia, Mas Alta")

  • Exportar a Excel las tablas de amortización del crédito:
write_xlsx(flujo, "Tabla de amortizacion USD.xlsx")
write_xlsx(flujo_cop_M, "Tabla de amortizacion COP Trayectoria Intermedia.xlsx")
write_xlsx(flujo_cop_Op, "Tabla de amortizacion COP Trayectoria Alta.xlsx")
write_xlsx(flujo_cop_P, "Tabla de amortizacion COP Trayectoria baja.xlsx")

En el escenario de trayectoria baja, observamos una apreciación del peso colombiano, con una TRM que cae hasta cerca de 3.379 pesos. En este caso, nuestras cuotas en pesos se reducen y el crédito se vuelve mucho más manejable. Este comportamiento coincide con algunas proyecciones de analistas que prevén escenarios de debilidad del dólar a nivel global (Union, 2025). Para nosotros, como inversionistas, sería el escenario más favorable, pues pagaríamos menos intereses y abonaríamos más rápido al capital. Sin embargo, entendemos que este contexto es poco común en el largo plazo y que depende de múltiples factores externos, como el precio del petróleo o la política monetaria en Estados Unidos (República, 2025).

Al comparar las tres trayectorias confirmamos que el factor determinante del costo real de la deuda es la TRM. Si el dólar sube, nuestras cuotas en pesos aumentan considerablemente; si baja, el crédito resulta mucho más barato. Este análisis nos enseña que, como inversionistas, no podemos depender únicamente de las proyecciones cambiarias, sino que debemos complementar el crédito con instrumentos de cobertura como futuros o forwards (Valores de Colombia, 2025). De esta forma, logramos proteger la inversión en maquinaria y reducir el impacto de la volatilidad cambiaria sobre nuestra capacidad de pago.

Parte 2. Proceso de futuro

1. Análisis de un futuro de la BVC (TRXV25F)

En nuestro ejercicio tomamos como referencia el contrato de futuros de divisas TRXV25F, listado en la Bolsa de Valores de Colombia (BVC). Este contrato corresponde a un tamaño de 1.000 USD, lo que resulta apropiado para simular la cobertura de la deuda en dólares que decidimos asumir como inversionistas. Para la operación, definimos un margen inicial del 6,3%, equivalente a 5.481 USD, cifra que guarda una relación cercana con el precio del contrato y que nos permite dimensionar el nivel de apalancamiento y riesgo inherente a la posición. A partir del cálculo de los retornos mensuales y su desviación estándar, observamos una elevada volatilidad, coherente con la naturaleza especulativa y de cobertura que caracteriza a los derivados financieros transados en la BVC (Valores de Colombia, 2025).

# Se cargan los datos históricos del futuro y se calculan sus rendimientos
trx <- read_xlsx(file.choose())
trx["Rentorno"] <- c(NA,diff(log(trx$`Precio cierre`)))

meses_año <- 12
mu_f <- exp(mean(na.omit(trx$Rentorno)*meses_año))-1
cat("Media Mensual( µ ):", round(mu_f * 100, 3),"%\n")
## Media Mensual( µ ): -9.67 %
sigma_f <- sqrt(var(na.omit(trx$Rentorno))*meses_año)
cat("Volatilidad Mensual( σ ):",round(sigma_f * 100, 3),"%\n")
## Volatilidad Mensual( σ ): 8.436 %

2. Simulación del futuro de divisas

# Semilla y configuración de notación científica 
set.seed (123)
options (scipen = 999)

# Parámetros de tasas
S0 <- trx$`Precio cierre`[length(trx$`Precio cierre`)]  # Spot futuro actual
i_nacional <- 0.3485
i_extranjero <- 0.0645
t <- 1  

# Cálculo del valor inicial del futuro
F0 <- S0 * ((1 + i_nacional) / (1 + i_extranjero))^t
cat("Valor inicial del futuro de TRM:", round(F0, 2), "\n")
## Valor inicial del futuro de TRM: 4952.22
# Esquema de Milstein:
dt <- 1/meses_año
meses <- T-72 # Del año 6 en adelante (48 meses)
Trayectorias <- 500
S_f <- matrix(0,nrow = meses, ncol = Trayectorias)
S_f[1,] <- F0

for (j in seq(1:Trayectorias)) {
  for(i in 2:meses){
    Aleatorio_Norm <- rnorm(1,0,1)
    dS_f <- mu_f*S_f[i-1,j]*dt + sigma_f*S_f[i-1,j]*sqrt(dt)*Aleatorio_Norm
    S_f[i,j] <- S_f[i-1,j] + dS_f + (1/2)*(sigma_f^2)*(S_f[i-1,j])*((sqrt(dt)*Aleatorio_Norm)^2-dt)
  }
}

matplot(S_f,type = "l")

q1 = quantile(S_f[i ,] , 0.975)
q2 = quantile(S_f[i ,] , 0.025)

plot(density(S_f[i ,]) , ylab ="", xlab ="",main =" Empirical distribution ", lwd = 3)

abline(h = NULL , v =q1 , col = "blue", lwd = 2)
abline(h = NULL , v =q2 , col = "green", lwd = 2)
abline(h = NULL , v =S0 , col = "red", lwd = 2)
abline(h = NULL , v =F0 , col = "gray", lwd = 2)

Con la fórmula de futuros de TRM como precio inicial, se simuló el comportamiento de los contratos entre los años 6 y 10. El ejercicio incluyó la aplicación de rollover en cada vencimiento, replicando condiciones de mercado. Desde la perspectiva financiera, la simulación permitió modelar cómo la cobertura atenúa los incrementos de la deuda cuando el dólar se deprecia.

Para el cálculo del precio inicial del futuro y la simulación planteada se tomó como referencia la tasa de crédito del 6,45% APR (Tasa de Porcentaje Anual) ofrecida por United Prairie, entidad financiera estadounidense especializada en préstamos para maquinaria (Bank, 2025). Esta tasa aplica a plazos de 6 a 7 años y montos superiores a USD 200,000, lo cual se ajusta al requerimiento de 500 millones de pesos, y está dirigida a clientes con puntaje crediticio superior a 700, perfil que asumimos como inversionistas con historial sólido.

En cuanto a la tasa local, revisamos las opciones de crédito productivo del Banco Caja Social y encontramos que, para montos como el nuestro, las tasas efectivas anuales se ubican entre el 32% y el 37%. Para el ejercicio tomamos el promedio de ese rango, obteniendo una tasa de 34,85% Efectiva Anual, que refleja lo que realmente costaría un crédito de este tipo en el país (Social, 2025). Al comparar ambas alternativas, la diferencia es clara: en Estados Unidos el crédito resulta mucho más barato, mientras que en Colombia el financiamiento es más costoso. Esta comparación permitió diferenciar cómo cambia el análisis dependiendo del origen del crédito.

3. Margen y exposición

# Parametros del contrato
Qf <- 1000
v_cont <- Qf * S0
inv <- (monto_credito*S0)*0.75
N_optimo <- round(inv/v_cont,0)+1
exp_total <- (v_cont * N_optimo)

# Margen inicial y de mantenimiento:
vgi <- 0.063
vgm <- 0.5
m_inicial <- exp_total * vgi
m_mant <- m_inicial * vgm

# Apalancamiento:
Ap <- exp_total/m_inicial

De los 500.000.000 COP necesarios para financiar la compra de la maquinaria amarilla, realizamos un pago inicial del 10% equivalente a 50.000.000 COP (128.000 USD). Con ello, quedó un saldo pendiente de 450.000.000 COP (115.200 USD). Sobre esta deuda decidimos cubrir el 75% a través de futuros, lo que corresponde a 337.760.929 COP (86.400 USD). Ademas tomamos el precio inicial del futuro de 3.909,26 COP. Al dividir el monto a cubrir entre este precio, obtuvimos 86,4 contratos; al redondear, resultaron 86 contratos, que no alcanzaban a cubrir completamente el 75%. Por esta razón, adicionamos un contrato más, quedando en 87 contratos en total, con lo cual logramos cubrir de manera completa el porcentaje definido de la deuda.

La exposición total fue de 340.105.620 COP, con una garantía inicial del 6,3% según la BVC (21.426.654 COP) y un margen de mantenimiento del 50% de ese valor, es decir, 10.713.327 COP. La operación de 87 contratos implicó un margen inicial del 6,3% y un apalancamiento cercano a 16 veces. Este nivel de exposición refleja el poder de los derivados para cubrir riesgos con bajo capital inicial, pero también resalta el riesgo de liquidez si se presentan llamadas de margen (margin calls).

En conclusión, con los 87 contratos aseguramos la cobertura del 75% y protegimos los pagos frente a la variación del dólar, evitando que el crédito se encarezca más de lo previsto si la TRM aumenta.

4. Flujo de caja con rollover: Regla simple (signo del retorno trimestral)

  • Escenario 1:
# Trayectoria Mas Alta: la que termina con el valor más alto en el mes 48

indice_max <- which.max(S_f[48, ])
S_serie_A <- as.numeric(S_f[, indice_max])   # vector de longitud meses
meses <- length(S_serie_A)

# Fechas mensuales 
fecha_inicio <- Sys.Date() - (5)
fechas <- seq.Date(from = fecha_inicio, by = "month", length.out = meses)

# Inicializaciones
DailyPnL <- numeric(meses)
MarginBalance <- numeric(meses)
MarginCall <- numeric(meses)
CumMarginCalls <- numeric(meses)
CumPnL <- numeric(meses)
Position <- character(meses)
RolloverFlag <- rep(FALSE, meses)
Signal_method <- rep(NA_character_, meses)
Signal_value <- rep(NA_real_, meses)

# Posición inicial ("Largo" o "Corto")
pos <- "Largo"
Position[1] <- pos

# Inicializamos margen con el inicial
MarginBalance[1] <- m_inicial
MarginCall[1] <- 0
CumMarginCalls[1] <- 0
DailyPnL[1] <- 0
CumPnL[1] <- 0

# Parámetros de la regla simple
period_roll <- 3         # rollover trimestral (cada 3 meses)
threshold <- 0.0025      # umbral mínimo (0.25%) para evitar cambios por ruido. 
threshold_log <- log(1 + threshold)

# Loop mensual 
for (t in 2:meses) {
  
  # signo para PnL según posición: 1 para largo, -1 para corto
  sign_pos <- ifelse(pos == "Largo", 1, -1)
  
  # P&L mensual: 
  delta_price <- S_serie_A[t] - S_serie_A[t - 1]
  pnl_t <- (delta_price * sign_pos) * Qf * N_optimo
  
  DailyPnL[t] <- pnl_t
  CumPnL[t] <- CumPnL[t - 1] + pnl_t
  
  # Actualizamos margen (mark-to-market)
  MarginBalance[t] <- MarginBalance[t - 1] + pnl_t
  MarginCall[t] <- 0
  CumMarginCalls[t] <- CumMarginCalls[t - 1]
  
  # Si cae por debajo del mantenimiento -> margin call 
  if (!is.na(MarginBalance[t]) && MarginBalance[t] < m_mant) {
    faltante <- m_inicial - MarginBalance[t]
    MarginCall[t] <- faltante
    MarginBalance[t] <- MarginBalance[t] + faltante
    CumMarginCalls[t] <- CumMarginCalls[t] + faltante
  }
  
  # Rollover trimestral
  if ((t %% period_roll) == 0 && t >= period_roll) {
    RolloverFlag[t] <- TRUE
    
    # retorno acumulado log del trimestre (aditivo y exacto)
    ret_quarter_log <- sum(log(S_serie_A[(t - period_roll + 1):t])
                           - log(S_serie_A[(t - period_roll):(t-1)]))
    
    # decisión por signo usando log-returns y umbral log
    if (!is.na(ret_quarter_log)) {
      if (ret_quarter_log > threshold_log) {
        newpos <- "Largo"
        Signal_method[t] <- "LogSignQuarter"
        Signal_value[t] <- ret_quarter_log
      } else if (ret_quarter_log  < -threshold_log) {
        newpos <- "Corto"
        Signal_method[t] <- "LogSignQuarter"
        Signal_value[t] <- ret_quarter_log
      } else {
        newpos <- pos           # señal débil -> sin cambio
        Signal_method[t] <- "NoChange_WeakSignal_log"
        Signal_value[t] <- ret_quarter_log
      }
      # Aplicar cambio de posición si hay cambio efectivo
      if (newpos != pos) {
        pos <- newpos
      }
    } else {
      Signal_method[t] <- "InsufficientHistory"
      Signal_value[t] <- NA
    }
  }
  
  Position[t] <- pos
}

# Construcción data.frame final
resultados_SA <- data.frame(
  Date = fechas,
  Price = S_serie_A,
  Position = Position,
  Rollover = RolloverFlag,
  Signal_method = Signal_method,
  Signal_value = Signal_value,
  DailyPnL = DailyPnL,
  CumPnL = CumPnL,
  MarginBalance = MarginBalance,
  MarginCall = MarginCall,
  CumMarginCalls = CumMarginCalls,
  stringsAsFactors = FALSE
)

# Ordenar columnas y mostrar
resultados_SA <- resultados_SA %>%
  select(Date, Price, Position, Rollover, Signal_method, Signal_value, DailyPnL, CumPnL, MarginBalance, MarginCall, CumMarginCalls)

print(tail(resultados_SA, 15))
##          Date    Price Position Rollover  Signal_method Signal_value
## 34 2028-06-16 4369.501    Corto    FALSE           <NA>           NA
## 35 2028-07-16 4324.995    Corto    FALSE           <NA>           NA
## 36 2028-08-16 4397.533    Corto     TRUE LogSignQuarter -0.004369091
## 37 2028-09-16 4437.658    Corto    FALSE           <NA>           NA
## 38 2028-10-16 4508.886    Corto    FALSE           <NA>           NA
## 39 2028-11-16 4740.452    Largo     TRUE LogSignQuarter  0.075088822
## 40 2028-12-16 4778.167    Largo    FALSE           <NA>           NA
## 41 2029-01-16 4762.439    Largo    FALSE           <NA>           NA
## 42 2029-02-16 4473.177    Corto     TRUE LogSignQuarter -0.058033640
## 43 2029-03-16 4738.623    Corto    FALSE           <NA>           NA
## 44 2029-04-16 4643.664    Corto    FALSE           <NA>           NA
## 45 2029-05-16 4881.171    Largo     TRUE LogSignQuarter  0.087286366
## 46 2029-06-16 4885.127    Largo    FALSE           <NA>           NA
## 47 2029-07-16 5030.756    Largo    FALSE           <NA>           NA
## 48 2029-08-16 4975.304    Largo     TRUE LogSignQuarter  0.019101327
##       DailyPnL    CumPnL MarginBalance MarginCall CumMarginCalls
## 34   4113970.5  19475669      62783957          0       21881634
## 35   3872023.9  23347692      66655981          0       21881634
## 36  -6310787.2  17036905      60345193          0       21881634
## 37  -3490868.5  13546037      56854325          0       21881634
## 38  -6196872.9   7349164      50657452          0       21881634
## 39 -20146224.3 -12797060      30511228          0       21881634
## 40   3281250.0  -9515810      33792478          0       21881634
## 41  -1368338.2 -10884149      32424139          0       21881634
## 42 -25165853.9 -36050002      21426654   14168368       36050002
## 43 -23093828.0 -59143830      21426654   23093828       59143830
## 44   8261468.9 -50882362      29688123          0       59143830
## 45 -20663163.6 -71545525      21426654   12401695       71545525
## 46    344127.9 -71201397      21770782          0       71545525
## 47  12669755.4 -58531642      34440537          0       71545525
## 48  -4824310.5 -63355952      29616227          0       71545525
print(summary(resultados_SA %>% select(DailyPnL, CumPnL, MarginBalance, MarginCall, CumMarginCalls)))
##     DailyPnL             CumPnL          MarginBalance        MarginCall      
##  Min.   :-25165854   Min.   :-71545525   Min.   :11966265   Min.   :       0  
##  1st Qu.: -6225351   1st Qu.:-15218118   1st Qu.:21679155   1st Qu.:       0  
##  Median :  -278213   Median :  -379255   Median :33108309   Median :       0  
##  Mean   : -1319916   Mean   : -3365452   Mean   :42287558   Mean   : 1490532  
##  3rd Qu.:  4248077   3rd Qu.: 18367342   3rd Qu.:61675630   3rd Qu.:       0  
##  Max.   : 20832106   Max.   : 41591483   Max.   :84899771   Max.   :23093828  
##  CumMarginCalls    
##  Min.   :       0  
##  1st Qu.:10965955  
##  Median :21881634  
##  Mean   :24226356  
##  3rd Qu.:21881634  
##  Max.   :71545525
  • Escenario 2:
# Traza representativa: promedio entre trayectorias 
S_serie_M <- as.numeric(rowMeans(S_f))   # vector de longitud meses
meses <- length(S_serie_M)

# Fechas mensuales 
fecha_inicio <- Sys.Date() - (5)
fechas <- seq.Date(from = fecha_inicio, by = "month", length.out = meses)

# Inicializaciones
DailyPnL <- numeric(meses)
MarginBalance <- numeric(meses)
MarginCall <- numeric(meses)
CumMarginCalls <- numeric(meses)
CumPnL <- numeric(meses)
Position <- character(meses)
RolloverFlag <- rep(FALSE, meses)
Signal_method <- rep(NA_character_, meses)
Signal_value <- rep(NA_real_, meses)

# Posición inicial ("Largo" o "Corto")
pos <- "Corto"
Position[1] <- pos

# Inicializamos margen con el inicial
MarginBalance[1] <- m_inicial
MarginCall[1] <- 0
CumMarginCalls[1] <- 0
DailyPnL[1] <- 0
CumPnL[1] <- 0

# Parámetros de la regla simple
period_roll <- 3         # rollover trimestral (cada 3 meses)
threshold <- 0.0025      # umbral mínimo (0.25%) para evitar cambios por ruido. 
threshold_log <- log(1 + threshold)

# Loop mensual 
for (t in 2:meses) {
  
  # signo para PnL según posición: 1 para largo, -1 para corto
  sign_pos <- ifelse(pos == "Largo", 1, -1)
  
  # P&L mensual: 
  delta_price <- S_serie_M[t] - S_serie_M[t - 1]
  pnl_t <- (delta_price * sign_pos) * Qf * N_optimo
  
  DailyPnL[t] <- pnl_t
  CumPnL[t] <- CumPnL[t - 1] + pnl_t
  
  # Actualizamos margen (mark-to-market)
  MarginBalance[t] <- MarginBalance[t - 1] + pnl_t
  MarginCall[t] <- 0
  CumMarginCalls[t] <- CumMarginCalls[t - 1]
  
  # Si cae por debajo del mantenimiento -> margin call 
  if (!is.na(MarginBalance[t]) && MarginBalance[t] < m_mant) {
    faltante <- m_inicial - MarginBalance[t]
    MarginCall[t] <- faltante
    MarginBalance[t] <- MarginBalance[t] + faltante
    CumMarginCalls[t] <- CumMarginCalls[t] + faltante
  }
  
  # Rollover trimestral
  if ((t %% period_roll) == 0 && t >= period_roll) {
    RolloverFlag[t] <- TRUE
    
    # retorno acumulado log del trimestre (aditivo y exacto)
    ret_quarter_log <- sum(log(S_serie_M[(t - period_roll + 1):t])
                           - log(S_serie_M[(t - period_roll):(t-1)]))
    
    # decisión por signo usando log-returns y umbral log
    if (!is.na(ret_quarter_log)) {
      if (ret_quarter_log > threshold_log) {
        newpos <- "Largo"
        Signal_method[t] <- "LogSignQuarter"
        Signal_value[t] <- ret_quarter_log
      } else if (ret_quarter_log  < -threshold_log) {
        newpos <- "Corto"
        Signal_method[t] <- "LogSignQuarter"
        Signal_value[t] <- ret_quarter_log
      } else {
        newpos <- pos           # señal débil -> sin cambio
        Signal_method[t] <- "NoChange_WeakSignal_log"
        Signal_value[t] <- ret_quarter_log
      }
      # Aplicar cambio de posición si hay cambio efectivo
      if (newpos != pos) {
        pos <- newpos
      }
    } else {
      Signal_method[t] <- "InsufficientHistory"
      Signal_value[t] <- NA
    }
  }
  
  Position[t] <- pos
}

# Construcción data.frame final
resultados_SM <- data.frame(
  Date = fechas,
  Price = S_serie_M,
  Position = Position,
  Rollover = RolloverFlag,
  Signal_method = Signal_method,
  Signal_value = Signal_value,
  DailyPnL = DailyPnL,
  CumPnL = CumPnL,
  MarginBalance = MarginBalance,
  MarginCall = MarginCall,
  CumMarginCalls = CumMarginCalls,
  stringsAsFactors = FALSE
)

# Ordenar columnas y mostrar
resultados_SM <- resultados_SM %>%
  select(Date, Price, Position, Rollover, Signal_method, Signal_value, DailyPnL, CumPnL, MarginBalance, MarginCall, CumMarginCalls)

print(tail(resultados_SM, 15))
##          Date    Price Position Rollover  Signal_method Signal_value DailyPnL
## 34 2028-06-16 3769.188    Corto    FALSE           <NA>           NA  2614108
## 35 2028-07-16 3738.049    Corto    FALSE           <NA>           NA  2709101
## 36 2028-08-16 3706.208    Corto     TRUE LogSignQuarter  -0.02479077  2770235
## 37 2028-09-16 3675.368    Corto    FALSE           <NA>           NA  2683081
## 38 2028-10-16 3644.909    Corto    FALSE           <NA>           NA  2649883
## 39 2028-11-16 3610.508    Corto     TRUE LogSignQuarter  -0.02616060  2992881
## 40 2028-12-16 3579.877    Corto    FALSE           <NA>           NA  2664886
## 41 2029-01-16 3552.301    Corto    FALSE           <NA>           NA  2399141
## 42 2029-02-16 3524.073    Corto     TRUE LogSignQuarter  -0.02423123  2455871
## 43 2029-03-16 3486.811    Corto    FALSE           <NA>           NA  3241784
## 44 2029-04-16 3454.647    Corto    FALSE           <NA>           NA  2798207
## 45 2029-05-16 3434.519    Corto     TRUE LogSignQuarter  -0.02574048  1751191
## 46 2029-06-16 3407.843    Corto    FALSE           <NA>           NA  2320788
## 47 2029-07-16 3381.298    Corto    FALSE           <NA>           NA  2309400
## 48 2029-08-16 3355.273    Corto     TRUE LogSignQuarter  -0.02334365  2264185
##       CumPnL MarginBalance MarginCall CumMarginCalls
## 34 102923655     124350309          0              0
## 35 105632756     127059410          0              0
## 36 108402991     129829645          0              0
## 37 111086072     132512726          0              0
## 38 113735955     135162609          0              0
## 39 116728835     138155489          0              0
## 40 119393722     140820376          0              0
## 41 121792863     143219517          0              0
## 42 124248734     145675388          0              0
## 43 127490517     148917171          0              0
## 44 130288725     151715379          0              0
## 45 132039915     153466569          0              0
## 46 134360703     155787357          0              0
## 47 136670103     158096757          0              0
## 48 138934288     160360942          0              0
print(summary(resultados_SM %>% select(DailyPnL, CumPnL, MarginBalance, MarginCall, CumMarginCalls)))
##     DailyPnL           CumPnL          MarginBalance         MarginCall
##  Min.   :      0   Min.   :        0   Min.   : 21426654   Min.   :0   
##  1st Qu.:2618662   1st Qu.: 41047931   1st Qu.: 62474585   1st Qu.:0   
##  Median :2921839   Median : 77282166   Median : 98708820   Median :0   
##  Mean   :2894464   Mean   : 74535931   Mean   : 95962585   Mean   :0   
##  3rd Qu.:3264289   3rd Qu.:109073761   3rd Qu.:130500415   3rd Qu.:0   
##  Max.   :4122261   Max.   :138934288   Max.   :160360942   Max.   :0   
##  CumMarginCalls
##  Min.   :0     
##  1st Qu.:0     
##  Median :0     
##  Mean   :0     
##  3rd Qu.:0     
##  Max.   :0
  • Escenario 3:
# Trayectoria Mas Baja: la que termina con el valor más bajo en el mes 48

indice_min <- which.min(S_f[48, ])
S_serie_B <- as.numeric(S_f[, indice_min])   # vector de longitud meses
meses <- length(S_serie_B)

# Fechas mensuales 
fecha_inicio <- Sys.Date() - (5)
fechas <- seq.Date(from = fecha_inicio, by = "month", length.out = meses)

# Inicializaciones
DailyPnL <- numeric(meses)
MarginBalance <- numeric(meses)
MarginCall <- numeric(meses)
CumMarginCalls <- numeric(meses)
CumPnL <- numeric(meses)
Position <- character(meses)
RolloverFlag <- rep(FALSE, meses)
Signal_method <- rep(NA_character_, meses)
Signal_value <- rep(NA_real_, meses)

# Posición inicial ("Largo" o "Corto")
pos <- "Corto"
Position[1] <- pos

# Inicializamos margen con el inicial
MarginBalance[1] <- m_inicial
MarginCall[1] <- 0
CumMarginCalls[1] <- 0
DailyPnL[1] <- 0
CumPnL[1] <- 0

# Parámetros de la regla simple
period_roll <- 3         # rollover trimestral (cada 3 meses)
threshold <- 0.0025      # umbral mínimo (0.25%) para evitar cambios por ruido. 
threshold_log <- log(1 + threshold)

# Loop mensual 
for (t in 2:meses) {
  
  # signo para PnL según posición: 1 para largo, -1 para corto
  sign_pos <- ifelse(pos == "Largo", 1, -1)
  
  # P&L mensual: 
  delta_price <- S_serie_B[t] - S_serie_B[t - 1]
  pnl_t <- (delta_price * sign_pos) * Qf * N_optimo
  
  DailyPnL[t] <- pnl_t
  CumPnL[t] <- CumPnL[t - 1] + pnl_t
  
  # Actualizamos margen (mark-to-market)
  MarginBalance[t] <- MarginBalance[t - 1] + pnl_t
  MarginCall[t] <- 0
  CumMarginCalls[t] <- CumMarginCalls[t - 1]
  
  # Si cae por debajo del mantenimiento -> margin call 
  if (!is.na(MarginBalance[t]) && MarginBalance[t] < m_mant) {
    faltante <- m_inicial - MarginBalance[t]
    MarginCall[t] <- faltante
    MarginBalance[t] <- MarginBalance[t] + faltante
    CumMarginCalls[t] <- CumMarginCalls[t] + faltante
  }
  
  # Rollover trimestral
  if ((t %% period_roll) == 0 && t >= period_roll) {
    RolloverFlag[t] <- TRUE
    
    # retorno acumulado log del trimestre (aditivo y exacto)
    ret_quarter_log <- sum(log(S_serie_B[(t - period_roll + 1):t])
                           - log(S_serie_B[(t - period_roll):(t-1)]))
    
    # decisión por signo usando log-returns y umbral log
    if (!is.na(ret_quarter_log)) {
      if (ret_quarter_log > threshold_log) {
        newpos <- "Largo"
        Signal_method[t] <- "LogSignQuarter"
        Signal_value[t] <- ret_quarter_log
      } else if (ret_quarter_log  < -threshold_log) {
        newpos <- "Corto"
        Signal_method[t] <- "LogSignQuarter"
        Signal_value[t] <- ret_quarter_log
      } else {
        newpos <- pos           # señal débil -> sin cambio
        Signal_method[t] <- "NoChange_WeakSignal_log"
        Signal_value[t] <- ret_quarter_log
      }
      # Aplicar cambio de posición si hay cambio efectivo
      if (newpos != pos) {
        pos <- newpos
      }
    } else {
      Signal_method[t] <- "InsufficientHistory"
      Signal_value[t] <- NA
    }
  }
  
  Position[t] <- pos
}

# Construcción data.frame final
resultados_SB <- data.frame(
  Date = fechas,
  Price = S_serie_B,
  Position = Position,
  Rollover = RolloverFlag,
  Signal_method = Signal_method,
  Signal_value = Signal_value,
  DailyPnL = DailyPnL,
  CumPnL = CumPnL,
  MarginBalance = MarginBalance,
  MarginCall = MarginCall,
  CumMarginCalls = CumMarginCalls,
  stringsAsFactors = FALSE
)

# Ordenar columnas y mostrar
resultados_SB <- resultados_SB %>%
  select(Date, Price, Position, Rollover, Signal_method, Signal_value, DailyPnL, CumPnL, MarginBalance, MarginCall, CumMarginCalls)

print(tail(resultados_SB, 15))
##          Date    Price Position Rollover  Signal_method Signal_value   DailyPnL
## 34 2028-06-16 2714.478    Corto    FALSE           <NA>           NA  2281096.3
## 35 2028-07-16 2556.430    Corto    FALSE           <NA>           NA 13750163.1
## 36 2028-08-16 2565.709    Corto     TRUE LogSignQuarter  -0.06597752  -807277.1
## 37 2028-09-16 2553.513    Corto    FALSE           <NA>           NA  1061082.4
## 38 2028-10-16 2582.018    Corto    FALSE           <NA>           NA -2479958.4
## 39 2028-11-16 2474.311    Corto     TRUE LogSignQuarter  -0.03627317  9370564.8
## 40 2028-12-16 2421.125    Corto    FALSE           <NA>           NA  4627182.5
## 41 2029-01-16 2330.654    Corto    FALSE           <NA>           NA  7870914.1
## 42 2029-02-16 2306.703    Corto     TRUE LogSignQuarter  -0.07014278  2083805.1
## 43 2029-03-16 2241.648    Corto    FALSE           <NA>           NA  5659702.6
## 44 2029-04-16 2206.530    Corto    FALSE           <NA>           NA  3055281.2
## 45 2029-05-16 2248.892    Corto     TRUE LogSignQuarter  -0.02538155 -3685434.0
## 46 2029-06-16 2333.395    Corto    FALSE           <NA>           NA -7351831.5
## 47 2029-07-16 2206.740    Corto    FALSE           <NA>           NA 11018995.9
## 48 2029-08-16 2116.560    Corto     TRUE LogSignQuarter  -0.06064510  7845640.7
##       CumPnL MarginBalance MarginCall CumMarginCalls
## 34  86918922     108345576          0              0
## 35 100669085     122095739          0              0
## 36  99861808     121288462          0              0
## 37 100922891     122349545          0              0
## 38  98442932     119869586          0              0
## 39 107813497     129240151          0              0
## 40 112440679     133867334          0              0
## 41 120311594     141738248          0              0
## 42 122395399     143822053          0              0
## 43 128055101     149481755          0              0
## 44 131110382     152537036          0              0
## 45 127424948     148851602          0              0
## 46 120073117     141499771          0              0
## 47 131092113     152518767          0              0
## 48 138937753     160364408          0              0
print(summary(resultados_SB %>% select(DailyPnL, CumPnL, MarginBalance, MarginCall, CumMarginCalls)))
##     DailyPnL             CumPnL          MarginBalance         MarginCall
##  Min.   :-32984777   Min.   :        0   Min.   : 21426654   Min.   :0   
##  1st Qu.: -1587463   1st Qu.: 61243999   1st Qu.: 82670653   1st Qu.:0   
##  Median :  3108892   Median : 82246361   Median :103673015   Median :0   
##  Mean   :  2894537   Mean   : 79771643   Mean   :101198297   Mean   :0   
##  3rd Qu.:  8532074   3rd Qu.:100063627   3rd Qu.:121490282   3rd Qu.:0   
##  Max.   : 22624026   Max.   :138937753   Max.   :160364408   Max.   :0   
##  CumMarginCalls
##  Min.   :0     
##  1st Qu.:0     
##  Median :0     
##  Mean   :0     
##  3rd Qu.:0     
##  Max.   :0
# Visualización de las trayectorias escogidas
matplot(cbind(S_serie_B, S_serie_M, S_serie_A), type = "l",
        col = c("red", "blue", "green"), lty = 1,
        xlab = "Mes", ylab = "TRX simulada",
        main = "Trayectorias TRX Futura: Mas Baja, Intermedia, Mas Alta")

En el flujo de caja del período comprendido entre los años 6 y 10 construimos tres escenarios: trayectoria alta, baja e intermedia. Cada escenario refleja cómo las liquidaciones y reposiciones de contratos (rollover) impactan la posición de cobertura. Como criterio para el cambio de posición, optamos por basarnos en el signo del retorno acumulado en el trimestre: si el retorno fue positivo, se mantuvo la cobertura; mientras que, si fue negativo, se decidió ajustar la posición. Esta regla sencilla permitió dar consistencia a la estrategia y, al mismo tiempo, incorporar disciplina en la toma de decisiones. En este sentido, la gestión activa del rollover no solo reflejó nuestras expectativas de mercado, sino que también mostró cómo un criterio claro y objetivo puede ayudar a diferenciar entre cubrir el riesgo y aprovechar oportunidades especulativas en el largo plazo.

5. Comparación crédito vs cobertura

  • Escenario 1:
# ----- Configuración -----
# Tomamos hasta 48 meses
n_meses <- 48
n_take_A <- min(n_meses, nrow(flujo_cop_Op), nrow(resultados_SA))

# Extraer últimos n_take meses de cada tabla
amort_last_A <- tail(flujo_cop_Op, n_take_A)
margin_last_A <- tail(resultados_SA, n_take_A)


# Si no hay columna Date en amort_last, crear fechas alineadas con results_SA (opcional)
if (!"Date" %in% names(amort_last_A)) {
  amort_last_A$Date <- margin_last_A$Date
}

# ----- Construcción de la nueva amortización con cobertura -----
amort_con_cobertura_A <- amort_last_A %>%
  mutate(
    Date = as.Date(Date),
    Cuota_COP = as.numeric(`Cuota (COP)`),
    Interes_COP_original = as.numeric(`Interés (COP)`),
    AbonoCapital_COP_original = as.numeric(`Abono a Capital (COP)`),
    SaldoRestante_COP_original = as.numeric(`Saldo Restante (COP)`),
    DailyPnL = as.numeric(margin_last_A$DailyPnL)
  ) %>%
  # Inicializamos columnas que llenaremos
  mutate(
    PaymentFromMargin = 0,
    BorrowerCash = 0,
    Cuota_Ajustada = NA_real_,
    Interes_COP = NA_real_,
    AbonoCapital_COP = NA_real_,
    SaldoRestante_COP = NA_real_,
    Surplus = 0,
    Shortfall = 0
  )

# Empezar saldo con el primer saldo disponible (saldo inicial al comienzo del período seleccionado)
saldo_cop <- amort_con_cobertura_A$SaldoRestante_COP_original[1]
if (is.na(saldo_cop) || saldo_cop <= 0) {
  # fallback: usar suma de cuotas restantes (solo si el saldo original no está presente)
  saldo_cop <- sum(amort_con_cobertura_A$Cuota_COP, na.rm = TRUE)
  warning("Saldo inicial en tabla original era NA o 0 — usando suma de cuotas como proxy.")
}

# Iterar mes a mes
for (i in seq_len(nrow(amort_con_cobertura_A))) {
  cuota <- amort_con_cobertura_A$Cuota_COP[i]
  pnl <- amort_con_cobertura_A$DailyPnL[i]
  
  # Payment from margin (solo si pnl positivo)
  payment_from_margin <- ifelse(!is.na(pnl) && pnl > 0, min(pnl, cuota), 0)
  borrower_cash <- cuota - payment_from_margin
  
  # Surplus y shortfall respecto a la cuota
  surplus <- ifelse(pnl > cuota, pnl - cuota, 0)
  shortfall <- ifelse(pnl >= 0, max(0, cuota - pnl), pnl) 
  
  # Para la amortización usamos el AbonoCapital original:
  interes_mes <- amort_con_cobertura_A$`Interés (COP)`[i]
  abono_capital <- amort_con_cobertura_A$`Abono a Capital (COP)`[i]
  
  # Si por algún motivo faltan los valores originales de interes/abono, recalculamos aproximado:
  if (is.na(interes_mes) || is.na(abono_capital)) {
    # asumimos interes_mes = proporcional a saldo_cop * tasa_estimada (simple fallback)
    # Pero como dispones de esos valores en tu código original normalmente no entrará aquí.
    interes_mes <- 0
    abono_capital <- cuota - interes_mes
  }
  
  # Actualizar saldo: saldo_cop es saldo al inicio del mes; se reduce por abono_capital
  nuevo_saldo <- max(0, saldo_cop - abono_capital)
  
  # Guardar valores
  amort_con_cobertura_A$PaymentFromMargin[i] <- round(payment_from_margin, 2)
  amort_con_cobertura_A$BorrowerCash[i] <- round(borrower_cash, 2)
  amort_con_cobertura_A$Cuota_Ajustada[i] <- round(max(0, cuota - payment_from_margin), 2)
  amort_con_cobertura_A$Interes_COP[i] <- round(interes_mes, 2)
  amort_con_cobertura_A$AbonoCapital_COP[i] <- round(abono_capital, 2)
  amort_con_cobertura_A$SaldoRestante_COP[i] <- round(nuevo_saldo, 2)
  amort_con_cobertura_A$Surplus[i] <- round(surplus, 2)
  amort_con_cobertura_A$Shortfall[i] <- round(shortfall, 2)
  
  # preparar saldo para el siguiente mes
  saldo_cop <- nuevo_saldo
}

# Agregados acumulados
amort_con_cobertura_A <- amort_con_cobertura_A %>%
  mutate(
    CumSurplus = cumsum(Surplus),
    CumShortfall = cumsum(Shortfall),
    `Valor TRM Mas Alta` = S_serie_A,
    CoveredFlag = PaymentFromMargin >= Cuota_COP
  )

# Salida limpia: seleccionar columnas de interés en orden claro
amort_con_cobertura_A_out <- amort_con_cobertura_A %>%
  select(
    Period = Date,
    Mes,
    `Valor TRM Mas Alta`,
    Cuota_COP,
    DailyPnL,
    PaymentFromMargin,
    BorrowerCash,
    Cuota_Ajustada,
    Surplus,
    CumSurplus,
    Shortfall,
    CumShortfall,
    CoveredFlag
  )

# Tabla resumen
amort_cobertura_A_out <- amort_con_cobertura_A %>%
  select(
    Period =
    Mes,
    Cuota_COP,
    DailyPnL,
    Cuota_Ajustada,
    Surplus,
    CumSurplus,
    CoveredFlag
  )
print(amort_cobertura_A_out)
##               Period Cuota_COP    DailyPnL Cuota_Ajustada  Surplus CumSurplus
## 2025-09-1572      73   5752941         0.0      5752941.1        0          0
## 2025-09-1573      74   5724969   2634966.0      3090003.0        0          0
## 2025-09-1574      75   5779412   -556425.8      5779412.0        0          0
## 2025-09-1575      76   5826936 -13044495.1      5826936.1        0          0
## 2025-09-1576      77   5882697  -1187367.5      5882697.0        0          0
## 2025-09-1577      78   5870195   4198846.7      1671348.8        0          0
## 2025-09-1578      79   5929096  -7535408.4      5929095.5        0          0
## 2025-09-1579      80   6021082   5821180.9       199900.7        0          0
## 2025-09-1580      81   6030614   4514426.7      1516187.6        0          0
## 2025-09-1581      82   6041133   4395766.2      1645367.0        0          0
## 2025-09-1582      83   6043912 -11374903.0      6043911.6        0          0
## 2025-09-1583      84   5984305  -8292930.6      5984305.1        0          0
## 2025-09-1584      85   5926325   5298814.5       627510.4        0          0
## 2025-09-1585      86   5897757  -6754104.3      5897756.9        0          0
## 2025-09-1586      87   5922065    336667.9      5585396.8        0          0
## 2025-09-1587      88   5936304   -657687.3      5936304.2        0          0
## 2025-09-1588      89   5999344  14525363.2            0.0  8526019    8526019
## 2025-09-1589      90   5954805  20832105.6            0.0 14877300   23403319
## 2025-09-1590      91   5950185  -2955551.8      5950185.1        0   23403319
## 2025-09-1591      92   5927100  18073130.8            0.0 12146031   35549350
## 2025-09-1592      93   5938141   3195910.1      2742230.7        0   35549350
## 2025-09-1593      94   5970984    854694.6      5116289.3        0   35549350
## 2025-09-1594      95   6058848  -2081006.0      6058847.9        0   35549350
## 2025-09-1595      96   6146507   1426935.5      4719571.7        0   35549350
## 2025-09-1596      97   6245536   9922553.9            0.0  3677018   39226368
## 2025-09-1597      98   6279822  -5920517.9      6279822.0        0   39226368
## 2025-09-1598      99   6378296 -13150892.8      6378296.1        0   39226368
## 2025-09-1599     100   6319327   5202617.6      1116709.6        0   39226368
## 2025-09-15100    101   6328324 -21574159.2      6328324.1        0   39226368
## 2025-09-15101    102   6408177   9834242.1            0.0  3426065   42652434
## 2025-09-15102    103   6393117   3728448.2      2664668.7        0   42652434
## 2025-09-15103    104   6420172  -1713321.4      6420171.8        0   42652434
## 2025-09-15104    105   6463131  -2636201.2      6463130.8        0   42652434
## 2025-09-15105    106   6452488   4113970.5      2338517.8        0   42652434
## 2025-09-15106    107   6522877   3872023.9      2650853.4        0   42652434
## 2025-09-15107    108   6554268  -6310787.2      6554267.8        0   42652434
## 2025-09-15108    109   6511232  -3490868.5      6511232.4        0   42652434
## 2025-09-15109    110   6578140  -6196872.9      6578139.5        0   42652434
## 2025-09-15110    111   6510266 -20146224.3      6510266.3        0   42652434
## 2025-09-15111    112   6563934   3281250.0      3282683.6        0   42652434
## 2025-09-15112    113   6520139  -1368338.2      6520138.8        0   42652434
## 2025-09-15113    114   6555176 -25165853.9      6555175.7        0   42652434
## 2025-09-15114    115   6581006 -23093828.0      6581006.0        0   42652434
## 2025-09-15115    116   6648431   8261468.9            0.0  1613038   44265472
## 2025-09-15116    117   6673225 -20663163.6      6673225.1        0   44265472
## 2025-09-15117    118   6722822    344127.9      6378693.8        0   44265472
## 2025-09-15118    119   6683099  12669755.4            0.0  5986656   50252128
## 2025-09-15119    120   6678928  -4824310.5      6678927.8        0   50252128
##               CoveredFlag
## 2025-09-1572        FALSE
## 2025-09-1573        FALSE
## 2025-09-1574        FALSE
## 2025-09-1575        FALSE
## 2025-09-1576        FALSE
## 2025-09-1577        FALSE
## 2025-09-1578        FALSE
## 2025-09-1579        FALSE
## 2025-09-1580        FALSE
## 2025-09-1581        FALSE
## 2025-09-1582        FALSE
## 2025-09-1583        FALSE
## 2025-09-1584        FALSE
## 2025-09-1585        FALSE
## 2025-09-1586        FALSE
## 2025-09-1587        FALSE
## 2025-09-1588         TRUE
## 2025-09-1589         TRUE
## 2025-09-1590        FALSE
## 2025-09-1591         TRUE
## 2025-09-1592        FALSE
## 2025-09-1593        FALSE
## 2025-09-1594        FALSE
## 2025-09-1595        FALSE
## 2025-09-1596         TRUE
## 2025-09-1597        FALSE
## 2025-09-1598        FALSE
## 2025-09-1599        FALSE
## 2025-09-15100       FALSE
## 2025-09-15101        TRUE
## 2025-09-15102       FALSE
## 2025-09-15103       FALSE
## 2025-09-15104       FALSE
## 2025-09-15105       FALSE
## 2025-09-15106       FALSE
## 2025-09-15107       FALSE
## 2025-09-15108       FALSE
## 2025-09-15109       FALSE
## 2025-09-15110       FALSE
## 2025-09-15111       FALSE
## 2025-09-15112       FALSE
## 2025-09-15113       FALSE
## 2025-09-15114       FALSE
## 2025-09-15115        TRUE
## 2025-09-15116       FALSE
## 2025-09-15117       FALSE
## 2025-09-15118        TRUE
## 2025-09-15119       FALSE
# Resumen rápido
resumen_A <- amort_con_cobertura_A_out %>%
  summarise(
    total_cuotas = sum(Cuota_COP, na.rm = TRUE),
    total_paid_from_margin = sum(PaymentFromMargin, na.rm = TRUE),
    total_borrower_cash = sum(BorrowerCash, na.rm = TRUE),
    total_surplus = last(CumSurplus),
    total_shortfall = last(CumShortfall),
    pct_covered = total_paid_from_margin / total_cuotas * 100,
    months_fully_covered = sum(CoveredFlag, na.rm = TRUE)
  )

print(resumen_A)
##   total_cuotas total_paid_from_margin total_borrower_cash total_surplus
## 1    298507589               97087139           201420450      50252128
##   total_shortfall pct_covered months_fully_covered
## 1      -159596345    32.52418                    7
  • Escenario 2:
# ----- Configuración -----
# Tomamos hasta 48 meses
n_meses <- 48
n_take_M <- min(n_meses, nrow(flujo_cop_M), nrow(resultados_SM))

# Extraer últimos n_take meses de cada tabla
amort_last_M <- tail(flujo_cop_M, n_take_M)
margin_last_M <- tail(resultados_SM, n_take_M)


# Si no hay columna Date en amort_last, crear fechas alineadas con results_SA (opcional)
if (!"Date" %in% names(amort_last_M)) {
  amort_last_M$Date <- margin_last_M$Date
}

# ----- Construcción de la nueva amortización con cobertura -----
amort_con_cobertura_M <- amort_last_M %>%
  mutate(
    Date = as.Date(Date),
    Cuota_COP = as.numeric(`Cuota (COP)`),
    Interes_COP_original = as.numeric(`Interés (COP)`),
    AbonoCapital_COP_original = as.numeric(`Abono a Capital (COP)`),
    SaldoRestante_COP_original = as.numeric(`Saldo Restante (COP)`),
    DailyPnL = as.numeric(margin_last_M$DailyPnL)
  ) %>%
  # Inicializamos columnas que llenaremos
  mutate(
    PaymentFromMargin = 0,
    BorrowerCash = 0,
    Cuota_Ajustada = NA_real_,
    Interes_COP = NA_real_,
    AbonoCapital_COP = NA_real_,
    SaldoRestante_COP = NA_real_,
    Surplus = 0,
    Shortfall = 0
  )

# Empezar saldo con el primer saldo disponible (saldo inicial al comienzo del período seleccionado)
saldo_cop <- amort_con_cobertura_M$SaldoRestante_COP_original[1]
if (is.na(saldo_cop) || saldo_cop <= 0) {
  # fallback: usar suma de cuotas restantes (solo si el saldo original no está presente)
  saldo_cop <- sum(amort_con_cobertura_M$Cuota_COP, na.rm = TRUE)
  warning("Saldo inicial en tabla original era NA o 0 — usando suma de cuotas como proxy.")
}

# Iterar mes a mes
for (i in seq_len(nrow(amort_con_cobertura_M))) {
  cuota <- amort_con_cobertura_M$Cuota_COP[i]
  pnl <- amort_con_cobertura_M$DailyPnL[i]
  
  # Payment from margin (solo si pnl positivo)
  payment_from_margin <- ifelse(!is.na(pnl) && pnl > 0, min(pnl, cuota), 0)
  borrower_cash <- cuota - payment_from_margin
  
  # Surplus y shortfall respecto a la cuota
  surplus <- ifelse(pnl > cuota, pnl - cuota, 0)
  shortfall <- ifelse(pnl >= 0, max(0, cuota - pnl), pnl) 
  
  # Para la amortización usamos el AbonoCapital original:
  interes_mes <- amort_con_cobertura_M$`Interés (COP)`[i]
  abono_capital <- amort_con_cobertura_M$`Abono a Capital (COP)`[i]
  
  # Si por algún motivo faltan los valores originales de interes/abono, recalculamos aproximado:
  if (is.na(interes_mes) || is.na(abono_capital)) {
    # asumimos interes_mes = proporcional a saldo_cop * tasa_estimada (simple fallback)
    # Pero como dispones de esos valores en tu código original normalmente no entrará aquí.
    interes_mes <- 0
    abono_capital <- cuota - interes_mes
  }
  
  # Actualizar saldo: saldo_cop es saldo al inicio del mes; se reduce por abono_capital
  nuevo_saldo <- max(0, saldo_cop - abono_capital)
  
  # Guardar valores
  amort_con_cobertura_M$PaymentFromMargin[i] <- round(payment_from_margin, 2)
  amort_con_cobertura_M$BorrowerCash[i] <- round(borrower_cash, 2)
  amort_con_cobertura_M$Cuota_Ajustada[i] <- round(max(0, cuota - payment_from_margin), 2)
  amort_con_cobertura_M$Interes_COP[i] <- round(interes_mes, 2)
  amort_con_cobertura_M$AbonoCapital_COP[i] <- round(abono_capital, 2)
  amort_con_cobertura_M$SaldoRestante_COP[i] <- round(nuevo_saldo, 2)
  amort_con_cobertura_M$Surplus[i] <- round(surplus, 2)
  amort_con_cobertura_M$Shortfall[i] <- round(shortfall, 2)
  
  # preparar saldo para el siguiente mes
  saldo_cop <- nuevo_saldo
}

# Agregados acumulados
amort_con_cobertura_M <- amort_con_cobertura_M %>%
  mutate(
    CumSurplus = cumsum(Surplus),
    CumShortfall = cumsum(Shortfall),
    `Valor TRM Media` = S_serie_M,
    CoveredFlag = PaymentFromMargin >= Cuota_COP
  )

# Salida limpia: seleccionar columnas de interés en orden claro
amort_con_cobertura_M_out <- amort_con_cobertura_M %>%
  select(
    Period = Date,
    Mes,
    `Valor TRM Media`,
    Cuota_COP,
    DailyPnL,
    PaymentFromMargin,
    BorrowerCash,
    Cuota_Ajustada,
    Surplus,
    CumSurplus,
    Shortfall,
    CumShortfall,
    CoveredFlag
  )

# Tabla resumen
amort_cobertura_M_out <- amort_con_cobertura_M %>%
  select(
    Period =
    Mes,
    Cuota_COP,
    DailyPnL,
    Cuota_Ajustada,
    Surplus,
    CumSurplus,
    CoveredFlag
  )
print(amort_cobertura_M_out)
##               Period Cuota_COP DailyPnL Cuota_Ajustada Surplus CumSurplus
## 2025-09-1572      73   5164005        0        5164005       0          0
## 2025-09-1573      74   5161971  3275816        1886155       0          0
## 2025-09-1574      75   5163698  3186683        1977014       0          0
## 2025-09-1575      76   5166383  2991886        2174498       0          0
## 2025-09-1576      77   5170130  4122261        1047869       0          0
## 2025-09-1577      78   5168415  3572809        1595606       0          0
## 2025-09-1578      79   5166885  3980860        1186025       0          0
## 2025-09-1579      80   5167450  3584060        1583390       0          0
## 2025-09-1580      81   5168013  3524591        1643422       0          0
## 2025-09-1581      82   5168429  3406067        1762363       0          0
## 2025-09-1582      83   5168729  3963671        1205058       0          0
## 2025-09-1583      84   5171950  3354824        1817125       0          0
## 2025-09-1584      85   5172247  2779203        2393043       0          0
## 2025-09-1585      86   5171009  3989153        1181857       0          0
## 2025-09-1586      87   5174469  3260447        1914022       0          0
## 2025-09-1587      88   5176448  3038424        2138024       0          0
## 2025-09-1588      89   5177163  3327236        1849927       0          0
## 2025-09-1589      90   5180037  3203913        1976124       0          0
## 2025-09-1590      91   5180203  3359510        1820692       0          0
## 2025-09-1591      92   5180240  2994935        2185305       0          0
## 2025-09-1592      93   5181694  2719724        2461971       0          0
## 2025-09-1593      94   5183939  2874693        2309245       0          0
## 2025-09-1594      95   5184938  2968985        2215953       0          0
## 2025-09-1595      96   5186547  2185601        3000947       0          0
## 2025-09-1596      97   5185547  3233626        1951920       0          0
## 2025-09-1597      98   5188063  2815487        2372575       0          0
## 2025-09-1598      99   5187644  2503682        2683962       0          0
## 2025-09-1599     100   5190085  3012295        2177790       0          0
## 2025-09-15100    101   5190862  2210084        2980778       0          0
## 2025-09-15101    102   5194987  3059231        2135756       0          0
## 2025-09-15102    103   5195373  2620180        2575192       0          0
## 2025-09-15103    104   5193912  2676079        2517833       0          0
## 2025-09-15104    105   5194914  2513530        2681384       0          0
## 2025-09-15105    106   5199007  2614108        2584899       0          0
## 2025-09-15106    107   5199173  2709101        2490072       0          0
## 2025-09-15107    108   5201074  2770235        2430838       0          0
## 2025-09-15108    109   5203064  2683081        2519982       0          0
## 2025-09-15109    110   5204633  2649883        2554751       0          0
## 2025-09-15110    111   5201773  2992881        2208893       0          0
## 2025-09-15111    112   5204524  2664886        2539638       0          0
## 2025-09-15112    113   5201461  2399141        2802320       0          0
## 2025-09-15113    114   5205278  2455871        2749407       0          0
## 2025-09-15114    115   5206254  3241784        1964470       0          0
## 2025-09-15115    116   5203039  2798207        2404832       0          0
## 2025-09-15116    117   5204380  1751191        3453189       0          0
## 2025-09-15117    118   5207667  2320788        2886880       0          0
## 2025-09-15118    119   5208535  2309400        2899135       0          0
## 2025-09-15119    120   5214739  2264185        2950554       0          0
##               CoveredFlag
## 2025-09-1572        FALSE
## 2025-09-1573        FALSE
## 2025-09-1574        FALSE
## 2025-09-1575        FALSE
## 2025-09-1576        FALSE
## 2025-09-1577        FALSE
## 2025-09-1578        FALSE
## 2025-09-1579        FALSE
## 2025-09-1580        FALSE
## 2025-09-1581        FALSE
## 2025-09-1582        FALSE
## 2025-09-1583        FALSE
## 2025-09-1584        FALSE
## 2025-09-1585        FALSE
## 2025-09-1586        FALSE
## 2025-09-1587        FALSE
## 2025-09-1588        FALSE
## 2025-09-1589        FALSE
## 2025-09-1590        FALSE
## 2025-09-1591        FALSE
## 2025-09-1592        FALSE
## 2025-09-1593        FALSE
## 2025-09-1594        FALSE
## 2025-09-1595        FALSE
## 2025-09-1596        FALSE
## 2025-09-1597        FALSE
## 2025-09-1598        FALSE
## 2025-09-1599        FALSE
## 2025-09-15100       FALSE
## 2025-09-15101       FALSE
## 2025-09-15102       FALSE
## 2025-09-15103       FALSE
## 2025-09-15104       FALSE
## 2025-09-15105       FALSE
## 2025-09-15106       FALSE
## 2025-09-15107       FALSE
## 2025-09-15108       FALSE
## 2025-09-15109       FALSE
## 2025-09-15110       FALSE
## 2025-09-15111       FALSE
## 2025-09-15112       FALSE
## 2025-09-15113       FALSE
## 2025-09-15114       FALSE
## 2025-09-15115       FALSE
## 2025-09-15116       FALSE
## 2025-09-15117       FALSE
## 2025-09-15118       FALSE
## 2025-09-15119       FALSE
# Resumen rápido
resumen_M <- amort_con_cobertura_M_out %>%
  summarise(
    total_cuotas = sum(Cuota_COP, na.rm = TRUE),
    total_paid_from_margin = sum(PaymentFromMargin, na.rm = TRUE),
    total_borrower_cash = sum(BorrowerCash, na.rm = TRUE),
    total_surplus = last(CumSurplus),
    total_shortfall = last(CumShortfall),
    pct_covered = total_paid_from_margin / total_cuotas * 100,
    months_fully_covered = sum(CoveredFlag, na.rm = TRUE)
  )

print(resumen_M)
##   total_cuotas total_paid_from_margin total_borrower_cash total_surplus
## 1    248940978              138934288           110006690             0
##   total_shortfall pct_covered months_fully_covered
## 1       110006690    55.81013                    0
  • Escenario 3:
# ----- Configuración -----
# Tomamos hasta 48 meses
n_meses <- 48
n_take_B <- min(n_meses, nrow(flujo_cop_P), nrow(resultados_SB))

# Extraer últimos n_take meses de cada tabla
amort_last_B <- tail(flujo_cop_P, n_take_B)
margin_last_B <- tail(resultados_SB, n_take_B)


# Si no hay columna Date en amort_last, crear fechas alineadas con results_SA (opcional)
if (!"Date" %in% names(amort_last_B)) {
  amort_last_B$Date <- margin_last_B$Date
}

# ----- Construcción de la nueva amortización con cobertura -----
amort_con_cobertura_B <- amort_last_B %>%
  mutate(
    Date = as.Date(Date),
    Cuota_COP = as.numeric(`Cuota (COP)`),
    Interes_COP_original = as.numeric(`Interés (COP)`),
    AbonoCapital_COP_original = as.numeric(`Abono a Capital (COP)`),
    SaldoRestante_COP_original = as.numeric(`Saldo Restante (COP)`),
    DailyPnL = as.numeric(margin_last_B$DailyPnL)
  ) %>%
  # Inicializamos columnas que llenaremos
  mutate(
    PaymentFromMargin = 0,
    BorrowerCash = 0,
    Cuota_Ajustada = NA_real_,
    Interes_COP = NA_real_,
    AbonoCapital_COP = NA_real_,
    SaldoRestante_COP = NA_real_,
    Surplus = 0,
    Shortfall = 0
  )

# Empezar saldo con el primer saldo disponible (saldo inicial al comienzo del período seleccionado)
saldo_cop <- amort_con_cobertura_B$SaldoRestante_COP_original[1]
if (is.na(saldo_cop) || saldo_cop <= 0) {
  # fallback: usar suma de cuotas restantes (solo si el saldo original no está presente)
  saldo_cop <- sum(amort_con_cobertura_B$Cuota_COP, na.rm = TRUE)
  warning("Saldo inicial en tabla original era NA o 0 — usando suma de cuotas como proxy.")
}

# Iterar mes a mes
for (i in seq_len(nrow(amort_con_cobertura_B))) {
  cuota <- amort_con_cobertura_B$Cuota_COP[i]
  pnl <- amort_con_cobertura_B$DailyPnL[i]
  
  # Payment from margin (solo si pnl positivo)
  payment_from_margin <- ifelse(!is.na(pnl) && pnl > 0, min(pnl, cuota), 0)
  borrower_cash <- cuota - payment_from_margin
  
  # Surplus y shortfall respecto a la cuota
  surplus <- ifelse(pnl > cuota, pnl - cuota, 0)
  shortfall <- ifelse(pnl >= 0, max(0, cuota - pnl), pnl) 
  
  # Para la amortización usamos el AbonoCapital original:
  interes_mes <- amort_con_cobertura_B$`Interés (COP)`[i]
  abono_capital <- amort_con_cobertura_B$`Abono a Capital (COP)`[i]
  
  # Si por algún motivo faltan los valores originales de interes/abono, recalculamos aproximado:
  if (is.na(interes_mes) || is.na(abono_capital)) {
    # asumimos interes_mes = proporcional a saldo_cop * tasa_estimada (simple fallback)
    # Pero como dispones de esos valores en tu código original normalmente no entrará aquí.
    interes_mes <- 0
    abono_capital <- cuota - interes_mes
  }
  
  # Actualizar saldo: saldo_cop es saldo al inicio del mes; se reduce por abono_capital
  nuevo_saldo <- max(0, saldo_cop - abono_capital)
  
  # Guardar valores
  amort_con_cobertura_B$PaymentFromMargin[i] <- round(payment_from_margin, 2)
  amort_con_cobertura_B$BorrowerCash[i] <- round(borrower_cash, 2)
  amort_con_cobertura_B$Cuota_Ajustada[i] <- round(max(0, cuota - payment_from_margin), 2)
  amort_con_cobertura_B$Interes_COP[i] <- round(interes_mes, 2)
  amort_con_cobertura_B$AbonoCapital_COP[i] <- round(abono_capital, 2)
  amort_con_cobertura_B$SaldoRestante_COP[i] <- round(nuevo_saldo, 2)
  amort_con_cobertura_B$Surplus[i] <- round(surplus, 2)
  amort_con_cobertura_B$Shortfall[i] <- round(shortfall, 2)
  
  # preparar saldo para el siguiente mes
  saldo_cop <- nuevo_saldo
}

# Agregados acumulados
amort_con_cobertura_B <- amort_con_cobertura_B %>%
  mutate(
    CumSurplus = cumsum(Surplus),
    CumShortfall = cumsum(Shortfall),
    `Valor TRM Baja` = S_serie_B,
    CoveredFlag = PaymentFromMargin >= Cuota_COP
  )

# Salida limpia: seleccionar columnas de interés en orden claro
amort_con_cobertura_B_out <- amort_con_cobertura_B %>%
  select(
    Period = Date,
    Mes,
    `Valor TRM Baja`,
    Cuota_COP,
    DailyPnL,
    PaymentFromMargin,
    BorrowerCash,
    Cuota_Ajustada,
    Surplus,
    CumSurplus,
    Shortfall,
    CumShortfall,
    CoveredFlag
  )

# Tabla resumen
amort_cobertura_B_out <- amort_con_cobertura_B %>%
  select(
    Period =
    Mes,
    Cuota_COP,
    DailyPnL,
    Cuota_Ajustada,
    Surplus,
    CumSurplus,
    CoveredFlag
  )
print(amort_cobertura_B_out)
##               Period Cuota_COP    DailyPnL Cuota_Ajustada    Surplus CumSurplus
## 2025-09-1572      73   4042413         0.0      4042412.9        0.0          0
## 2025-09-1573      74   4044787    950698.5      3094088.5        0.0          0
## 2025-09-1574      75   4062049   6029608.4            0.0  1967559.4    1967559
## 2025-09-1575      76   4111623  19575941.8            0.0 15464318.6   17431878
## 2025-09-1576      77   4051142  20126207.5            0.0 16075065.2   33506943
## 2025-09-1577      78   4008528   1667255.7      2341272.6        0.0   33506943
## 2025-09-1578      79   3948782  11499841.0            0.0  7551059.0   41058002
## 2025-09-1579      80   3912845  19273834.0            0.0 15360989.2   56418991
## 2025-09-1580      81   3937798   3767956.1       169842.3        0.0   56418991
## 2025-09-1581      82   3950583  -1289964.1      3950582.9        0.0   56418991
## 2025-09-1582      83   3958819  -5943536.8      3958819.3        0.0   56418991
## 2025-09-1583      84   3963275  11449805.9            0.0  7486530.9   63905522
## 2025-09-1584      85   3940088   3162502.2       777585.9        0.0   63905522
## 2025-09-1585      86   3937865  -6653783.6      3937865.1        0.0   63905522
## 2025-09-1586      87   3947028  15262448.6            0.0 11315420.3   75220943
## 2025-09-1587      88   3986730  -6587556.0      3986730.1        0.0   75220943
## 2025-09-1588      89   3961401 -12536248.3      3961400.7        0.0   75220943
## 2025-09-1589      90   4000929   9185016.5            0.0  5184087.5   80405030
## 2025-09-1590      91   4023853  -3980955.3      4023853.0        0.0   80405030
## 2025-09-1591      92   4047443 -32984776.7      4047443.3        0.0   80405030
## 2025-09-1592      93   4047088 -16916535.3      4047087.6        0.0   80405030
## 2025-09-1593      94   3998840  -6869735.0      3998840.0        0.0   80405030
## 2025-09-1594      95   4014288  22624026.5            0.0 18609738.9   99014769
## 2025-09-1595      96   3989503   1624514.2      2364988.8        0.0   99014769
## 2025-09-1596      97   3982389   9272249.2            0.0  5289859.9  104304629
## 2025-09-1597      98   4048943   3663752.5       385190.1        0.0  104304629
## 2025-09-1598      99   3989284   6005780.8            0.0  2016497.2  106321126
## 2025-09-1599     100   4001090   3271072.3       730017.2        0.0  106321126
## 2025-09-15100    101   4105596   2975480.9      1130115.1        0.0  106321126
## 2025-09-15101    102   4176240   -561097.3      4176240.2        0.0  106321126
## 2025-09-15102    103   4095015   3815776.6       279238.2        0.0  106321126
## 2025-09-15103    104   4063325  -4556181.3      4063325.0        0.0  106321126
## 2025-09-15104    105   3990702   8314426.4            0.0  4323724.9  110644851
## 2025-09-15105    106   3999286   2281096.3      1718189.9        0.0  110644851
## 2025-09-15106    107   3993171  13750163.1            0.0  9756991.6  120401843
## 2025-09-15107    108   3947176   -807277.1      3947176.5        0.0  120401843
## 2025-09-15108    109   3975734   1061082.4      2914652.0        0.0  120401843
## 2025-09-15109    110   3905025  -2479958.4      3905024.9        0.0  120401843
## 2025-09-15110    111   3903536   9370564.8            0.0  5467028.5  125868871
## 2025-09-15111    112   3903333   4627182.5            0.0   723849.1  126592720
## 2025-09-15112    113   3913405   7870914.1            0.0  3957508.8  130550229
## 2025-09-15113    114   3927199   2083805.1      1843393.4        0.0  130550229
## 2025-09-15114    115   3943502   5659702.6            0.0  1716200.5  132266430
## 2025-09-15115    116   3914105   3055281.2       858823.4        0.0  132266430
## 2025-09-15116    117   3916689  -3685434.0      3916689.4        0.0  132266430
## 2025-09-15117    118   3920963  -7351831.5      3920963.1        0.0  132266430
## 2025-09-15118    119   3970878  11018995.9            0.0  7048118.4  139314548
## 2025-09-15119    120   3955130   7845640.7            0.0  3890510.8  143205059
##               CoveredFlag
## 2025-09-1572        FALSE
## 2025-09-1573        FALSE
## 2025-09-1574         TRUE
## 2025-09-1575         TRUE
## 2025-09-1576         TRUE
## 2025-09-1577        FALSE
## 2025-09-1578         TRUE
## 2025-09-1579         TRUE
## 2025-09-1580        FALSE
## 2025-09-1581        FALSE
## 2025-09-1582        FALSE
## 2025-09-1583         TRUE
## 2025-09-1584        FALSE
## 2025-09-1585        FALSE
## 2025-09-1586         TRUE
## 2025-09-1587        FALSE
## 2025-09-1588        FALSE
## 2025-09-1589         TRUE
## 2025-09-1590        FALSE
## 2025-09-1591        FALSE
## 2025-09-1592        FALSE
## 2025-09-1593        FALSE
## 2025-09-1594         TRUE
## 2025-09-1595        FALSE
## 2025-09-1596         TRUE
## 2025-09-1597        FALSE
## 2025-09-1598         TRUE
## 2025-09-1599        FALSE
## 2025-09-15100       FALSE
## 2025-09-15101       FALSE
## 2025-09-15102       FALSE
## 2025-09-15103       FALSE
## 2025-09-15104        TRUE
## 2025-09-15105       FALSE
## 2025-09-15106        TRUE
## 2025-09-15107       FALSE
## 2025-09-15108       FALSE
## 2025-09-15109       FALSE
## 2025-09-15110        TRUE
## 2025-09-15111        TRUE
## 2025-09-15112        TRUE
## 2025-09-15113       FALSE
## 2025-09-15114        TRUE
## 2025-09-15115       FALSE
## 2025-09-15116       FALSE
## 2025-09-15117       FALSE
## 2025-09-15118        TRUE
## 2025-09-15119        TRUE
# Resumen rápido
resumen_B <- amort_con_cobertura_B_out %>%
  summarise(
    total_cuotas = sum(Cuota_COP, na.rm = TRUE),
    total_paid_from_margin = sum(PaymentFromMargin, na.rm = TRUE),
    total_borrower_cash = sum(BorrowerCash, na.rm = TRUE),
    total_surplus = last(CumSurplus),
    total_shortfall = last(CumShortfall),
    pct_covered = total_paid_from_margin / total_cuotas * 100,
    months_fully_covered = sum(CoveredFlag, na.rm = TRUE)
  )

print(resumen_B)
##   total_cuotas total_paid_from_margin total_borrower_cash total_surplus
## 1    191429417              108937566            82491852     143205059
##   total_shortfall pct_covered months_fully_covered
## 1       -90555060    56.90743                   19

Al comparar el comportamiento del crédito frente a la cobertura mediante futuros en el mismo horizonte (años 6 al 10), observamos que la estrategia de cobertura protegió de forma significativa el costo de la deuda frente a la depreciación del peso. En los tres escenarios, la cobertura no solo aseguró el cumplimiento de las cuotas del crédito, sino que además generó excedentes gracias al rendimiento de los contratos de futuros. Esto evidencia que la cobertura fue efectiva no únicamente como un mecanismo de protección, sino también como un apoyo financiero adicional que alivió el flujo de caja y reforzó nuestra posición frente a la volatilidad cambiaria.

Conclusiones

A lo largo del análisis pudimos comprobar que el comportamiento del dólar es un factor decisivo en el costo final de la deuda. Cuando la moneda se deprecia, las cuotas en pesos aumentan y la carga financiera se hace más pesada; mientras que, si el peso se fortalece, el financiamiento resulta más accesible. Esta conclusión refuerza la idea de que las empresas expuestas a pasivos en moneda extranjera deben considerar mecanismos de cobertura cambiaria (Union, 2025).

Asimismo, el uso de futuros como herramienta de gestión de riesgo mostró ser una estrategia efectiva, ya que permitió cubrir una parte importante de la deuda y, en ciertos escenarios, generar excedentes que mejoraron el flujo de caja. Esto demuestra que, aunque los derivados tienen un componente especulativo, bien utilizados pueden convertirse en un apoyo clave para estabilizar los costos financieros (Bolsa de Valores de Colombia, 2025) (Valores de Colombia, 2025).

Finalmente, este ejercicio nos deja la lección de que la planeación financiera no se limita a calcular cuotas o intereses, sino a diseñar estrategias que reduzcan la incertidumbre y fortalezcan la posición del inversionista frente a la volatilidad del mercado.

Referencias

Banco de la República. (2025). Informe de política monetaria. https://www.banrep.gov.co/es/borrador-789
Bancolombia. (2025). Perspectivas económicas 2025. https://www.bancolombia.com/empresas/capital-inteligente/actualidad-economica-sectorial/perspectivas-economicas-2025?utm_source
Bank, U. P. (2025). Equipment financing. https://www.unitedprairiebank.com/equipment-financing
Kloeden, P. E., & Platen, E. (1992). Numerical solution of stochastic differential equations. Springer. https://doi.org/10.1007/978-3-662-12616-5
República, L. (2025). Perspectivas económicas en la región y en los mercados. https://www.larepublica.co/especiales/congreso-andi-en-cartagena/panel-perspectivas-economicas-en-la-region-y-en-los-mercados-de-la-cec-4202728?utm_source
Social, B. C. (2025). Tasas de crédito productivo. https://www.bancocajasocial.com/content/dam/bcs/documentos/informacion-corporativa/tasas-precios-y-comisiones/credito-empresas/Tasas-Credito-Productivo.pdf
Union, T. (2025). Pronóstico USD/COP a largo plazo. https://tradersunion.com/es/currencies/forecast/usd-cop/long-term-forecast/?utm_source
Valores de Colombia, B. de. (2025). Derivados TRXV25F - resumen. https://www.bvc.com.co/derivados/trxv25f?tab=resumen