Introducción

En este informe, se realizará un análisis de series temporales del precio de la acción de Bancolombia (BIC), con el objetivo de extraer señales y descomponer los patrones subyacentes en los datos. El periodo de análisis abarca desde enero de 2010 hasta diciembre de 2024, con frecuencia mensual, lo que permitirá identificar tendencias, estacionalidad y componentes irregulares en el comportamiento del precio de la acción.

El análisis de series temporales es una herramienta fundamental para entender la dinámica de los mercados financieros. En el caso de Bancolombia, uno de los bancos más importantes de Colombia, este estudio proporcionará insights valiosos sobre cómo ha evolucionado su valoración en el mercado a lo largo del tiempo. La identificación de patrones y tendencias no solo ayuda a comprender el desempeño histórico del activo, sino que también sirve como base para la planificación económica y la toma de decisiones estratégicas, tanto para inversionistas como para los gestores del propio banco.

Análisis exploratorio de la variable

#Cargar librerías necesarias
library(readxl)  # Para leer archivos Excel
library(tseries)  # Para pruebas de estacionariedad
library(forecast)  # Para modelado ARIMA y pronósticos
library(ggplot2)  # Para visualización de datos
library(plotly)  # Para gráficos interactivos
library(timetk)   

Cargar base de datos

library(readxl)
data_col <- read_excel("C:/Users/juand/OneDrive/Escritorio/PACCIONES COL.xlsx", 
    col_types = c("date", "numeric"))
View(data_col)

Un paso indispensable es declarar la variable como serie temporal:

# Convertir/declarar variable 1=ENER en serie de tiempo mensual
variable1_ts <- ts(data_col$BIC, start = c(2010, 1), frequency = 12)
variable1_ts
##           Jan      Feb      Mar      Apr      May      Jun      Jul      Aug
## 2010 22060.15 22159.52 22139.65 22874.99 23351.96 23610.32 26134.33 28240.97
## 2011 26849.79 26730.55 29016.06 28618.58 28916.69 29016.06 28817.32 28618.58
## 2012 27980.00 27900.00 28100.00 28900.00 27200.00 26980.00 27180.00 25800.00
## 2013 30480.00 28800.00 28800.00 30000.00 27500.00 26400.00 26800.00 26380.00
## 2014 22000.00 24100.00 26940.00 26600.00 25600.00 26540.00 28300.00 29500.00
## 2015 27460.00 25080.00 24500.00 25200.00 24600.00 26700.00 26020.00 24960.00
## 2016 22600.00 23800.00 24700.00 25500.00 23700.00 23800.00 24300.00 26800.00
## 2017 25480.00 25000.00 26260.00 27040.00 30480.00 31780.00 30600.00 33000.00
## 2018 32520.00 30160.00 30220.00 33200.00 33120.00 35320.00 33460.00 32800.00
## 2019 33340.00 35980.00 39600.00 39400.00 37400.00 38640.00 39300.00 39580.00
## 2020 42440.00 39000.00 24100.00 25320.00 25000.00 24000.00 26800.00 26000.00
## 2021 30600.00 31400.00 28750.00 27920.00 27000.00 26400.00 27100.00 31070.00
## 2022 39500.00 39040.00 43380.00 38820.00 45320.00 33540.00 35990.00 34200.00
## 2023 41990.00 35900.00 34990.00 36000.00 27800.00 31000.00 33380.00 29100.00
## 2024 32760.00 32940.00 34280.00 33800.00 35900.00 35300.00 36520.00 37980.00
## 2025 43100.00 47400.00                                                      
##           Sep      Oct      Nov      Dec
## 2010 29194.93 29413.54 28161.48 29314.17
## 2011 28121.73 28817.32 27426.14 28300.59
## 2012 26400.00 28660.00 27720.00 30000.00
## 2013 26900.00 26100.00 24300.00 23820.00
## 2014 27680.00 28200.00 27600.00 27640.00
## 2015 23720.00 23540.00 21660.00 20980.00
## 2016 26100.00 26500.00 24900.00 25220.00
## 2017 32740.00 28200.00 29000.00 29980.00
## 2018 31900.00 30280.00 32180.00 30400.00
## 2019 39500.00 41100.00 41680.00 44000.00
## 2020 24280.00 24500.00 27680.00 34980.00
## 2021 32960.00 33680.00 32370.00 34700.00
## 2022 31100.00 35500.00 40000.00 42500.00
## 2023 30810.00 29350.00 31720.00 33200.00
## 2024 36240.00 37800.00 38580.00 37600.00
## 2025

Ahora si calculamos estadísticas descriptivas

En la tabla se presentan las estadísticas descriptivas del precio de la acción de Bancolombia (BIC) durante el periodo analizado. A continuación, se interpretan los resultados:

• El precio mínimo registrado fue de 20,980 pesos, mientras que el máximo alcanzó 47,400 pesos. Esta amplia diferencia refleja la volatilidad y los cambios significativos en la valoración del activo a lo largo del tiempo.

• El precio promedio de la acción fue de 30,247.61 pesos. Este valor sirve como referencia central para entender el comportamiento típico del activo en el mercado.

• Con un valor de 28,808.66 pesos, la mediana indica que el 50% de los precios observados estuvieron por debajo de este nivel y el otro 50% por encima. Al ser ligeramente inferior a la media, sugiere la presencia de algunos precios atípicos elevados que han influido en el promedio.

• La desviación estándar de 5,530.663 pesos señala una variabilidad considerable en los precios alrededor de la media. Esto confirma que el precio de la acción ha experimentado fluctuaciones importantes durante el periodo estudiado.

• Con un valor de 0.1828 (18.28%), la variabilidad relativa del precio de la acción no es excesivamente alta. Esto indica que, aunque hay oscilaciones, los precios no son extremadamente dispersos en relación con el promedio, lo que podría sugerir cierta estabilidad relativa en el comportamiento del activo.

Implicaciones: La combinación de una mediana menor que la media y una desviación estándar relativamente alta refleja la presencia de periodos con precios excepcionalmente altos que han elevado el promedio. Este patrón es común en activos financieros sujetos a ciclos económicos o eventos específicos del mercado. El coeficiente de variación moderado sugiere que, a pesar de las fluctuaciones, el precio de la acción de Bancolombia no ha sido extremadamente volátil en comparación con otros activos de mayor riesgo.

# Calcular estadísticas descriptivas básicas
descriptive_stats <- data.frame(
  Min = min(variable1_ts),
  Max = max(variable1_ts),
  Media = mean(variable1_ts),
  Mediana = median(variable1_ts),
  DesviacionEstandar = sd(variable1_ts),
  CoefVar = sd(variable1_ts) / mean(variable1_ts)
)
print(descriptive_stats)
##     Min   Max    Media  Mediana DesviacionEstandar   CoefVar
## 1 20980 47400 30247.61 28808.66           5530.663 0.1828463

Análisis de la evolución del precio de la acción de Bancolombia (BIC)

En la Figura 1 se observa la trayectoria del precio de la acción de Bancolombia desde 2010 hasta 2025, donde destacan los siguientes patrones:

  1. En la serie original del precio de acción de Bancolombia, observamos que el precio de acción máximo se da en 2025 y el valor mínimo del precio de la acción se ubicó en 2015.

  2. A partir de 2015, el precio experimenta un crecimiento significativo, superando los 30,000 pesos y alcanzando máximos históricos en años posteriores. Este comportamiento refleja períodos de fortalecimiento del sector financiero colombiano y posiblemente una mayor confianza de los inversionistas en el banco.

  3. A medida que el precio aumenta, también se intensifican las oscilaciones, con picos pronunciados y caídas abruptas. Por ejemplo:

    • Un repunte notable entre 2016 y 2018, seguido de una corrección.
    • Un máximo destacado hacia 2021-2022, coincidiendo con la recuperación postpandemia.
  4. En 2023, se evidencia una desaceleración o disminución en el precio, posiblemente vinculada a:

    • Un entorno económico menos favorable (ej. inflación alta, menor crecimiento del PIB).
    • Cambios regulatorios en el sector bancario.
    • Pérdida de impulso en la rentabilidad del banco.

Implicaciones:
La Figura 1 sugiere que, aunque el precio de Bancolombia ha mostrado una tendencia alcista a largo plazo, su trayectoria está marcada por volatilidad cíclica. Esto subraya la importancia de considerar estrategias de inversión que mitiguen riesgos ante fluctuaciones abruptas.

Gráfico inicial de la variable en niveles - Original

library(ggplot2)
library(plotly)

# Convertir la serie temporal a un vector numérico para lograr graficar con ggplot2
data_col$variable1 <- as.numeric(variable1_ts)

# Crear el gráfico
grafico_serie <- ggplot(data_col, aes(x = seq.Date(from = as.Date("2010-01-01"), by = "month", length.out = nrow(data_col)), 
                                      y = variable1)) +
  geom_line(color = "grey", linewidth = 0.4) +  # Cambiado 'size' por 'linewidth'
  geom_point(color = "black", size = 0.1) +
  ggtitle("Figura 1: Evolución del precio de la acción de Bancolombia") +
  xlab("Tiempo") +
  ylab("Precio") +
  theme_minimal()

ggplotly(grafico_serie)

Extracción de señales

Muchas series de tiempo son una combinación de varias influencias. Es por eso que separar la tendencia, la estacionalidad y los componentes aleatorios permite entender mejor qué está impulsando los cambios en la serie.

Si analizamos el precio de la acción de Bancolombia (BIC), podríamos querer saber si su comportamiento se debe a una tendencia real del mercado financiero o a fluctuaciones estacionales propias del sector bancario.

Los modelos de pronóstico funcionan mejor cuando las señales subyacentes están bien definidas. Por ejemplo, si eliminamos la estacionalidad de la serie del precio de BIC, los modelos predictivos pueden enfocarse en la tendencia real y reducir errores en las proyecciones.

Detectar cambios inesperados en la serie es más fácil cuando se eliminan componentes predecibles. Ejemplo: Si hay una caída abrupta en el precio de la acción, podemos verificar si es una anomalía (ruido) o un cambio estructural en el sector financiero.

En conclusión, la descomposición de series de tiempo permite comprender mejor los datos, mejorar predicciones y tomar decisiones más estratégicas. Es una herramienta clave en el análisis financiero, especialmente en entornos donde las fluctuaciones en los precios de las acciones pueden afectar inversiones, políticas económicas y estrategias corporativas.

# Cargar librerías necesarias
library(ggplot2)
library(plotly)

# Descomposición de la serie temporal
stl_decomp_var1 <- stl(variable1_ts, s.window = "periodic")

# Convertir la descomposición a un data frame para graficar con ggplot2
stl_df_var1 <- data.frame(
  Time = rep(time(variable1_ts), 4),  # Tiempo repetido para cada componente (son 4 componentes)
  Value = c(stl_decomp_var1$time.series[, "seasonal"], 
            stl_decomp_var1$time.series[, "trend"], 
            stl_decomp_var1$time.series[, "remainder"], 
            variable1_ts),
  Component = rep(c("Estacional", "Tendencia", "Residuo", "Serie Original"), each = length(variable1_ts))
)

# Crear gráfico con ggplot2
p <- ggplot(stl_df_var1, aes(x = Time, y = Value, color = Component)) +
  geom_line() +
  facet_wrap(~Component, scales = "free_y", ncol = 1) + 
  theme_minimal() +
  labs(title = "Figura 2.Descomposición temporal del precio de la acción de BIC",
       x = "Tiempo",
       y = "Valor")

# Convertir a gráfico interactivo con plotly
ggplotly(p)

Interpretación Figura 2.Descomposición temporal

La Figura 2 presenta la descomposición de la serie temporal del precio de la acción de Bancolombia en sus tres componentes principales: tendencia, estacionalidad y residuos. Esta desagregación nos permite entender mejor los distintos factores que influyen en el comportamiento del precio.

Componente estacional

El componente estacional muestra patrones recurrentes a lo largo de todo el período analizado. Se observa que los picos estacionales suelen concentrarse a finales de cada año, especificamente en diciembre, mientras que los valores mínimos aparecen de forma consistente mayo.Estos movimientos estacionales podrían estar relacionados con ciclos financieros como la publicación de resultados trimestrales o el pago de dividendos.

Componente de residuo

Los residuos representan la parte de la serie que no puede explicarse por los componentes de tendencia o estacionalidad. Estas variaciones irregulares incluyen reacciones a eventos específicos como cambios regulatorios, crisis económicas o noticias corporativas. Eventos como la pandemia de COVID-19 en 2020 son claramente visibles en este componente residual.

Componente de tendencia

El componente de tendencia del precio de la acción de Bancolombia entre 2010 y 2024 muestra un comportamiento general alcista, con algunos periodos de estancamiento y caídas asociadas a factores macroeconómicos. Entre 2010 y 2016, la acción presentó leves fluctuaciones sin una dirección clara, pero a partir de 2017 inició una recuperación sostenida que alcanzó su punto más alto para 2019 justo antes de la caída abrupta en 2020, atribuible al impacto de la pandemia del COVID-19. Desde 2021, la tendencia retomó un camino ascendente con algunas oscilaciones, alcanzando nuevos máximos hacia finales de 2024, lo que refleja una recuperación sólida y una renovada confianza del mercado en el desempeño financiero de la entidad.

Después de la descomposición temporal de cada variable, se extrae la variable ajustada por estacionalidad para graficarla junto con la serie original:

Se crea la variable1 ajustada por estacionalidad

# Extraer los componentes de la descomposición
variable1_sa <- variable1_ts - stl_decomp_var1$time.series[, "seasonal"]

Ahora si, graficamos la serie original VS ajustada por estacionalidad

library(ggplot2)
library(plotly)
# Crear vector de fechas correctamente alineado con la serie
fechas_var1 <- seq.Date(from = as.Date("2010-01-01"), by = "month", length.out = length(variable1_ts))

# Gráfico mejorado con fechas en el eje X
grafico_ajustada_var1 <- ggplot() +
  geom_line(aes(x = fechas_var1, y = variable1_ts), color = "grey", size = 0.5, linetype = "solid", name = "Serie Original") +
  geom_line(aes(x = fechas_var1, y = variable1_sa), color = "black", size = 0.6, linetype = "solid", name = "Serie Ajustada") +
  ggtitle("Figura 3.BIC:Serie Original vs Serie Ajustada por Estacionalidad") +
  xlab("Tiempo") +
  ylab("Precio BIC") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Rotar etiquetas para mejor visualización

# Convertir a gráfico interactivo
ggplotly(grafico_ajustada_var1)

Con base en el gráfico, se observa que la serie original y la serie ajustada por estacionalidad del precio de la acción de Bancolombia se mantienen muy próximas entre sí a lo largo del tiempo, lo que indica que el componente estacional tiene un impacto mínimo sobre la variable analizada. Esta cercanía sugiere que los movimientos del precio están mayormente determinados por factores estructurales o coyunturales, más que por patrones estacionales recurrentes.

Para la gestión financiera de Bancolombia, este hallazgo permite contar con herramientas más precisas para la toma de decisiones estratégicas. La serie ajustada facilita la evaluación del desempeño real del precio de la acción, al eliminar cualquier ruido estacional, lo cual contribuye al diseño de estrategias de comunicación más claras con los inversionistas, enfocadas en tendencias fundamentales. Además, mejora la precisión de los modelos predictivos al basarse en una serie depurada.

En el ámbito de las inversiones, esta metodología permite a los actores del mercado distinguir con mayor claridad entre movimientos estructurales y fluctuaciones transitorias. Así, los gestores de portafolios pueden optimizar sus decisiones de asignación de activos con base en un análisis más robusto y confiable

Ahora graficamos la serie original vs tendencia

library(ggplot2)
library(plotly)

# Convertir la serie a un vector numérico
variable1_vec <- as.numeric(variable1_ts)
tendencia_var1 <- as.numeric(stl_decomp_var1$time.series[, "trend"])

# Asegurar que 'fechas' tenga la misma longitud
fechas <- seq.Date(from = as.Date("2010-01-01"), by = "month", length.out = length(variable1_ts))

# Gráfico interactivo de la serie original vs tendencia
grafico_tendencia_var1 <- ggplot() +
  geom_line(aes(x = fechas, y = variable1_vec, color = "Serie Original"), size = 0.7, linetype = "solid") +
  geom_line(aes(x = fechas, y = tendencia_var1, color = "Tendencia"), size = 0.8, linetype = "solid") +
  scale_color_manual(values = c("Serie Original" = "grey", "Tendencia" = "black")) +
  ggtitle("Figura 4.BIC:Serie Original vs Tendencia") +
  xlab("Tiempo") +
  ylab("Precio") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Rotar etiquetas del eje X

# Convertir a gráfico interactivo con plotly
ggplotly(grafico_tendencia_var1)

Se observa que, a pesar de la volatilidad presente en la serie original, la tendencia revela un crecimiento sostenido a largo plazo. La línea de tendencia suaviza las fluctuaciones de corto plazo y permite identificar periodos clave, como la caída abrupta en 2020 asociada a la pandemia y la posterior recuperación. Hacia 2025, la tendencia continúa en ascenso, lo que sugiere una percepción positiva del mercado y una valorización estructural del activo. Esta representación es útil para enfocar el análisis en los movimientos fundamentales del precio, eliminando el ruido generado por eventos coyunturales o de alta frecuencia.

Ahora calculamos la tasa de crecimiento de la serie original vs tendencia:

# Cálculo de la tasa de crecimiento anual
tasa_crecimiento_var1 <- (variable1_ts[(13:length(variable1_ts))] / variable1_ts[1:(length(variable1_ts) - 12)] - 1) * 100
tasa_tendencia_var1 <- (tendencia_var1[(13:length(tendencia_var1))] / tendencia_var1[1:(length(tendencia_var1) - 12)] - 1) * 100

# Crear vector de fechas que coincida con las tasas (empezando desde enero 2011)
fechas_corregidas_var1 <- seq(from = as.Date("2011-01-01"), 
                             by = "month", 
                             length.out = length(tasa_crecimiento_var1))

# Verificar longitudes
print(length(fechas_corregidas_var1))
## [1] 170
print(length(tasa_crecimiento_var1))
## [1] 170
print(length(tasa_tendencia_var1))
## [1] 170
library(ggplot2)
library(plotly)

# Gráfico de la tasa de crecimiento anual variable 1
grafico_crecimiento_var1 <- ggplot() +
  geom_line(aes(x = fechas_corregidas_var1, y = tasa_crecimiento_var1), color = "grey", size = 0.7) +
  geom_line(aes(x = fechas_corregidas_var1, y = tasa_tendencia_var1), color = "black", size = 0.8, linetype = "dashed") +
  ggtitle("Figura 5.BIC: Tasa de crecimiento anual % de la serie Original y la tendencia") +
  xlab("Tiempo") +
  ylab("% de Crecimiento Anual") +
  theme_minimal()

# Convertir a gráfico interactivo
ggplotly(grafico_crecimiento_var1)

Interpretación Figura 5. Tendencia VS original: crecimiento anual%

La Figura 5 compara la tasa de crecimiento anual del precio original de BIC con su componente tendencial, revelando que:

Se observa una alta volatilidad en la serie original, con variaciones anuales que en algunos momentos superan el 50% o caen por debajo del -25%, reflejando la sensibilidad del mercado a eventos coyunturales.

La tendencia suaviza estos movimientos, revelando ciclos económicos más prolongados.

Se destacan tres momentos: una desaceleración marcada entre 2012 y 2016, una recuperación sostenida entre 2017 y 2019, y una fuerte caída en 2020 seguida de un repunte gradual y positivo. Hacia 2025, la tendencia indica un crecimiento anual positivo constante, lo que sugiere una recuperación sólida y sostenida del activo.

Modelo ARIMA

Un modelo ARIMA (Autoregressive Integrated Moving Average) es una herramienta estadística utilizada para analizar y predecir series de tiempo. En términos simples, es como una “bola de cristal matemática” que usa datos pasados para estimar valores futuros, especialmente útil en finanzas para preveer precios, ventas o ingresos.

Desglose del nombre ARIMA

Autoregressive o autorregresivo (AR) → Usa valores pasados para predecir el futuro

Integrated (I) → Ajusta tendencias en los datos para hacerlos estacionarios (sin patrones cambiantes en el tiempo).

MAving Average o media móvil (MA)- → Suaviza fluctuaciones aleatorias a partir de errores pasados.

ARIMA combina estos tres elementos para crear una predicción más precisa.

Metodología Box-Jenkins

La metodología Box-Jenkins es un enfoque sistemático para construir modelos ARIMA con el objetivo de analizar y pronosticar series de tiempo. Fue desarrollada por George Box y Gwilym Jenkins y se basa en cuatro etapas clave:

1️⃣ Identificación 2️⃣ Estimación 3️⃣ Validación 4️⃣ Pronóstico

Se usa especialmente cuando se quiere encontrar el modelo ARIMA más adecuado para una serie de tiempo.

Antes de empezar a aplicar la metodlogía BOX-JENKINS, debemos dividir el conjunto de datos de prueba y entrenamiento

✅ Para entrenar el modelo con datos históricos sin usar información futura. ✅ Para evaluar la precisión del modelo comparando sus predicciones con los datos reales de prueba.

💡 Esta división es clave en modelos predictivos para evitar sobreajuste y evaluar el rendimiento en datos no vistos.

División en conjunto de entrenamiento y prueba para la variable del precio de la acción de Bancolombia

El código siguiente divide la serie temporal (variable1_ts) en dos subconjuntos:

• Conjunto de entrenamiento (train): Datos desde enero de 2010 hasta septiembre de 2024.

• Conjunto de prueba (test): Datos desde noviembre de 2024 hasta diciembre 2024.

Esto se hace para evaluar el desempeño de modelos de predicción en datos no vistos.

train_size <- length(variable1_ts) - 3 # Se deja fuera los últimos 3 valores para usarlos como set de prueba
train_ts <- window(variable1_ts, end = c(2024, 9))  # Entrenamiento hasta septiembre 2024
test_ts <- window(variable1_ts, start = c(2024, 10))  # Prueba inicia desde octubre 2024

Paso 1: Identificación del modelo

Identificar estacionariedad En el análisis de series de tiempo, una serie es estacionaria si su comportamiento es constante a lo largo del tiempo, es decir:

✅ Su promedio no cambia con el tiempo. ✅ Su variabilidad (qué tanto fluctúa) se mantiene estable. ✅ Su relación con valores pasados es siempre la misma.

¿Cómo saber si una serie es estacionaria?

1️⃣ Observando un gráfico: Si el gráfico de la serie muestra una tendencia creciente o decreciente, o si las variaciones se hacen más grandes con el tiempo, la serie probablemente no es estacionaria.

2️⃣ Usando la prueba de Dickey-Fuller Aumentada (ADF): Es una prueba estadística que nos dice si la serie tiene una tendencia fuerte. Si el p-valor de la prueba es mayor a 0.05, significa que la serie no es estacionaria.

¿Cómo hacer una serie estacionaria?

Si encontramos que la serie no es estacionaria, podemos transformarla para que lo sea:

✅ Diferenciación: Restamos cada valor con su valor anterior. Esto elimina tendencias crecientes o decrecientes. ✅ Tomar el logaritmo: Si la variabilidad crece con el tiempo, aplicar un logaritmo estabiliza la varianza. ✅ Eliminar tendencias o ajustar estacionalidad: Si hay patrones repetitivos, podemos restarlos o modelarlos por separado.

Test de Dickey-Fuller

El test de Dickey-Fuller aumentado (ADF) se usa para verificar si una serie temporal es estacionaria, es decir, si sus propiedades estadísticas (media y varianza) permanecen constantes en el tiempo.

HO: Serie no estacionaria HI: Serie estacionaria

¿Qué significa el p-valor?

Si el p-valor es bajo (< 0.05) → Rechazamos la hipótesis nula y concluimos que la serie es estacionaria. Si el p-valor es alto (> 0.05) → No podemos rechazar la hipótesis nula, lo que indica que la serie no es estacionaria.

A continuación se aplica el test ADF para validar estacionariedad en el conjunto de entrenamiento de la variable 1, que es la elegida para pronosticar:

library(tseries)
# Prueba de estacionariedad con Augmented Dickey-Fuller (ADF)
adf_test <- adf.test(train_ts) #Se aplica el test ADF a la variable 1 (conjunto de entrenamiento)
print(adf_test) # se muestra el resultado del test
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_ts
## Dickey-Fuller = -3.4531, Lag order = 5, p-value = 0.04877
## alternative hypothesis: stationary

Aunque el valor p es levemente menor que 0.05, se debe hacer otra diferenciación ya que prima el analisis del gráfico y este muestra que no hay estacionariedad.

Diferenciación en niveles

Primera diferenciación

#Se crea un nuevo objeto o variable que se llama train_diff, en donde se diferencia la variable 1 , una sola vez:
train_diff <- diff(train_ts, differences = 1) 

A continuación, se realiza el gráfico de la serie original y diferenciada (una vez) de la variable 1 para ver graficamente el cambio o ajuste:

# Graficar la serie original en un gráfico separado
  p2 <- ggplot(data.frame(Tiempo = time(train_ts), variable1 = as.numeric(train_ts)), aes(x = Tiempo, y = variable1)) +
    geom_line(color = "blue") +
    ggtitle("Variable 1:Serie Original") +
    xlab("Tiempo") + ylab("Gwh")
  
  ggplotly(p2)  # Convertir en gráfico interactivo
  # Graficar la serie diferenciada en un gráfico separado
  p3 <- ggplot(data.frame(Tiempo = time(train_ts)[-1], variable1_Diff = as.numeric(train_diff)), aes(x = Tiempo, y = variable1_Diff)) +
    geom_line(color = "red") +
    ggtitle("Precio de la acción Bancolombia:Serie Estacionaria (Una diferenciación)") +
    xlab("Tiempo") + ylab("variable 1 diferenciada")
  
  ggplotly(p3)  # Convertir en gráfico interactivo

Segunda diferenciación

Ahora probamos estacionariedad en la serie diferenciada ( en nivel)

adf_test_diff <- adf.test(train_diff2)
print(adf_test_diff)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_diff2
## Dickey-Fuller = -9.2432, Lag order = 5, p-value = 0.01
## alternative hypothesis: stationary

El valor-p ahora es mucho menor a 0.05 con una segunda diferencia en niveles y el gráfico presento una mejoría.

En el test ADF se muestra que el valor que puede tomar d=2:

Identificación manual de p y q

Con el fin de identificar que valores toma p y q se utiliza:

ACF (Autocorrelation Function): Muestra la correlación de la serie con sus rezagos y ayuda a determinar el parámetro q en un modelo ARIMA(p, d, q).

PACF (Partial Autocorrelation Function): Muestra la correlación parcial entre la serie y un rezago específico, eliminando el efecto de rezagos intermedios. Ayuda a determinar el parámetro p en un modelo ARIMA(p, d, q).

Recordemos El eje X representa los rezagos (lags). El eje Y muestra la autocorrelación parcial en cada rezago. Las líneas azules punteadas son los intervalos de confianza (aproximadamente 95%). Si una barra sobrepasa estos límites, indica una autocorrelación significativa. Si las barras caen dentro de los límites, no son significativamente diferentes de cero.

En el código siguiente se crean los correlogramas para determinar los posibles valores que puede tomar el parámetro p y q:

library(forecast)
# Graficar ACF y PACF
acf_plot <- ggAcf(train_ts, lag.max = 6) + ggtitle("Autocorrelation Function (ACF)-Determinar q")
pacf_plot <- ggPacf(train_ts, lag.max = 6) + ggtitle("Partial Autocorrelation Function (PACF)-Determinar p")

ggplotly(acf_plot)
ggplotly(pacf_plot)

Interpretación correlogramas

Se puede observar que los valores que podrian tomar p y q serian:

p = 1

q = 1 , 2, 3, 4, 5 , 6 (Óptimo 1)

El modelo óptimo para esta variable seria (1,2,1)

Paso 2. Estimación manual del modelo

AIC y BIC: Se usan para comparar modelos; cuanto más bajos, mejor.

Métricas de evaluación

Mean Absolute Error (MAE)

Representa el error absoluto promedio entre las predicciones del modelo y los valores reales.

Root Mean Squared Error (RMSE)

Similar al MAE, pero da más peso a los errores grandes, porque eleva las diferencias al cuadrado antes de promediarlas.

Comparación con MAE: Como el RMSE es mayor que el MAE, es posible que haya algunos errores grandes que estén influyendo más en el RMSE.

Mean Absolute Percentage Error (MAPE) = 5.28%

Expresa el error en términos relativos, como porcentaje del valor real.

Regla general: MAPE < 10% → Muy buen modelo ✅ 10%-20% → Modelo aceptable 👍 20%-50% → Modelo pobre ⚠️ 50% → Modelo muy malo ❌

En este caso, un MAPE de 5.28% sugiere un muy buen modelo para pronostico.

Estimación del modelo identificado (1,2,1)

# Cálculo manual de modelo ARIMA
manual_arima_model <- Arima(train_ts, order = c(1,2,1)) #Se va a estimar un modelo Arima de orden (1,2,1)
summary(manual_arima_model)
## Series: train_ts 
## ARIMA(1,2,1) 
## 
## Coefficients:
##           ar1     ma1
##       -0.1276  -1.000
## s.e.   0.0750   0.015
## 
## sigma^2 = 6250336:  log likelihood = -1619.23
## AIC=3244.47   AICc=3244.61   BIC=3253.96
## 
## Training set error measures:
##                     ME     RMSE      MAE       MPE     MAPE      MASE
## Training set -61.26059 2471.656 1601.917 -0.543127 5.285126 0.3428324
##                     ACF1
## Training set 0.001065692

Significancia de coefientes

library(lmtest)
# Evaluar la significancia estadística de los coeficientes del modelo ARIMA
coeftest(manual_arima_model)
## 
## z test of coefficients:
## 
##      Estimate Std. Error  z value Pr(>|z|)    
## ar1 -0.127641   0.074990  -1.7021  0.08873 .  
## ma1 -1.000000   0.015023 -66.5666  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Interpretación significancia coeficientes

ma1, es altamente significativo (***), lo que significa que este coeficiente tiene un impacto importante en el modelo. Como al menos uno de los dos componentes e significativo, entonces continuo con la validación de residuos del modelo.

Paso 3. Validación de residuos del modelo estimado manual

La validación de residuos es crucial para determinar si el modelo ARIMA es adecuado o si necesita mejoras. El objetivo es verificar que los residuos (errores de predicción) se comporten como ruido blanco, es decir, sin patrones detectables.

Serie de residuos (gráfico superior)

Muestra cómo se comportan los errores a lo largo del tiempo. Idealmente, deberían oscilar alrededor de cero sin tendencias evidentes ni grandes acumulaciones de error.

Problema posible: Se observan picos alrededor de 2020, lo que podría indicar cambios estructurales en los datos (como efectos de la pandemia en el precio de la acción de Bancolombia).

Función de Autocorrelación

Si el modelo es adecuado, los residuos no deben mostrar correlaciones significativas en el tiempo.

Interpretación: La mayoría de las barras están dentro de las líneas azules (intervalos de confianza). Sin embargo, algunos rezagos (3) salen del rango, lo que sugiere que aún puede haber estructura no capturada en los datos. Esto indica que el modelo podría mejorarse, pero no es un modelo muy aceptable.

Histograma de residuos con ajuste normal (gráfico inferior derecho)

Sirve para verificar si los errores siguen una distribución normal, lo cual es un supuesto clave en ARIMA.

Interpretación: La curva naranja representa la distribución normal teórica. Los residuos se acercan a la normalidad, pero hay algunos valores extremos. Esto indica que puede haber eventos atípicos o datos no bien explicados por el modelo (en este caso pandemia).

✅ El modelo ARIMA(1,2,1)parece razonablemente bueno, pero tiene algunas señales de que podría mejorarse.

checkresiduals(manual_arima_model)

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(1,2,1)
## Q* = 20.5, df = 22, p-value = 0.5518
## 
## Model df: 2.   Total lags used: 24

Paso 4. Pronóstico (modelo manual)

Pronóstico en el test de prueba (oct, nov y dic 2024) y gráfico

#Aquí se crea el pronóstico con el modelo ARIMA manual y se guarda en una nueva variable u objeto "manual_forecast"
manual_forecast <- forecast(manual_arima_model, h = length(test_ts)) #Se generan tantos pronósticos como valores tenga el conjunto de prueba (test_ts).

# Crear dataframe para gráfico interactivo del pronóstico manual
manual_forecast_data <- data.frame(Tiempo = time(manual_forecast$mean), ## Extrae las fechas del pronóstico
                                   Pronostico = as.numeric(manual_forecast$mean), ## Valores pronosticados
                                   Observado = as.numeric(test_ts)) ## Valores reales de la serie

# Graficar pronóstico manual junto con los valores observados reales
p_manual <- ggplot(manual_forecast_data, aes(x = Tiempo)) +
  geom_line(aes(y = Pronostico, color = "Pronóstico Manual")) +
  geom_line(aes(y = Observado, color = "Observado")) +
  ggtitle("Variable1:Pronóstico Manual vs Observado") +
  xlab("Tiempo") + ylab("Variable1")

ggplotly(p_manual)

En esta gráfica se evidencia una divergencia creciente entre el pronóstico manual y los valores observados. Mientras que el modelo mantiene una trayectoria ligeramente ascendente y estable, los datos observados presentan un incremento acelerado a partir de finales de 2024, lo que indica que el modelo subestima considerablemente la realidad a partir de ese punto. Esta diferencia sugiere que el modelo no está capturando adecuadamente algún cambio estructural o tendencia acelerada en la serie, posiblemente influenciado por factores externos.

Métricas de evaluación del modelo manual dentro del periodo de prueba (nov, dic 2024)

# Calcular métricas de evaluación del modelo manual
mae_manual <- mean(abs(manual_forecast$mean - test_ts), na.rm = TRUE)
rmse_manual <- sqrt(mean((manual_forecast$mean - test_ts)^2, na.rm = TRUE))

# Mostrar métricas de evaluación del modelo manual
cat("MAE Manual: ", mae_manual, "\n")
## MAE Manual:  4199.941
cat("RMSE Manual: ", rmse_manual, "\n")
## RMSE Manual:  5612.529

A continuación se calcula la Tabla de pronóstico modelo manual VS los datos reales u observado en oct,nov y dic2024

# Cargar librerías necesarias
library(forecast)
library(dplyr)

# Generar pronóstico con el modelo ARIMA identificado
arima_forecast_manual <- forecast(manual_arima_model, h = length(test_ts))

# Crear un dataframe con los valores observados y pronosticados
forecast_table_manual <- data.frame(
  Tiempo = time(arima_forecast_manual$mean),  # Extraer las fechas del pronóstico
  Observado = as.numeric(test_ts),  # Valores reales
  Pronosticado = as.numeric(arima_forecast_manual$mean)  # Valores pronosticados
)

# Mostrar la tabla
print(forecast_table_manual)
##     Tiempo Observado Pronosticado
## 1 2024.750     37800     36554.26
## 2 2024.833     38580     36606.30
## 3 2024.917     37600     36691.82
## 4 2025.000     43100     36773.06
## 5 2025.083     47400     36854.85

Ahora pronosticamos fuera del periodo de análisis: Enero 2025

# Cargar librerías necesarias
library(forecast)

# Hacer un pronóstico para el siguiente mes (1 período adicional)
next_forecast_manual <- forecast(manual_arima_model, h = length(test_ts) + 1)

# Extraer el pronóstico del próximo mes
next_month_forecast_manual <- data.frame(
  Tiempo = time(next_forecast_manual$mean),  # Extraer la fecha del pronóstico
  Pronostico = as.numeric(next_forecast_manual$mean)  # Valor pronosticado
)

# Mostrar el pronóstico completo
print(next_month_forecast_manual)
##     Tiempo Pronostico
## 1 2024.750   36554.26
## 2 2024.833   36606.30
## 3 2024.917   36691.82
## 4 2025.000   36773.06
## 5 2025.083   36854.85
## 6 2025.167   36936.57
# Extraer solo el valor del trimestre adicional (último de la tabla)
next_month <- tail(next_month_forecast_manual, 1)
print(paste("Pronóstico para 2025:", next_month$Tiempo, "=", next_month$Pronostico))
## [1] "Pronóstico para 2025: 2025.16666666667 = 36936.5735674693"

Modelo ARIMA automático

Usa la función auto.arima() de forecast en R para seleccionar automáticamente los mejores parámetros (p,d,q).

✅ Ventajas:

✔ Optimización automática: Encuentra los valores óptimos de ARIMA sin intervención manual. ✔ Ahorra tiempo: Útil cuando hay muchas series a modelar. ✔ Evita sesgo humano: Reduce el riesgo de elegir un modelo incorrecto por falta de experiencia. ✔ Incluye corrección por estacionalidad si se usa con seasonal = TRUE. ✔ Suele funcionar bien en la mayoría de los casos, ya que usa criterios como AIC/BIC para optimizar.

❌ Desventajas: ❌ Puede no ser el mejor modelo posible, ya que depende del criterio de selección. ❌ Menos interpretabilidad: No siempre es claro por qué eligió ciertos parámetros. ❌ Puede ignorar conocimiento experto sobre la serie o factores externos.

Identificación automática del modelo ARIMA

library(forecast)

# Ajustar un modelo ARIMA automático sin estacionalidad, por eso se pone seasonal=FALSE
auto_arima_model_no_seasonal <- auto.arima(train_ts, seasonal = FALSE)

# Mostrar el modelo seleccionado
summary(auto_arima_model_no_seasonal)
## Series: train_ts 
## ARIMA(0,1,0) 
## 
## sigma^2 = 6260946:  log likelihood = -1626.92
## AIC=3255.84   AICc=3255.86   BIC=3259.01
## 
## Training set error measures:
##                    ME    RMSE      MAE         MPE     MAPE      MASE      ACF1
## Training set 80.23678 2495.11 1638.758 -0.03294984 5.388945 0.3507169 -0.132936

Significancia de coeficientes

# Ajuste del modelo ARIMA(0,1,0) automático sin parte estacional y crearlo como variable darima_auto para luego poder graficarlo y crear la tabla
darima_auto <- Arima(train_ts, 
                order = c(0, 1, 0))  # Especificamos directamente (p=0, d=1, q=0)  

# Mostrar resumen del modelo ajustado
summary(darima_auto)
## Series: train_ts 
## ARIMA(0,1,0) 
## 
## sigma^2 = 6260946:  log likelihood = -1626.92
## AIC=3255.84   AICc=3255.86   BIC=3259.01
## 
## Training set error measures:
##                    ME    RMSE      MAE         MPE     MAPE      MASE      ACF1
## Training set 80.23678 2495.11 1638.758 -0.03294984 5.388945 0.3507169 -0.132936
# Diagnóstico del modelo (los residuos deben ser ruido blanco)
checkresiduals(darima_auto)  # Verificar si los residuos son aleatorios y no presentan patrones

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(0,1,0)
## Q* = 24.224, df = 24, p-value = 0.4488
## 
## Model df: 0.   Total lags used: 24

Serie de residuos: La serie de residuos oscila alrededor de cero, lo cual es una buena señal. Sin embargo, hay algunos datos atípicos marcados, especialmente uno muy fuerte hacia 2020, lo que significa el choque de la pandemia. Los residuos parecen no tener una estructura clara, lo que indica que el modelo está capturando bien la tendencia general de la serie.

Función de Autocorrelación: La mayoría de los rezagos están dentro de las bandas de confianza (líneas azules), sin embargo, hay algunos datos que se pasan.

Histograma de residuos con ajuste normal: El histograma tiene una forma aproximadamente simétrica, aunque ligeramente sesgada a la izquierda.

Pronóstico modelo ARIMA automático (0,1,0)

# Generar pronóstico para el conjunto de prueba
forecast_arima_auto <- forecast(darima_auto, h = length(test_ts))  # Predecir los valores futuros

# Crear dataframe para gráfico interactivo del pronóstico
forecast_data_auto <- data.frame(Tiempo = time(forecast_arima_auto$mean), 
                            Pronostico = as.numeric(forecast_arima_auto$mean),
                            Observado = as.numeric(test_ts))

# Graficar pronóstico junto con los valores observados reales
p4auto <- ggplot(forecast_data_auto, aes(x = Tiempo)) +
  geom_line(aes(y = Pronostico, color = "Pronóstico")) +
  geom_line(aes(y = Observado, color = "Observado")) +
  ggtitle("Pronóstico vs Observado") +
  xlab("Tiempo") + ylab("variable1")

ggplotly(p4auto)  # Convertir el gráfico en interactivo

En esta gráfica, el pronóstico automático se mantiene constante, mientras que los valores observados muestran un aumento marcado. Esto indica que el modelo no capta la tendencia creciente de la serie y, por tanto, subestima significativamente los valores reales.

Conclusión:

El modelo ARIMA manual (1,2,1), aunque subestima los valores observados, logra seguir una tendencia ligeramente ascendente, lo que sugiere cierto grado de ajuste al comportamiento real de la serie. Esta capacidad de adaptación se ve respaldada por sus menores valores en los criterios AIC (3244.61) y BIC (3253.96), lo que indica una leve eficiencia estadística en comparación con el modelo automático. Con base en este modelo, se estima que el precio de la acción de Bancolombia para enero de 2025 será de $36.936.

Por otro lado, el modelo automático (0,1,0) genera un pronóstico completamente plano, lo que lo hace insensible a los cambios en la serie observada. Esto evidencia una falta total de ajuste a la dinámica real de los datos.

Cabe señalar que se intentaron otras combinaciones de parámetros, pero los resultados no fueron óptimos. Por esta razón, se recomienda considerar otro tipo de modelo que logre capturar mejor la naturaleza del comportamiento de los datos y permita una mayor precisión, especialmente teniendo en cuenta la alta volatilidad del precio de las acciones.