Introducción

En este informe se realizará el análisis y la extracción de señales aplicadas al precio de la acción del Banco de Occidente, utilizando la metodología ARIMA (AutoRegressive Integrated Moving Average), durante el período comprendido entre enero de 2010 y febrero de 2025.

El análisis de series temporales mediante modelos ARIMA permite captar patrones históricos del comportamiento del activo, como la tendencia, la estacionalidad y las fluctuaciones aleatorias. Este enfoque es fundamental para comprender la dinámica de los precios, identificar estructuras recurrentes y generar proyecciones que faciliten la toma de decisiones en entornos financieros. Los resultados obtenidos ofrecerán una visión detallada de la evolución histórica del precio de la acción, así como estimaciones futuras que pueden servir como referencia para inversionistas y analistas financieros.

En este estudio, se aplicarán técnicas estadísticas avanzadas para ajustar un modelo ARIMA adecuado, evaluando su precisión y capacidad predictiva. A lo largo del informe, se describirá el proceso de selección del modelo, los parámetros identificados y la interpretación de las proyecciones, destacando sus implicaciones en el contexto del mercado bursátil y la gestión estratégica del Banco de Occidente.

Extracción de Señales

#Cargar librerías necesarias
library(readxl)  # Para leer archivos Excel
library(tseries)  # Para pruebas de estacionariedad
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(forecast)  # Para modelado ARIMA y pronósticos
library(ggplot2)  # Para visualización de datos
library(plotly)  # Para gráficos interactivos
## 
## Adjuntando el paquete: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
library(timetk)   
data_col <- read_excel("PACCIONES COL.xlsx", col_types = c("date", 
    "numeric"))
# Convertir/declarar variable 1=ENER en serie de tiempo mensual
variable1_ts <- ts(data_col$OCC, start = c(2010, 1), frequency = 12)
variable1_ts
##        Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec
## 2010 28500 29000 30000 30600 30600 30600 31500 34060 36000 36000 36000 37660
## 2011 37100 34040 34980 34300 33500 33500 33060 32000 30000 30000 29500 30000
## 2012 28500 30300 30300 30500 32000 30020 29520 30940 31000 31500 31500 32000
## 2013 32520 33960 33700 33940 39600 40400 40000 40040 40200 40000 39980 40100
## 2014 40100 39800 39500 39000 40000 40000 40000 40500 40800 41120 42480 42000
## 2015 40020 41000 39000 37760 36500 35380 35300 32000 36500 38000 39000 38200
## 2016 37000 36100 36000 36000 36000 36500 36500 36500 36500 36420 36520 37000
## 2017 36800 37800 38500 38000 38000 37000 38500 38500 38500 38520 38520 39000
## 2018 38880 39000 39000 38500 39300 39500 40000 40020 39500 39000 39000 38000
## 2019 38100 38300 38500 38620 37500 36000 37380 37000 38000 38000 38000 38000
## 2020 38000 40000 36500 36500 36500 34720 34500 34500 34000 34000 34000 34000
## 2021 33000 33500 33500 34000 33900 33900 33900 33900 29000 28000 27500 27500
## 2022 28200 28500 29200 24000 24000 24000 24000 24000 24000 24000 24000 24000
## 2023 19000 15000 15000 15000 14000 14000 14000 12000 12000 12000 12000 12000
## 2024 13960 12380 12380 13980 11840 13660 13200 13200 13200 13200 18000 18000
## 2025 17000 17160

Análisis exploratotio-descriptivo

# 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 11840 42480 32058.46   34610           8493.875 0.2649495

Conclusiones

El precio de la acción del Banco de Occidente muestra una variabilidad moderada, con un coeficiente de variación del 26.49%, lo que implica que los valores no experimentan cambios extremos, aunque presentan cierta fluctuación a lo largo del tiempo.

Existe una diferencia significativa entre el precio mínimo (11,840 COP) y el máximo (42,480 COP), lo cual sugiere que la acción ha atravesado períodos de baja valoración, así como momentos de mayor confianza y rendimiento positivo. Estas variaciones pueden estar influenciadas por factores como los resultados financieros, cambios regulatorios o la percepción de los inversionistas.

La proximidad entre la media (32,058.46 COP) y la mediana (34,610 COP) indica que la distribución de los precios no está sesgada de manera significativa, lo que sugiere que la mayoría de los valores observados se concentran cerca del promedio.

La variabilidad de los precios de la acción es de 8,493.88 COP, lo que indica fluctuaciones considerables en los valores observados durante el período. Este nivel de dispersión refleja la sensibilidad del precio a factores internos y externos que influyen en el mercado financiero. Precio de la acción mensual de Banco de Occidente

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("Variable 1: Serie original") +
  xlab("Tiempo") +
  ylab("Unidad Variable 1") +
  theme_minimal()

ggplotly(grafico_serie)

La figura 1, muestra las variaciones históricas en el precio de la acción del Banco de Occidente desde 2020 hasta febrero del 2025. Se puede observar una tendencia alcista hasta el 2014, año en el que alcanza su máximo de 42.480 pesos. Sin embargo desde el 2020 la acción se desvaloriza siguiendo una tendencia decrecientes, posiblemente por el factor de la pandemia y los cambios en las perspectivas economicas. En los años más recientes, el precio muestra signos de estabilización con una leve recuperación hacia los 20.000, lo que sugiere una posible mejora en las perspectivas del mercado.

# 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 = "Descomposición temporal de la variable 1",
       x = "Tiempo",
       y = "Valor")

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

De la gráfica anterior se puede inferir que existe un componente de estacionalidad, pero al ser un activo financiero, especificamente una acción, este no tiene un gran impacto en el comportamiento de la variable. Además, las oscilaciones del residuo que se extienden más allá del cero, alcanzando valores superiores a -2000 y 2000, indican una alta dispersión respecto al ajuste del modelo, es decir,evidencia la existencia de factores estructurales, no estacionales, como noticias, anuncios de politica monetaria que afectan al sector.

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

Gráfico serie original VS ajustada Variable

# 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("Variable 1:Serie Original vs Serie Ajustada por Estacionalidad") +
  xlab("Tiempo") +
  ylab("Unidad de medida variable 1") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Rotar etiquetas para mejor visualización
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning in geom_line(aes(x = fechas_var1, y = variable1_ts), color = "grey", :
## Ignoring unknown parameters: `name`
## Warning in geom_line(aes(x = fechas_var1, y = variable1_sa), color = "black", :
## Ignoring unknown parameters: `name`
# Convertir a gráfico interactivo
ggplotly(grafico_ajustada_var1)
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.

La serie ajustada por estacionalidad (línea negra) sigue muy de cerca a la serie original (línea gris), lo que indica que la estacionalidad no tiene un impacto significativo en la variación del precio de la acción del Banco de Occidente, se puede concluir que los cambios en el precio de la acción están más relacionados con factores estructurales y eventos macroeconómicos que con patrones estacionales recurrentes.

Este comportamiento tiene sentido, ya que las acciones son instrumentos de renta variable y su precio fluctúa según la oferta y demanda, las expectativas del mercado, el desempeño financiero de la empresa y factores macroeconómicos como la política monetaria, la inflación y los ciclos económicos.

Ahora graficamos serie original vs tendencia

-La extracción de la tendencia permite centrarse en los cambios estructurales de la serie. -Analizar la tendencia ayuda a prever escenarios futuros y anticipar posibles crisis o oportunidades en el sector o variable de análisis

Primero se debe obtener la tendencia de cada variable y luego graficarla Tendencia Variable 1

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("Variable 1: Serie Original vs Tendencia") +
  xlab("Tiempo") +
  ylab("Unidad de medida Variable 1") +
  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)

Entre 2010 y 2014, el precio de la acción del Banco de Occidente mostró una tendencia alcista, pasando de aproximadamente 30.000 a 42,480, lo que sugiere un período de expansión y confianza en el desempeño financiero del banco. Sin embargo, a partir de 2015, la acción comenzó a experimentar volatilidad y estancamiento, fluctuando alrededor de 40.000, lo que podría reflejar un equilibrio entre las expectativas del mercado y las condiciones económicas.

Esta estabilidad se mantuvo hasta 2019, pero desde 2020 se observa una caída significativa en el precio, alcanzando un minimo de 12,000 pesos en el 2023, una disminución del 71.76% encompración al 2014.. Este descenso podría estar relacionado con los efectos de la pandemia de COVID-19, que generó incertidumbre en los mercados financieros y afectó la percepción de riesgo en el sector bancario. En el tramo más reciente, correspondiente a 2024 y 2025, se percibe un ligero repunte en la tendencia (punto de quiebre), lo que sugiere una posible recuperación en el valor de la acción. Aunque aún es prematuro determinar si esta recuperación será sostenida, el cambio de dirección en la tendencia es un indicio positivo para el desempeño futuro del banco.

#Cálculo de la tasa de crecimiento anual correctamente alineada
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 corregido, es decir que inicie desde enero 2013
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

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

Tasa de crecimiento de la serie de tendencia y original para la variable 1

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("Variable1: 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)

Entre 2014 y 2016, se evidencia un ciclo expansivo con un crecimiento positivo que alcanza cerca del 30%, seguido de una fase contractiva en la que las tasas caen por debajo de 0%. Posteriormente, entre 2017 y 2021, el comportamiento presenta fluctuaciones alrededor de tasas cercanas a 0%, lo que refleja una etapa de relativa estabilidad o estancamiento.

A partir de 2022, se observa un ciclo contractivo más pronunciado, con tasas de crecimiento negativas que descienden por debajo de -40%, lo que indica una fuerte caída. Sin embargo, desde 2024, se registra una recuperación acelerada, marcando el inicio de un nuevo ciclo expansivo. Este movimiento es especialmente evidente al considerar que la tasa de crecimiento anual pasa de valores negativos cercanos a -50% a finales de 2023 a valores positivos de manera acelerada (50% actualemnte). La tendencia sigue este mismo comportamiento alcanzando un 2.22% en julio de 2024 y llegando a un máximo de 33.36% en febrero de 2025. Este fuerte incremento sugiere una recuperación significativa en el precio de la acción, lo que refuerza la idea de que la tendencia ha cambiado y que el valor de la acción podría estar entrando en una fase de estabilidad o crecimiento sostenido.

El presidente Gerardo Silva Castro de la entidad ha señalado que este movimiento se debe a la gran liquidez que tiene la acción (La República, 2024). Esta declaración respalda la idea de que el reciente cambio en la tendencia no es un fenómeno aislado, sino que está relacionado con factores fundamentales que impulsan el comportamiento del activo.

Actualmente, la tendencia indica una fase expansiva, lo que sugiere que podría ser un buen momento para tomar una posición larga (comprar) con la expectativa de que las tasas de crecimiento sigan aumentando en el corto plazo. Si la recuperación se mantiene, los inversionistas actuales podrían beneficiarse de una apreciación del capital e incluso de un posible aumento en los dividendos si la empresa mejora su desempeño financiero. Sin embargo, es recomendable monitorear de cerca la evolución de la tasa de crecimiento anual para identificar posibles señales de agotamiento en la expansión. Existen ciclos económicos que afectan las variables.

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 1. Autoregressive o autorregresivo (AR) → Usa valores pasados para predecir el futuro 2. Integrated (I) → Ajusta tendencias en los datos para hacerlos estacionarios (sin patrones cambiantes en el tiempo). 3.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 metodología BOX-JENKINS, lo ideal es 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 1 que es la elegida para pronosticar

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

Conjunto de entrenamiento (train): Datos desde enero de 2010 hasta noviembre de 2024. Conjunto de prueba (test): Datos desde diciembre de 2024 hasta febrero de 2025.

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

# Esta división idealmente podria se 80%-70% de los datos para entrenamiento y 20%-30% para prueba o test

# En este ejemplo el conjunto de entrenamiento es: Enero 2010-Diciembre 2024 y  el conjunto de prueba o test: diciembre 2024-febrero 

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, 11))  # Entrenamiento hasta noviembre 2024
test_ts <- window(variable1_ts, start = c(2024, 12))  # Prueba inicia desde dic2024

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 = -1.6452, Lag order = 5, p-value = 0.7245
## alternative hypothesis: stationary

El test ADF en la variable 1 arrojó un p-value igual a 0.7245, este valor es mayor a 0.05, por tanto la serie es no estacionaria. De ese modo se debe ejecutar el código siguiente para diferenciar una vez la variable 1 y luego volver a aplicar el test ADF a esa serie diferenciada una vez:

#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) 

Diferenciación en niveles variable 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:

library(ggplot2)
library(plotly)
 # 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
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.
library(ggplot2)
library(plotly)
 # 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("variable 1:Serie Estacionaria (Una diferenciación)") +
    xlab("Tiempo") + ylab("variable 1 diferenciada")
  
  ggplotly(p3)  # Convertir en gráfico interactivo

Segunda Diferenciación

#Se crea un nuevo objeto o variable que se llama train_diff2, en donde se diferencia la variable 1 , por segunda vez:
train_diff2 <- diff(train_ts, differences = 2) 
library(ggplot2)
library(plotly)

# Graficar la serie diferenciada dos veces
p3 <- ggplot(data.frame(Tiempo = time(train_ts)[-c(1,2)], 
                        variable1_Diff2 = as.numeric(train_diff2)), 
             aes(x = Tiempo, y = variable1_Diff2)) +
  geom_line(color = "red") +
  ggtitle("variable 1: Serie Estacionaria (Dos diferenciaciones)") +
  xlab("Tiempo") + 
  ylab("variable 1 diferenciada (segunda diferencia)")

ggplotly(p3)  # Convertir en gráfico interactivo

Se usa la segunda diferencia porque en la metodología de Box y Jenkins para ARIMA, se recomienda que p y q sean pequeños (usualmente≤6), ya que valores más altos pueden indicar sobreajuste o modelos poco interpretables. Si bien, se cumple con que sea estacionaria, al usar la diferenciada una vez, el principio mencionado no,ya que p y q tomaban valores mayores a 7.Además los coeficientes no eran significativos.

Ejemplo Diferenciación en logaritmo Cuando una serie de tiempo tiene una creciente varianza (lo que significa que la amplitud de las fluctuaciones aumenta con el tiempo), aplicar un logaritmo puede ayudar a estabilizar esta varianza. Muchas series económicas o financieras, como el precio de acciones o el Producto Interno Bruto (PIB), tienden a mostrar crecimiento exponencial o crecimiento en porcentaje (por ejemplo, tasas de crecimiento de doble dígito).

En conclusión, la aplicación de logaritmos en series de tiempo se realiza principalmente para lograr que la serie sea más estable, lineal y estacionaria. Esta transformación es relevante porque permite modelar mejor las series que siguen un crecimiento exponencial y facilita la aplicación de técnicas estadísticas que requieren estacionariedad.

A continuación se aplica la diferenciación logarítimica y la varible u objeto ahora se llama train_diff_log:

# Si la serie no es estacionaria (p-valor > 0.05), aplicar diferenciación

train_diff_log <- diff(log(train_ts), differences = 1)

Ahora graficamos la serie orignal versus la serie diferenciada una vez con logaritmo

library(ggplot2)
library(plotly)
# 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("Variable1:Serie Original") +
    xlab("Tiempo") + ylab("Variable1")
  
  ggplotly(p2)  # Convertir en gráfico interactivo
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.
 # Graficar la serie diferenciada en un gráfico separado
  
  p3 <- ggplot(data.frame(Tiempo = time(train_ts)[-1], micro_Diff = as.numeric(train_diff_log)), aes(x = Tiempo, y = micro_Diff)) +
    geom_line(color = "red") +
    ggtitle("Variable1:Serie Estacionaria (Una diferenciación en logaritmo)") +
    xlab("Tiempo") + ylab("Variable 1 diferenciada")
  
  ggplotly(p3)  # Convertir en gráfico interactivo

Ahora probamos estacionariedad en la serie diferenciada ( en nivel y logaritmo)

# Segunda prueba de estacionariedad sobre la serie diferenciada en niveles
  adf_test_diff <- adf.test(train_diff)
## Warning in adf.test(train_diff): p-value smaller than printed p-value
  print(adf_test_diff)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_diff
## Dickey-Fuller = -5.335, Lag order = 5, p-value = 0.01
## alternative hypothesis: stationary
library(tseries)

# Segunda prueba de estacionariedad sobre la serie diferenciada dos veces
adf_test_diff2 <- adf.test(train_diff2)
## Warning in adf.test(train_diff2): p-value smaller than printed p-value
print(adf_test_diff2)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_diff2
## Dickey-Fuller = -10.154, Lag order = 5, p-value = 0.01
## alternative hypothesis: stationary
# Segunda prueba de estacionariedad sobre la serie diferenciada en logaritmo
  adf_test_diff_log <- adf.test(train_diff_log)
## Warning in adf.test(train_diff_log): p-value smaller than printed p-value
  print(adf_test_diff_log)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_diff_log
## Dickey-Fuller = -4.4904, Lag order = 5, p-value = 0.01
## alternative hypothesis: stationary

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

El p-value es menor a 0.05 en los tres casos:niveles o con logaritmo natural. Por tanto el valor que puede tomar d es igual a 1. Para un modelo más simple en términos del componente autoregresivo y de media móvil se usará d=2.

Identificación manual de p y q

¿Qué hacen estos gráficos?

ACF (Autocorrelation Function)

Muestra la correlación de la serie con sus rezagos.

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 puedeo tomar el parámetro p y q:

library(forecast)
# Graficar ACF y PACF
acf_plot <- ggAcf(train_diff2, lag.max = 8) + ggtitle("Autocorrelation Function (ACF)-Determinar q")
pacf_plot <- ggPacf(train_diff2, lag.max = 8) + 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,p=2,p=4,p=6 y q=1,q=6,q=7 (P optimo=1) (q ó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)= 800.4147

Representa el error absoluto promedio entre las predicciones del modelo y los valores reales.Interpretación: En promedio, el modelo se equivoca en 807.33 pesos al predecir el precio de la acción del Banco de Occidente.

Root Mean Squared Error (RMSE)= 1325.008

Similar al MAE, pero da más peso a los errores grandes, porque eleva las diferencias al cuadrado antes de promediarlas.Interpretación: Un error típico en la predicción es de aproximadamente 1.325 pesos.

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) = 3%

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

Interpretación: En promedio, el modelo se equivoca en un 3% al predecir el precio de la acción.

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 3% sugiere un muy buen modelo para pronóstico.

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 (7,1,7)
summary(manual_arima_model)
## Series: train_ts 
## ARIMA(1,2,1) 
## 
## Coefficients:
##          ar1      ma1
##       0.0063  -0.9761
## s.e.  0.0807   0.0224
## 
## sigma^2 = 1795775:  log likelihood = -1526.15
## AIC=3058.3   AICc=3058.44   BIC=3067.83
## 
## Training set error measures:
##                     ME     RMSE      MAE        MPE     MAPE      MASE
## Training set -110.0363 1325.008 800.4147 -0.3182911 3.000829 0.2076512
##                     ACF1
## Training set -0.01297009

Significancia de coefientes

library(lmtest)
## Cargando paquete requerido: zoo
## 
## Adjuntando el paquete: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
# 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.0062978  0.0806591   0.0781   0.9378    
## ma1 -0.9760860  0.0224052 -43.5652   <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. Regla general:Como al menos uno de los dos componentes es 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.

1. 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: Aunque los residuos parecen centrados en cero, se observan varios picos y caídas abruptas, especialmente después de 2020. Esto sugiere que aún hay cierta estructura en los datos que el modelo no captura completamente. También pueden reflejar eventos atípicos que influyeron en la serie original como la pandemia, aceleramiento de la inflación, etc. Función de Autocorrelación (gráfico inferior izquierdo, ACF de residuos)

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 parecen salir 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 es un modelo 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 roja representa la distribución normal teórica. Se observa que los residuos están aproximadamente centrados en cero, lo que es una buena señal, sin embargo, la distribución tiene colas más gruesas de lo esperado (hay valores extremos en ambos extremos), lo que indica la presencia de eventos atípicos. También se aprecia una ligera asimetría, lo que sugiere que los errores no siguen completamente una distribución normal.

Conclusión y acciones recomendadas

✅El Modelo ARIMA (1,2,1) parece razonablemente bueno, pero tiene algunas señales de que podría mejorarse. ⚠️ Posibles mejoras:

Revisar la estructura del modelo: Se pueden probar otros órdenes ARIMA o incluso modelos más complejos como SARIMA o modelos con variables exógenas (ARIMAX).

Manejo de valores atípicos: Considerar incluir un término de intervención si eventos como la pandemia afectaron la serie.

Transformación de datos: Si los residuos no son normales, una transformación logarítmica o Box-Cox puede ayudar.

Recordar: El supuesto de normalidad significa que los errores o residuos de un modelo deben seguir una distribución normal (o “campana de Gauss”). Si los errores son normales, podemos hacer predicciones más confiables y usar ciertas pruebas estadísticas que asumen esta propiedad.

 checkresiduals(manual_arima_model)

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

Paso 4. Pronóstico (modelo manual)

El modelo sigue la tendencia general, pero tiene un sesgo de sobreestimación.

Si la serie tiene patrones estacionales fuertes y no están bien capturados, el modelo puede fallar en prever las fluctuaciones. En ese caso se puede mejorar el modelo, al trabajar desde el inicio con la serie ajustada por estacionalidad en caso de que se detecte un componente estacional fuerte.

Pronóstico en el test de prueba (dic 2024, enero y febrero 2025) y gráfico

library(ggplot2)
library(plotly)
library(forecast)
#Aquí se crea el pronóstico con el modelo ARIMA manual y se guarda en una nueva riable 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)
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.

Por medio del gráfico se puede determinar que el modelo univariante no logra recoger las característica propias de la variable, es decir,no es el modelo más apropiado. Métricas de evaluación del modelo manual dentro del periodo de prueba (dic 2024,enero y feb2025)

# 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:  410.8673
cat("RMSE Manual: ", rmse_manual, "\n")
## RMSE Manual:  475.0918

MAE (Mean Absolute Error)

Indica el error promedio en unidades de la variable, es decir, en pesos.

RMSE (Root Mean Squared Error)

Penaliza más los errores grandes debido a la elevación al cuadrado antes de calcular la raíz.

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

# Cargar librerías necesarias
library(forecast)
library(dplyr)
## 
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
# 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.917     18000     17871.48
## 2 2025.000     17000     17711.92
## 3 2025.083     17160     17552.16

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

Es decir, le sumamos al periodo de prueba, una observación más (marzo 2025). Es decir, se estan pronosticando 4 observaciones o meses:

# 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.917   17871.48
## 2 2025.000   17711.92
## 3 2025.083   17552.16
## 4 2025.167   17392.41

Como se pudo observar en el gráfico del pronóstico dentro de la muestra, el modelo no logra capturar la información completa de la variable, por esta razón, es que los valores pronósticados no tienen un comportamiento similar al de la tendencia.

# Extraer solo el valor del trimestre adicional (último de la tabla)
next_month <- tail(next_month_forecast_manual, 1)
print(paste("Pronóstico para marzo 2025:", next_month$Tiempo, "=", next_month$Pronostico))
## [1] "Pronóstico para marzo 2025: 2025.16666666667 = 17392.4052108667"

Otra forma para calcular un valor futuro (fuera de muestra)-Modelo manual, es decir, en caso de que no se haga la dviisón inicial de conjunto de entrenamiento y prueba

Si no hay conjunto de prueba o test y solo quieres el siguiente punto u observación, el código a usar seria algo asi:

# Pronosticar octubre de 2024
future_forecast_manual <- forecast(manual_arima_model, h = 1)

# Extraer el valor específico de octubre 2024
forecast_dic2024 <- future_forecast_manual$mean[1]
print(paste("Pronóstico para dic 2024:", forecast_dic2024))
## [1] "Pronóstico para dic 2024: 17871.4785601231"

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
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 = 1759121:  log likelihood = -1532.42
## AIC=3066.84   AICc=3066.86   BIC=3070.02
## 
## Training set error measures:
##                 ME     RMSE      MAE        MPE     MAPE      MASE       ACF1
## Training set -58.5 1322.609 743.9581 -0.4085424 2.734773 0.1930047 0.02025457

El modelo automático identificado es (0,1,0). Si se compara el AIC o BIC de este modelo frente el modelo manual (1,2,1) se obtiene un valor más alto en esta métricas de este modelo automático, aunque no por una gran diferencia. Lo que nos deja ver que probablemnete el modelo manual pudiera ser un buen modelo para pronosticar la variable 1=precio de la acción del Banco de Occidente. Significancia de coeficientes

library(lmtest)

# Evaluar la significancia estadística de los coeficientes del modelo ARIMA
coefs <- auto_arima_model_no_seasonal$coef
se <- sqrt(diag(auto_arima_model_no_seasonal$var.coef))
t_values <- coefs / se
p_values <- 2 * (1 - pnorm(abs(t_values)))

data.frame(Coefficients = coefs, Std_Error = se, T_Value = t_values, P_Value = p_values)
## [1] Coefficients
## <0 rows> (o 0- extensión row.names)
# 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 = 1759121:  log likelihood = -1532.42
## AIC=3066.84   AICc=3066.86   BIC=3070.02
## 
## Training set error measures:
##                 ME     RMSE      MAE        MPE     MAPE      MASE       ACF1
## Training set -58.5 1322.609 743.9581 -0.4085424 2.734773 0.1930047 0.02025457
# 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* = 25.033, df = 24, p-value = 0.404
## 
## Model df: 0.   Total lags used: 24

En general, ninguno de los dos modelos produce residuos que sean un ruido blanco perfecto,pero ARIMA(0,1,0) se ajusta mejor en términos de independencia de residuos, mientras que ARIMA(1,2,1) tiene una mejor distribución normal.

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
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.

El modelo automático (0,1,0) no es adecuado para pronósticos fuera de muestra o a futuro, ya que muestra un comportamiento lineal y no captura bien los puntos de quiebre. Dentro de la prueba, su capacidad de pronóstico es limitada, lo que indica que no modela adecuadamente la estructura de la serie.

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

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

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

# Mostrar la tabla
print(forecast_table_auto)
##     Tiempo Observado Pronosticado
## 1 2024.917     18000        18000
## 2 2025.000     17000        18000
## 3 2025.083     17160        18000

Ahora pronosticamos fuera del periodo de análisis

Es decir, le sumamos al periodo de prueb auna observación más. Es decir, se estan pronosticando 4 observaciones o trimestres.

# Cargar librerías necesarias
library(forecast)

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

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

# Mostrar el pronóstico completo
print(next_month_forecast_auto)
##     Tiempo Pronostico
## 1 2024.917      18000
## 2 2025.000      18000
## 3 2025.083      18000
## 4 2025.167      18000
# Extraer solo el valor del trimestre adicional (último de la tabla)
next_month <- tail(next_month_forecast_auto, 1)
print(paste("Pronóstico para marzo", next_month$Tiempo, "=", next_month$Pronostico))
## [1] "Pronóstico para marzo 2025.16666666667 = 18000"

Otra forma para calcular un valor futuro (fuera de muestra)

# Pronosticar  el mes dic24
future_forecast_auto <- forecast(auto_arima_model_no_seasonal, h = 1)

# Extraer el valor específico de octubre 2024
forecast_oct2024_auto <- future_forecast_auto$mean[1]
print(paste("Pronóstico para dic24:", forecast_oct2024_auto))
## [1] "Pronóstico para dic24: 18000"

Modelo SARIMA automático

Este modelo podria ser una solución o mejora al modelo arima tradicional ya que recoge el efecto estacional de las variables, es recomendable por tanto para datos que si tienen un componente estacional fuerte.

Un SARIMA (Seasonal ARIMA) es un modelo que combina:

✅ Un modelo ARIMA tradicional para capturar relaciones entre valores pasados y errores. ✅ Un componente estacional, útil cuando los datos muestran patrones repetitivos en el tiempo (por ejemplo, ventas trimestrales, datos mensuales de temperatura, etc.).

Básicamente, es como un ARIMA mejorado que puede manejar ciclos estacionales.

En este caso, el modelo detectó un patrón repetitivo cada 4 períodos, por lo que usa componentes autoregresivos y de media móvil para modelarlo

El modelo ajustado es un SARIMA(0,1,1)(1,0,0)[12], lo que significa:

(0,1,1): Parte ARIMA no estacional: 0 términos autorregresivos (AR). 1 diferenciación (d), lo que indica que la serie fue diferenciada una vez para hacerla estacionaria. 1 término de media móvil (MA).

(1,0,0)[12]: Parte estacional con periodicidad 12 (mensual si los datos son mensuales): 1 término autorregresivo estacional (SAR). 0 diferenciaciones estacionales. 0 términos de media móvil estacionales (SMA).

El modelo SARIMA(0,1,1)(1,0,0)[12] sugiere que:

La serie tiene una tendencia no estacionaria, corregida con una diferenciación. Existe una influencia significativa del error pasado (MA(1)). Hay un componente estacional autorregresivo fuerte cada 12 períodos. El ajuste es adecuado según los criterios AIC y BIC, pero se podría comparar con otros modelos para mejorar la predicción.

# Identificación automática del mejor modelo ARIMA
auto_arima_model <- auto.arima(train_ts)  # Busca automáticamente los mejores parámetros del modelo ARIMA
print(auto_arima_model)
## Series: train_ts 
## ARIMA(0,1,0) 
## 
## sigma^2 = 1759121:  log likelihood = -1532.42
## AIC=3066.84   AICc=3066.86   BIC=3070.02

A continuación, se crea el objeto darima para luego poder graficar los valores reales y observados:

# Cargar el paquete necesario
library(forecast)

# Ajustar el modelo SARIMA(0,1,0)
darima <- Arima(train_ts, 
                order = c(0, 1, 0),  # (p,d,q) -> (0,1,1)
                seasonal = list(order = c(0, 1, 0),  # (P,D,Q) -> (1,0,0)
                                period = 12))  # Periodicidad estacional de 12 meses

# Mostrar resumen del modelo ajustado
summary(darima)
## Series: train_ts 
## ARIMA(0,1,0)(0,1,0)[12] 
## 
## sigma^2 = 3480772:  log likelihood = -1485.75
## AIC=2973.5   AICc=2973.53   BIC=2976.62
## 
## Training set error measures:
##                     ME     RMSE      MAE       MPE    MAPE      MASE       ACF1
## Training set -14.64853 1796.658 1124.034 0.1821985 4.17282 0.2916077 0.08391455
# Diagnóstico del modelo (los residuos deben ser ruido blanco)
checkresiduals(darima)  # Verificar si los residuos son aleatorios y no presentan patrones

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(0,1,0)(0,1,0)[12]
## Q* = 75.723, df = 24, p-value = 2.878e-07
## 
## Model df: 0.   Total lags used: 24

Pronóstico con el modelo SARIMA

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

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

# Graficar pronóstico junto con los valores observados reales
p4 <- ggplot(forecast_data, 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("Unidad Variable 1")

ggplotly(p4)  # Convertir el gráfico en interactivo
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.
# Cargar librerías necesarias
library(forecast)
library(dplyr)

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

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

# Mostrar la tabla
print(forecast_table)
##     Tiempo Observado Pronosticado
## 1 2024.917     18000        18000
## 2 2025.000     17000        19960
## 3 2025.083     17160        18380

Ahora pronosticamos fuera del periodo de análisis

Es decir, le sumamos al periodo de prueba una observación más. Es decir, se estan pronosticando 4 observaciones o meses.

# Cargar librerías necesarias
library(forecast)

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

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

# Mostrar el pronóstico completo
print(next_month_forecast)
##     Tiempo Pronostico
## 1 2024.917      18000
## 2 2025.000      18000
## 3 2025.083      18000
## 4 2025.167      18000
# Extraer solo el valor del trimestre adicional (último de la tabla)
next_month <- tail(next_month_forecast, 1)
print(paste("Pronóstico para marzo 2025:", next_month$Tiempo, "=", next_month$Pronostico))
## [1] "Pronóstico para marzo 2025: 2025.16666666667 = 18000"

Otra forma para calcular un valor futuro (fuera de muestra)

# Identificación automática del mejor modelo ARIMA
auto_arima_model <- auto.arima(train_ts)  # Busca automáticamente los mejores parámetros del modelo ARIMA
print(auto_arima_model)
## Series: train_ts 
## ARIMA(0,1,0) 
## 
## sigma^2 = 1759121:  log likelihood = -1532.42
## AIC=3066.84   AICc=3066.86   BIC=3070.02
# Pronosticar dic 2024
future_forecast_sarima <- forecast(auto_arima_model, h = 1)

# Extraer el valor específico de octubre 2024
forecast_oct2024_sarima <- future_forecast_sarima$mean[1]
print(paste("Pronóstico para dic 2024:", forecast_oct2024_sarima))
## [1] "Pronóstico para dic 2024: 18000"

Conclusión:

A pesar de que el modelo SARIMA automático (0,1,0) (0,1,0) muestra los mejores valores de AIC y BIC dentro de la muestra, su desempeño fuera de la muestra lo hace poco confiable, ya que genera pronósticos constantes sin capturar la variabilidad del mercado.

Por lo tanto, el modelo ARIMA(1,2,1) se posiciona como el más adecuado para el pronóstico del precio de la acción del Banco de Occidente. Aunque su AIC y BIC no son los más bajos, su estructura permite reflejar mejor los movimientos de la serie, lo que lo convierte en la opción más confiable.

Para mejorar los pronósticos, se podría explorar la inclusión de un modelo SARIMA con términos autorregresivos y de media móvil o aplicar transformaciones que permitan captar mejor la dinámica del mercado.

Referencias Cortés Rodríguez, N. (27 de noviembre de 2024). Acción del Banco de Occidente sube más de 28% a $18.000 por liquidez de la misma. La República. Recuperado de https://www.larepublica.co/finanzas/accion-del-banco-de-occidente-sube-mas-de-28-a-18-000-por-liquidez-de-la-misma-4008771

Note that the echo = FALSE parameter was added to the code chunk to prevent printing of the R code that generated the plot.