Considere la serie de tiempo asociada con las acciones de Tecnoglass desde que comenzó a comercializarse hasta la fecha del día de hoy. Puede utilizar la API de Yahoo Finance para obtener esta serie de tiempo (ver yahoofinancer).
Repita TODOS los pasos indicados en esta sección para encontrar modelos ARIMA para predecir el precio de las acciones de Tecnoglass con los siguientes horizontes: 7, 14 días, 21 días, 28 días. Utilizar siempre predicciones usando rolling con ventana de predicción continua de un día. Cualquier cantidad de pasos extra para enriquecer su análisis predictivo serán aceptados siempre y cuando sean acordes con lo que indica la teoría de análisis de series de tiempo.
Repita el paso 2 ahora sin utilizar rolling. Esto es, realice el pronóstico solo utilizando forecast() para los diferentes horizontes de predicción, 7, 14 días, 21 días, 28 días.
Realice tablas de error para los ítems 1 y 2, utilizando las métricas: MAPE, MAE, RMSE, MSE, R2. Además, agregue el gráfico de correlación entre la observación real y su predicción en el test, Corr(yt,yt)Corr(��,��).
Repita el análisis desarrollado en los pasos anteriores, considerando ahora el criterio de inferencia Bayesiana (BIC) y el criterio de información de Hannan–Quinn (HQIC) para encontrar el mejor modelo ARIMA y, compare los errores con aquellos obtenidos con el criterio de Akaike.
Escriba en cada paso las conclusiones y análisis estadísticos asociados con los resultados obtenidos. Realice tests de normalidad e independencia para los residuales obtenidos para cada predicción, en cada caso agregue las correspondientes conclusiones. Figuras y algoritmos que no estén acompañados de una conclusión, descripción y análisis estadístico, no serán tenidas en cuenta.
Capturando la información del TICKER TGLS (Tecnoglass)
stock <-"TGLS"start_date <-as.Date("2014-04-01")end_date <-Sys.Date()getSymbols(stock, src ="yahoo", from = start_date, to = end_date)
[1] "TGLS"
TGLS_data <-na.omit(get(stock))TGLS<-data.frame(Date =index(TGLS_data), TGLS_data)# Mostrar el marco de datos con las fechas usando datatabledatatable(TGLS)
plot(TGLS$Date, TGLS$TGLS.Close, type ="l", main ="Serie de Tiempo de TGLS.Close",ylab ="Precio de Cierre",xlab ="Fecha")
hist(TGLS$TGLS.Close, main ="Histgram for Closing Price", xlab ="Freq", breaks ="Sturges", probability =TRUE)lines(density(TGLS$TGLS.Close))
La mayor parte del tiempo de la serie, el precio de cierre del stock ha estado alrededor de los 10 USD
library(ggplot2)library(lubridate)
Attaching package: 'lubridate'
The following objects are masked from 'package:base':
date, intersect, setdiff, union
TGLS$Month <-factor(month(TGLS$Date), labels = month.abb)ggplot(TGLS, aes(x = Month, y = TGLS.Close)) +geom_boxplot() +labs(x ="Mes", y ="Precio de Cierre", title ="Boxplot de TGLS.Close por Mes")
De esta figura no se logra identificar una estacionariedad mensual, en general los datos parecen tener mayor dispersión por encima de la mediana. El mes de abril es en el que se encuentran la mayoría de datos atípicos.
Probando Estacionariedad con prueba de Dickey-Fuller
adf.test(Cl(TGLS_data$TGLS.Close), alternative ="stationary")
Augmented Dickey-Fuller Test
data: Cl(TGLS_data$TGLS.Close)
Dickey-Fuller = -0.63585, Lag order = 13, p-value = 0.9757
alternative hypothesis: stationary
A partir de lo anterior, como P-value >0.05 podemos afirmar que esta serie de tiempo No es Estacionaria
Transformacion a serie estacionaria
* Usemos una diferenciación de primer orden:
price_diff <-diff(TGLS$TGLS.Close, lag =1)date_diff <- TGLS$Date[-1]# Graficar las diferenciasplot(date_diff, price_diff, type ="l", main ="Diferencia en Precio de Cierre de TGLS.Close",ylab ="Diferencia de Precio de Cierre",xlab ="Fecha")
Realicemos una vez mas la prueba de Dickey Fuller para verificar si esta nueva serie es estacionaria
# Realiza la prueba ADF ignorando los valores faltantesadf.test(na.omit(price_diff), alternative ="stationary")
Warning in adf.test(na.omit(price_diff), alternative = "stationary"): p-value
smaller than printed p-value
Augmented Dickey-Fuller Test
data: na.omit(price_diff)
Dickey-Fuller = -13.064, Lag order = 13, p-value = 0.01
alternative hypothesis: stationary
Como el P-valor < 0.05 entonces rechazamos la hipótesis nula, y aceptamos la hipótesis alternativa en la que la serie Si es estacionaria
De estos graficos entendemos que una buena aproximación para los factores de diferenciación podrían ser AR(2) y diferenciación de nivel 1. (2,1,0)
TGLS_Price <-arima(price_diff, order =c(2, 1, 0))summary(TGLS_Price)
Call:
arima(x = price_diff, order = c(2, 1, 0))
Coefficients:
ar1 ar2
-0.6397 -0.3780
s.e. 0.0184 0.0184
sigma^2 estimated as 0.5769: log likelihood = -2898.81, aic = 5803.63
Training set error measures:
ME RMSE MAE MPE MAPE MASE ACF1
Training set 0.0001253908 0.7593618 0.403262 NaN Inf 0.8126558 -0.08701854
checkresiduals(TGLS_Price)
Ljung-Box test
data: Residuals from ARIMA(2,1,0)
Q* = 245.53, df = 8, p-value < 2.2e-16
Model df: 2. Total lags used: 10
Este modelo no resulta muy bueno, dado que no pasa la prueba de Normalidad ni de Indepencia, no podemos afirmar que los residuales son ruido blanco, es decir, todavía existe un componente de autocorrelación que no se logró capturar en el modelo.
Ahora reentrenamos el modelo usando los parámetros encontrados en el paso anterior:
Capturamos y evaluamos los residuales:
Funcion para evaluar residuales
evaluar_residuales <-function(model, alpha =0.05) {# Prueba de normalidad (Shapiro-Wilk) shapiro_test <-shapiro.test(model$residuals)if (shapiro_test$p.value > alpha) { resultado_normalidad <-"Los residuales se distribuyen normalmente" } else { resultado_normalidad <-"Los residuales no se distribuyen normalmente" }# Prueba de independencia (Ljung-Box) ljung <-Box.test(model$residuals, type ="Ljung-Box", lag =20) ljung_pvalues <- ljung$p.valueif (all(ljung_pvalues > alpha)) { resultado_independencia <-"Los residuales son independientes" } else { resultado_independencia <-"Los residuales no son independientes" }# Gráfico de residuoscheckresiduals(model)return(list(resultado_normalidad = resultado_normalidad, resultado_independencia = resultado_independencia))}
best_fit <-arima(scale(ts), order=c(2,1,2), seasonal =list(order =c(1,1,1), period =12),xreg=1:length(ts), method="CSS-ML")best_residuales<-residuals(best_fit)evaluar_residuales(best_fit,alpha =0.05)
Ljung-Box test
data: Residuals from ARIMA(2,1,2)(1,1,1)[12]
Q* = 1054.7, df = 724, p-value = 9.77e-15
Model df: 6. Total lags used: 730
$resultado_normalidad
[1] "Los residuales no se distribuyen normalmente"
$resultado_independencia
[1] "Los residuales son independientes"
Aunque los residuales no se distribuyan normalmente, podemos confirmar que son independientes lo cual nos garantiza que el modelo esta capturando correctamente la correlación de la serie y el los residuales son ruido
Funciones
Arima Rolling
arima_rolling <-function(history, test, best_order) { predictions <-vector() residuals <-vector()for (t in1:length(test)) { model <-arima(history, order = best_order) model_fit <- forecast:::Arima(y = history, model = model, h =1) residuals <- residuals + model$residuals output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1] predictions <-c(predictions, yhat) obs <- test[t] history <-c(history, obs)cat(sprintf("predicted=%f, expected=%f\n", yhat, obs)) }return(list(predictions = predictions, residuals = residuals))}
Arima sin Rolling
arima_sin_rolling <-function(train, test, best_order) { model <-arima(history, order = best_order) predictions <- forecast::forecast(model, h =length(test))$mean residuals <-residuals(model)return(list(predictions = predictions, model=model))}
Graficos de predicciones
library(plotly)# Función para graficar predicciones de forma interactiva con plotlygrafico_predicciones<-function(dates_train, train, dates_w, test_wl, yhat_w, window) {# Crear el gráfico de líneas con plotly p <-plot_ly() %>%add_lines(x = dates_train, y = train, name ="Train", color =I("green")) %>%add_lines(x = dates_w, y = test_wl, name ="Test", color =I("blue")) %>%add_lines(x = dates_w, y = yhat_w, name ="Forecast", color =I("red")) %>%layout(title =paste("Gráfico de Predicciones para Horizonte de", window, "Días"),xaxis =list(title ="Fecha"),yaxis =list(title ="Valor"),legend =list(x =0.9, y =1) )# Mostrar el gráfico p}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3644"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 7"
ARIMA CON ROLLING
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3637"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 14"
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3630"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 21"
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3623"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 28"
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3644"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 7"
ARIMA CON ROLLING
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3637"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 14"
ARIMA CON ROLLING
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3630"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 21"
ARIMA CON ROLLING
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
# Creamos la secuencia de fechas de entrenamiento y pruebadates_train <-seq(start_date, end_date, by ="days")print(paste("Fechas de entrenamiento:", length(dates_train)))
[1] "Fechas de entrenamiento: 3623"
start_date_test <- end_date +1end_date_test <- start_date_test + n_test-1dates_test <-seq(start_date_test, end_date_test, by ="days")print(paste("Fechas de prueba:", length(dates_test)))
[1] "Fechas de prueba: 28"
ARIMA CON ROLLING
predictions <-vector()residuals <-vector()model <-arima(train, order = best_pdq)history<-trainfor (t in1:length(test_w)) {# Realizar el pronóstico para un paso adelante model_fit <- forecast:::Arima(y = history, model = model, h =1)# Actualizar los residuales residuals <-c(residuals, model_fit$residuals)# Obtener la predicción output <- forecast:::forecast.Arima(model_fit, h =1) yhat <- output$mean[1]# Almacenar la predicción predictions <-c(predictions, yhat)# Avanzar al siguiente paso de tiempo obs <- test_w[t] history <-c(history, obs) # Agregar el nuevo valor observado a history history <- history[-1] # Eliminar el primer valor de history para mantener su longitud constante# Imprimir la predicción y el valor esperadocat(sprintf("predicted=%f, expected=%f\n", yhat, obs))}
Para convertir la serie en Estacionaria, se requirió una diferenciación de primer orden.
Cuando usamos la funcion para minimizar el AIC los mejores parámetros pdq fueron 2,1,2 respectivamente, mientras que con el BIC, fueron1,1,1
Comparativamente las predicciones realizadas con **Rolling Forecasting** resultaron mejores que las predicciones simples (sin rolling), ya que logran capturar mucho mejor las fluctuaciones de la seria a menor escala.
En cuanto a métricas, el MAPE es mejor en las predicciones con rolling para las ventanas de 14, 21 y 28 días, manteniendose constante alrededor del 13%
Mientras que en los modelos sin rolling las predicciones de la ventana de 7 días resultan mejores, pero a medida que la ventana de predicción aumenta, también lo hace el MAPE, llegando hasta un 40% de error en la ventana de los 28 días.
Si comparamos el criterio AIC vs BIC, para este caso particular, el BIC nos arroja mejores resultados, aunque, las diferencias son mínimas, entre 1%y 2%.
En cuanto a los residuos, todos los modelos realizados arrojan independencia, pero no se logra una distribución normal.