1. Introdução¶
O mercado financeiro é um ambiente dinâmico e altamente influenciado por uma ampla gama de fatores, incluindo indicadores econômicos, polÃticas governamentais, eventos globais e o comportamento dos investidores. Dentro desse contexto, prever o movimento futuro da bolsa de valores, especificamente do Ãndice Ibovespa, é um desafio complexo que vai muito além da simples análise dos dados históricos. Se fosse possÃvel prever com 100% de precisão o comportamento do mercado, aqueles que dominassem essa habilidade certamente estariam entre as pessoas mais ricas do mundo.
Neste trabalho, utilizaremos uma base de dados do Ibovespa com um perÃodo de 10 anos para construir modelos de previsão utilizando técnicas de séries temporais. Os modelos escolhidos para essa tarefa são o ARIMA (Autoregressive Integrated Moving Average) e o SARIMA (Seasonal Autoregressive Integrated Moving Average), que são amplamente utilizados para analisar padrões e tendências em dados temporais. Nosso objetivo é desenvolver um modelo que possa atingir uma taxa de acerto acima de 70%, oferecendo previsões que, embora não sejam infalÃveis, possam auxiliar na tomada de decisões estratégicas no mercado de ações.
2. Importação de Bibliotecas e Carregamento dos Dados¶
- pandas Manipulação de dados tabulares (DataFrames).
- numpy Cálculos numéricos e manipulação de arrays/matrizes.
- matplotlib Gráficos básicos (linha, barras, dispersão).
- seaborn Visualizações estatÃsticas avançadas e estilizadas.
- scikit-learn Machine Learning (classificação, regressão, clustering).
- statsmodels Modelos estatÃsticos e séries temporais (ARIMA, regressão).
# instalando os pacotes necessários
#pip install pandas numpy matplotlib seaborn scikit-learn statsmodels
from datetime import datetime
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import adfuller
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.statespace.sarimax import SARIMAX
from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.graphics.tsaplots import plot_acf
import warnings
warnings.filterwarnings("ignore")
df = pd.read_csv('data.csv')
df.head()
| Data | Último | Abertura | Máxima | MÃnima | Vol. | Var% | |
|---|---|---|---|---|---|---|---|
| 0 | 30.12.2024 | 120.283 | 120.267 | 121.050 | 120.158 | 8,90M | 0,01% |
| 1 | 27.12.2024 | 120.269 | 121.078 | 121.609 | 120.252 | 8,94M | -0,67% |
| 2 | 26.12.2024 | 121.078 | 120.767 | 121.612 | 120.428 | 8,34M | 0,26% |
| 3 | 23.12.2024 | 120.767 | 122.105 | 122.105 | 120.617 | 9,95M | -1,09% |
| 4 | 20.12.2024 | 122.102 | 121.183 | 122.209 | 120.700 | 18,13M | 0,75% |
- Invertemos o DataFrame para que os dados fiquem organizados em ordem crescente, garantindo que as datas sejam apresentadas do mais antigo para o mais recente.
- Definimos a variável data como o Ãndice de nosso data set.
df['Data'] = pd.to_datetime(df['Data'])
df.set_index('Data', inplace=True) # definindo a data como Ãndice
df.head()
| Último | Abertura | Máxima | MÃnima | Vol. | Var% | |
|---|---|---|---|---|---|---|
| Data | ||||||
| 2024-12-30 | 120.283 | 120.267 | 121.050 | 120.158 | 8,90M | 0,01% |
| 2024-12-27 | 120.269 | 121.078 | 121.609 | 120.252 | 8,94M | -0,67% |
| 2024-12-26 | 121.078 | 120.767 | 121.612 | 120.428 | 8,34M | 0,26% |
| 2024-12-23 | 120.767 | 122.105 | 122.105 | 120.617 | 9,95M | -1,09% |
| 2024-12-20 | 122.102 | 121.183 | 122.209 | 120.700 | 18,13M | 0,75% |
df = df.iloc[::-1]
df.head()
| Último | Abertura | Máxima | MÃnima | Vol. | Var% | |
|---|---|---|---|---|---|---|
| Data | ||||||
| 2015-01-02 | 48.512 | 50.004 | 50.004 | 48.345 | 2,88M | -2,99% |
| 2015-01-05 | 47.517 | 48.512 | 48.512 | 47.264 | 3,87M | -2,05% |
| 2015-01-06 | 48.001 | 47.517 | 48.061 | 47.338 | 4,56M | 1,02% |
| 2015-01-07 | 49.463 | 48.006 | 49.882 | 48.006 | 4,41M | 3,05% |
| 2015-01-08 | 49.943 | 49.463 | 50.261 | 49.017 | 3,62M | 0,97% |
df.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 2479 entries, 2015-01-02 to 2024-12-30 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Último 2479 non-null float64 1 Abertura 2479 non-null float64 2 Máxima 2479 non-null float64 3 MÃnima 2479 non-null float64 4 Vol. 2478 non-null object 5 Var% 2479 non-null object dtypes: float64(4), object(2) memory usage: 135.6+ KB
# Plot dos dados originais
plt.figure(figsize=(14, 7))
plt.plot(df.index, df["Último"], label="Valor de Fechamento")
plt.title("Fechamento IBOVESPA (2015-2025)")
plt.xlabel("Data")
plt.ylabel("Fechamento(R$)")
plt.legend()
plt.show()
3. Decomposição da Série Temporal¶
- Tendência: Representa o movimento de longo prazo nos dados.
- Sazonalidade: Padrões que se repetem em intervalos regulares.
- ResÃduos: Variações aleatórias ou ruÃdo nos dados.
Primeiro vamos análisar com o modelo "additive" e um periodo de um ano (253 dias úteis)
# Decomposição aditiva
resultado = seasonal_decompose(df["Último"], model="additive", period=253)
# Plot da decomposição
ax = resultado.plot()
ax.get_figure().set_size_inches(15, 7)
Observamos uma tendência de alta, uma sazonalidade bem definida e um ruÃdo contÃnuo, conforme evidenciado em nosso gráfico.
Nossa decomposição utilizando o modelo "additive" apresentou um ruÃdo menos disperso do que o esperado. Agora, vamos analisá-la com o modelo "multiplicative" para verificar possÃveis diferenças
# Decomposição aditiva
resultado = seasonal_decompose(df["Último"], model="multiple", period=253)
# Plot da decomposição
ax = resultado.plot()
ax.get_figure().set_size_inches(15, 7)
Observamos que, ao utilizar o modelo "multiplicative", os resÃduos deixam de oscilar em torno de zero, indicando uma possÃvel mudança na estrutura da decomposição.
Vamos retornar ao modelo "additive", agora considerando um perÃodo de 1 mês (20 dias úteis) para ver como os dados de comportam
# Decomposição aditiva
resultado = seasonal_decompose(df["Último"], model="additive", period=20)
# Plot da decomposição
ax = resultado.plot()
ax.get_figure().set_size_inches(15, 7)
Com o perÃodo de um mês, os resÃduos se aproximam mais de uma distribuição normal, e a sazonalidade apresenta mudanças significativas, refletindo melhor a estrutura dos dados.
4. Teste de Estacionariedade¶
Vamos realizar um teste de estacionariedade em nossos dados para verificar se podemos utilizá-los diretamente no modelo ARIMA ou se será necessário aplicar diferenciação.
# Teste ADF
resultado_adf = adfuller(df["Último"].dropna())
print(f"EstatÃstica ADF: {resultado_adf[0]}")
print(f"Valor-p: {100*resultado_adf[1]:.2f}%")
# for key, value in resultado_adf[4].items():
# print(f"Valor CrÃtico {key}: {value}")
# Interpretação
if resultado_adf[1] < 0.05:
print("A série é estacionária.")
else:
print("A série não é estacionária.")
EstatÃstica ADF: -1.6164309707909765 Valor-p: 47.46% A série não é estacionária.
Como podemos observar, será necessário aplicar uma diferenciação antes de utilizar o modelo ARIMA
5. Estacionarização da Série Temporal¶
Vamos aplicar dois tipos de diferenciação: a de primeira ordem e a sazonal, para analisar como cada uma impacta a série e seu comportamento.
- Diferenciação de Primeira Ordem
# Diferenciação de primeira ordem
df["ultimo_diff"] = df["Último"] - df["Último"].shift(1)
df["ultimo_diff"] = df["ultimo_diff"].dropna()
# df["tavg_diff"] = df["tavg"].diff().dropna()
# Plot da série diferenciada
plt.figure(figsize=(14, 7))
plt.plot(df.index, df["ultimo_diff"], label="Série Diferenciada (1ª Ordem)")
plt.title("Série Diferenciada (1ª Ordem)")
plt.xlabel("Data")
plt.ylabel("Diferença de Temperatura (°C)")
plt.legend()
plt.show()
- Diferenciação Sazonal
# Diferenciação sazonal (perÃodo de 365 dias)
df["ultimo_seasonal_diff"] = df["Último"] - df["Último"].shift(365)
df["ultimo_seasonal_diff"] = df["ultimo_seasonal_diff"].dropna()
# Plot da série sazonalmente diferenciada
plt.figure(figsize=(14, 7))
plt.plot(
df.index, df["ultimo_seasonal_diff"], label="Série Diferenciada Sazonalmente"
)
plt.title("Série Diferenciada Sazonalmente")
plt.xlabel("Data")
plt.ylabel("Diferença de Temperatura (°C)")
plt.legend()
<matplotlib.legend.Legend at 0x237991032c0>
Agora, vamos aplicar o teste de estacionariedade ADF em ambas as séries para análise.
- Diferenciação de Primeira Ordem
# Teste ADF na série diferenciada
resultado_adf_diff = adfuller(df["ultimo_diff"].dropna())
print(f"EstatÃstica ADF: {resultado_adf_diff[0]}")
print(f"Valor-p: {100*resultado_adf_diff[1]:.4}%")
# Interpretação
if resultado_adf_diff[1] < 0.05:
print("A série diferenciada é estacionária.")
else:
print("A série diferenciada não é estacionária.")
EstatÃstica ADF: -15.923310753216212 Valor-p: 7.879e-27% A série diferenciada é estacionária.
- Diferenciação Sazonal
# Teste ADF na série diferenciada
resultado_adf_diff = adfuller(df["ultimo_seasonal_diff"].dropna())
print(f"EstatÃstica ADF: {resultado_adf_diff[0]}")
print(f"Valor-p: {100*resultado_adf_diff[1]:.4}%")
# Interpretação
if resultado_adf_diff[1] < 0.05:
print("A série diferenciada é estacionária.")
else:
print("A série diferenciada não é estacionária.")
EstatÃstica ADF: -3.639258929767526 Valor-p: 0.5053% A série diferenciada é estacionária.
6. Identificação dos Parâmetros do Modelo¶
Para utilizar o modelo ARIMA, precisamos identificar os parâmetros "q" e "p". Para isso, usaremos a Função de Autocorrelação (ACF) para determinar "q" e a Função de Autocorrelação Parcial (PACF) para identificar "p".
# Plot da ACF e PACF da série original
plt.figure(figsize=(14, 6))
plt.subplot(121)
plot_acf(df["Último"].dropna(), ax=plt.gca(), lags=253)
plt.title("Função de Autocorrelação (ACF)")
plt.subplot(122)
plot_pacf(df["Último"].dropna(), ax=plt.gca(), lags=50)
plt.title("Função de Autocorrelação Parcial (PACF)")
plt.show()
Com esses gráficos, podemos observar que os momentos de normalidade para "q" e "p" ocorrem, respectivamente, entre os lags 210 e 4. Vamos ampliar os gráficos para visualizar com mais precisão em quais lags esses momentos se encontram.
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.stattools import pacf
# Criar a figura
plt.figure(figsize=(14, 6))
# Plot ACF
plt.subplot(121)
plot_acf(df["Último"].dropna(), ax=plt.gca(), lags=253)
plt.title("Função de Autocorrelação (ACF)")
# Plot PACF com rótulos nos pontos
plt.subplot(122)
ax_pacf = plt.gca()
plot_pacf(df["Último"].dropna(), ax=ax_pacf, lags=5)
# Obtendo valores PACF para rotulagem
pacf_values = pacf(df["Último"].dropna(), nlags=5)
lags = range(51) # Inclui o lag 0
# Adicionando rótulos nos pontos do PACF
for i, value in enumerate(pacf_values):
ax_pacf.annotate(f'{value:.2f}', (i, value), textcoords="offset points", xytext=(0, 5), ha='center')
plt.title("Função de Autocorrelação Parcial (PACF)")
plt.show()
Podemos observar, por meio da equação PACF, que o momento de normalidade do parâmetro "p" ocorre no lag 2. Agora, vamos ampliar o gráfico de ACF para identificar com mais precisão o lag correspondente ao momento de normalidade do parâmetro "q".
import matplotlib.pyplot as plt
import numpy as np
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.stattools import pacf
# Criar a figura
plt.figure(figsize=(28, 6))
# Plot ACF com grid no eixo x e rótulos de 10 em 10
plt.subplot(121)
ax_acf = plt.gca()
plot_acf(df["Último"].dropna(), ax=ax_acf, lags=253)
ax_acf.set_title("Função de Autocorrelação (ACF)")
ax_acf.grid(axis='x', linestyle='--', alpha=0.7) # Adicionando grid no eixo x
ax_acf.set_xticks(np.arange(0, 254, 10)) # Define os rótulos do eixo x de 10 em 10
plt.show()
Definindo os parametos: Utilizamos $d = 1$ porque é necessário diferenciar os dados uma vez para torná-los estacionários.
p = 2
q = 210
d = 1
7. Aplicando modelo ARIMA¶
# Ajuste do modelo ARIMA(p, d, q)
# Suposição: p=9, d=0, q=20 (com base na análise dos gráficos ACF e PACF)
model_arima = ARIMA(df["Último"], order=(p, d, q))
resultado_arima = model_arima.fit()
# Resumo do modelo
print(resultado_arima.summary())
SARIMAX Results
==============================================================================
Dep. Variable: Último No. Observations: 2479
Model: ARIMA(2, 1, 210) Log Likelihood -4040.006
Date: Fri, 21 Mar 2025 AIC 8506.012
Time: 05:46:32 BIC 9744.651
Sample: 0 HQIC 8955.898
- 2479
Covariance Type: opg
==============================================================================
coef std err z P>|z| [0.025 0.975]
------------------------------------------------------------------------------
ar.L1 -0.1908 1.544 -0.124 0.902 -3.216 2.835
ar.L2 -0.1834 0.379 -0.484 0.628 -0.926 0.559
ma.L1 0.1041 1.544 0.067 0.946 -2.922 3.130
ma.L2 0.2029 0.438 0.464 0.643 -0.655 1.061
ma.L3 0.0181 0.077 0.236 0.813 -0.132 0.169
ma.L4 -0.0217 0.046 -0.475 0.635 -0.111 0.068
ma.L5 0.0133 0.059 0.226 0.821 -0.102 0.128
ma.L6 -0.0373 0.035 -1.071 0.284 -0.106 0.031
ma.L7 0.0973 0.057 1.703 0.089 -0.015 0.209
ma.L8 0.0183 0.162 0.113 0.910 -0.299 0.336
ma.L9 0.0229 0.040 0.567 0.570 -0.056 0.102
ma.L10 0.0312 0.021 1.457 0.145 -0.011 0.073
ma.L11 0.0281 0.050 0.566 0.572 -0.069 0.125
ma.L12 0.0230 0.038 0.603 0.546 -0.052 0.098
ma.L13 -0.0553 0.028 -1.995 0.046 -0.110 -0.001
ma.L14 0.0027 0.099 0.027 0.978 -0.191 0.196
ma.L15 -0.0246 0.041 -0.604 0.546 -0.105 0.055
ma.L16 0.0189 0.033 0.580 0.562 -0.045 0.083
ma.L17 0.0151 0.039 0.389 0.697 -0.061 0.091
ma.L18 0.0175 0.030 0.582 0.561 -0.042 0.077
ma.L19 -0.0158 0.027 -0.581 0.561 -0.069 0.037
ma.L20 -0.0311 0.039 -0.789 0.430 -0.108 0.046
ma.L21 0.0098 0.048 0.202 0.840 -0.085 0.105
ma.L22 -0.0248 0.042 -0.594 0.552 -0.107 0.057
ma.L23 -0.0330 0.044 -0.754 0.451 -0.119 0.053
ma.L24 -0.0023 0.053 -0.044 0.965 -0.106 0.101
ma.L25 -0.0380 0.032 -1.193 0.233 -0.100 0.024
ma.L26 0.0043 0.058 0.075 0.940 -0.109 0.117
ma.L27 0.0118 0.032 0.365 0.715 -0.052 0.075
ma.L28 0.0115 0.035 0.332 0.740 -0.056 0.079
ma.L29 0.0587 0.024 2.467 0.014 0.012 0.105
ma.L30 -0.0319 0.086 -0.371 0.711 -0.201 0.137
ma.L31 -0.0325 0.078 -0.418 0.676 -0.185 0.120
ma.L32 -0.0152 0.054 -0.283 0.777 -0.120 0.090
ma.L33 0.0073 0.026 0.277 0.782 -0.044 0.059
ma.L34 0.0622 0.031 1.993 0.046 0.001 0.123
ma.L35 0.0071 0.095 0.075 0.940 -0.178 0.193
ma.L36 0.0218 0.035 0.618 0.537 -0.047 0.091
ma.L37 0.0161 0.031 0.515 0.606 -0.045 0.077
ma.L38 0.0078 0.033 0.238 0.812 -0.057 0.072
ma.L39 0.0464 0.026 1.800 0.072 -0.004 0.097
ma.L40 -0.0208 0.070 -0.296 0.767 -0.159 0.117
ma.L41 -0.0072 0.059 -0.123 0.902 -0.122 0.108
ma.L42 -0.0056 0.029 -0.195 0.845 -0.061 0.050
ma.L43 -0.0174 0.024 -0.721 0.471 -0.065 0.030
ma.L44 0.0010 0.035 0.029 0.977 -0.069 0.071
ma.L45 -0.0245 0.026 -0.955 0.340 -0.075 0.026
ma.L46 0.0889 0.043 2.056 0.040 0.004 0.174
ma.L47 -0.0208 0.150 -0.138 0.890 -0.315 0.274
ma.L48 -0.0145 0.076 -0.190 0.849 -0.164 0.135
ma.L49 -0.0470 0.045 -1.043 0.297 -0.135 0.041
ma.L50 0.0237 0.058 0.407 0.684 -0.090 0.138
ma.L51 -0.0126 0.064 -0.196 0.845 -0.139 0.114
ma.L52 -0.0270 0.036 -0.745 0.456 -0.098 0.044
ma.L53 -0.0366 0.051 -0.717 0.473 -0.137 0.064
ma.L54 -0.0264 0.048 -0.548 0.584 -0.121 0.068
ma.L55 0.0104 0.032 0.323 0.747 -0.053 0.074
ma.L56 -0.0197 0.041 -0.483 0.629 -0.100 0.060
ma.L57 -0.0010 0.041 -0.025 0.980 -0.081 0.079
ma.L58 -0.0019 0.025 -0.076 0.939 -0.051 0.047
ma.L59 -0.0189 0.024 -0.789 0.430 -0.066 0.028
ma.L60 -0.0315 0.037 -0.841 0.400 -0.105 0.042
ma.L61 -0.0123 0.049 -0.253 0.800 -0.108 0.083
ma.L62 -0.0447 0.026 -1.733 0.083 -0.095 0.006
ma.L63 -0.0168 0.065 -0.258 0.796 -0.144 0.111
ma.L64 -0.0321 0.029 -1.118 0.263 -0.088 0.024
ma.L65 0.0333 0.042 0.788 0.431 -0.050 0.116
ma.L66 0.0076 0.069 0.109 0.913 -0.128 0.143
ma.L67 -0.0074 0.029 -0.256 0.798 -0.064 0.049
ma.L68 -0.0210 0.034 -0.624 0.533 -0.087 0.045
ma.L69 -0.0070 0.037 -0.190 0.849 -0.079 0.065
ma.L70 -0.0119 0.024 -0.500 0.617 -0.059 0.035
ma.L71 -0.0423 0.028 -1.518 0.129 -0.097 0.012
ma.L72 -0.0056 0.065 -0.087 0.931 -0.132 0.121
ma.L73 0.0290 0.030 0.966 0.334 -0.030 0.088
ma.L74 -0.0047 0.063 -0.074 0.941 -0.129 0.119
ma.L75 0.0155 0.036 0.426 0.670 -0.056 0.087
ma.L76 -0.0506 0.032 -1.576 0.115 -0.113 0.012
ma.L77 0.0120 0.084 0.143 0.886 -0.152 0.176
ma.L78 -0.0516 0.049 -1.062 0.288 -0.147 0.044
ma.L79 0.0150 0.078 0.193 0.847 -0.138 0.168
ma.L80 -0.0606 0.047 -1.301 0.193 -0.152 0.031
ma.L81 0.0072 0.092 0.078 0.938 -0.172 0.187
ma.L82 -0.0404 0.043 -0.937 0.349 -0.125 0.044
ma.L83 -0.0219 0.058 -0.380 0.704 -0.135 0.091
ma.L84 -0.0538 0.039 -1.394 0.163 -0.130 0.022
ma.L85 -0.0389 0.070 -0.558 0.577 -0.176 0.098
ma.L86 0.0016 0.049 0.032 0.974 -0.094 0.097
ma.L87 -0.0126 0.039 -0.324 0.746 -0.089 0.064
ma.L88 -0.0516 0.031 -1.668 0.095 -0.112 0.009
ma.L89 -0.0254 0.084 -0.301 0.763 -0.191 0.140
ma.L90 -0.0350 0.033 -1.060 0.289 -0.100 0.030
ma.L91 -0.0159 0.043 -0.373 0.709 -0.099 0.068
ma.L92 -0.0194 0.028 -0.690 0.491 -0.075 0.036
ma.L93 -0.0152 0.032 -0.469 0.639 -0.079 0.048
ma.L94 0.0006 0.029 0.021 0.984 -0.057 0.058
ma.L95 -0.0082 0.027 -0.298 0.766 -0.062 0.045
ma.L96 -0.0383 0.026 -1.458 0.145 -0.090 0.013
ma.L97 0.0052 0.062 0.083 0.934 -0.117 0.127
ma.L98 -0.0653 0.037 -1.755 0.079 -0.138 0.008
ma.L99 -0.0270 0.098 -0.276 0.783 -0.219 0.165
ma.L100 -0.0080 0.037 -0.213 0.831 -0.081 0.065
ma.L101 -0.0029 0.027 -0.109 0.913 -0.055 0.049
ma.L102 -0.0122 0.025 -0.482 0.630 -0.062 0.038
ma.L103 -0.0273 0.031 -0.883 0.377 -0.088 0.033
ma.L104 0.0319 0.044 0.729 0.466 -0.054 0.118
ma.L105 -0.0064 0.067 -0.096 0.924 -0.138 0.125
ma.L106 0.0020 0.034 0.058 0.953 -0.065 0.069
ma.L107 0.0242 0.023 1.037 0.300 -0.021 0.070
ma.L108 0.0081 0.047 0.171 0.864 -0.085 0.101
ma.L109 -0.0043 0.026 -0.169 0.866 -0.055 0.046
ma.L110 -0.0022 0.028 -0.078 0.938 -0.057 0.053
ma.L111 0.0197 0.025 0.786 0.432 -0.029 0.069
ma.L112 0.0097 0.041 0.237 0.812 -0.071 0.090
ma.L113 0.0258 0.026 1.010 0.312 -0.024 0.076
ma.L114 0.0373 0.040 0.932 0.351 -0.041 0.116
ma.L115 -0.0006 0.054 -0.011 0.991 -0.106 0.105
ma.L116 -0.0016 0.034 -0.048 0.962 -0.068 0.065
ma.L117 0.0218 0.026 0.850 0.395 -0.028 0.072
ma.L118 0.0304 0.045 0.673 0.501 -0.058 0.119
ma.L119 0.0540 0.046 1.165 0.244 -0.037 0.145
ma.L120 -0.0213 0.071 -0.301 0.763 -0.160 0.117
ma.L121 0.0703 0.065 1.079 0.281 -0.057 0.198
ma.L122 -0.0352 0.113 -0.312 0.755 -0.256 0.186
ma.L123 -0.0030 0.080 -0.037 0.970 -0.160 0.154
ma.L124 -0.0185 0.030 -0.612 0.541 -0.078 0.041
ma.L125 0.0100 0.028 0.353 0.724 -0.046 0.066
ma.L126 -0.0041 0.033 -0.122 0.903 -0.070 0.061
ma.L127 -0.0243 0.026 -0.938 0.348 -0.075 0.027
ma.L128 0.0505 0.046 1.100 0.271 -0.040 0.141
ma.L129 0.0503 0.093 0.539 0.590 -0.132 0.233
ma.L130 0.0073 0.070 0.105 0.917 -0.129 0.144
ma.L131 0.0148 0.037 0.399 0.690 -0.058 0.088
ma.L132 0.0309 0.030 1.040 0.298 -0.027 0.089
ma.L133 0.0341 0.053 0.642 0.521 -0.070 0.138
ma.L134 0.0302 0.046 0.656 0.512 -0.060 0.121
ma.L135 0.0697 0.036 1.916 0.055 -0.002 0.141
ma.L136 0.0181 0.097 0.188 0.851 -0.171 0.207
ma.L137 0.0315 0.032 0.986 0.324 -0.031 0.094
ma.L138 0.0110 0.038 0.289 0.773 -0.063 0.085
ma.L139 0.0576 0.025 2.273 0.023 0.008 0.107
ma.L140 -0.0170 0.083 -0.203 0.839 -0.181 0.147
ma.L141 0.0282 0.059 0.475 0.635 -0.088 0.145
ma.L142 0.0444 0.047 0.946 0.344 -0.048 0.136
ma.L143 0.0177 0.072 0.244 0.807 -0.124 0.160
ma.L144 0.0214 0.030 0.721 0.471 -0.037 0.080
ma.L145 0.0372 0.029 1.291 0.197 -0.019 0.094
ma.L146 0.0181 0.057 0.319 0.749 -0.093 0.129
ma.L147 0.0267 0.029 0.920 0.357 -0.030 0.084
ma.L148 0.0071 0.037 0.193 0.847 -0.065 0.079
ma.L149 0.0146 0.026 0.563 0.574 -0.036 0.065
ma.L150 0.0009 0.029 0.030 0.976 -0.056 0.058
ma.L151 -0.0287 0.025 -1.129 0.259 -0.078 0.021
ma.L152 -0.0144 0.053 -0.273 0.785 -0.118 0.089
ma.L153 0.0131 0.028 0.462 0.644 -0.043 0.069
ma.L154 0.0070 0.041 0.169 0.866 -0.074 0.088
ma.L155 0.0006 0.026 0.024 0.981 -0.050 0.051
ma.L156 0.0475 0.025 1.889 0.059 -0.002 0.097
ma.L157 0.0497 0.078 0.634 0.526 -0.104 0.203
ma.L158 0.0720 0.065 1.108 0.268 -0.055 0.199
ma.L159 -0.0444 0.087 -0.513 0.608 -0.214 0.125
ma.L160 0.0156 0.108 0.145 0.885 -0.196 0.227
ma.L161 0.0133 0.047 0.283 0.777 -0.079 0.106
ma.L162 0.0202 0.039 0.521 0.602 -0.056 0.096
ma.L163 0.0363 0.030 1.227 0.220 -0.022 0.094
ma.L164 0.0022 0.051 0.044 0.965 -0.097 0.101
ma.L165 -0.0116 0.028 -0.421 0.674 -0.065 0.042
ma.L166 0.0042 0.035 0.120 0.905 -0.064 0.072
ma.L167 0.0089 0.028 0.313 0.754 -0.047 0.064
ma.L168 -0.0174 0.028 -0.626 0.532 -0.072 0.037
ma.L169 -0.0182 0.041 -0.441 0.659 -0.099 0.063
ma.L170 0.0088 0.034 0.259 0.796 -0.058 0.076
ma.L171 -0.0111 0.035 -0.315 0.753 -0.080 0.058
ma.L172 0.0155 0.031 0.498 0.619 -0.046 0.077
ma.L173 -0.0062 0.032 -0.193 0.847 -0.069 0.056
ma.L174 -0.0274 0.027 -1.021 0.307 -0.080 0.025
ma.L175 -0.0126 0.051 -0.249 0.804 -0.112 0.086
ma.L176 -0.0259 0.027 -0.976 0.329 -0.078 0.026
ma.L177 -0.0368 0.039 -0.955 0.339 -0.112 0.039
ma.L178 -0.0140 0.053 -0.264 0.792 -0.118 0.090
ma.L179 -0.0409 0.028 -1.460 0.144 -0.096 0.014
ma.L180 -0.0153 0.056 -0.272 0.786 -0.126 0.095
ma.L181 -0.0804 0.028 -2.833 0.005 -0.136 -0.025
ma.L182 0.0131 0.113 0.116 0.907 -0.208 0.234
ma.L183 0.0097 0.062 0.158 0.875 -0.111 0.130
ma.L184 0.0173 0.037 0.464 0.643 -0.056 0.091
ma.L185 -0.0476 0.029 -1.639 0.101 -0.104 0.009
ma.L186 -0.0155 0.085 -0.183 0.855 -0.181 0.150
ma.L187 -0.0580 0.032 -1.805 0.071 -0.121 0.005
ma.L188 -0.0107 0.077 -0.140 0.889 -0.161 0.139
ma.L189 -0.0099 0.031 -0.326 0.745 -0.070 0.050
ma.L190 0.0397 0.025 1.619 0.105 -0.008 0.088
ma.L191 -0.0183 0.066 -0.277 0.782 -0.148 0.111
ma.L192 -0.0326 0.052 -0.631 0.528 -0.134 0.069
ma.L193 -0.0206 0.056 -0.366 0.714 -0.131 0.090
ma.L194 -0.0174 0.030 -0.576 0.564 -0.076 0.042
ma.L195 0.0004 0.028 0.016 0.987 -0.054 0.055
ma.L196 -0.0003 0.025 -0.013 0.990 -0.050 0.049
ma.L197 -0.0274 0.025 -1.112 0.266 -0.076 0.021
ma.L198 -0.0263 0.052 -0.505 0.613 -0.128 0.076
ma.L199 -0.0157 0.039 -0.403 0.687 -0.092 0.061
ma.L200 0.0005 0.026 0.021 0.983 -0.050 0.051
ma.L201 -0.0504 0.027 -1.881 0.060 -0.103 0.002
ma.L202 -0.0217 0.082 -0.266 0.790 -0.182 0.138
ma.L203 0.0103 0.033 0.317 0.751 -0.054 0.074
ma.L204 0.0323 0.043 0.753 0.451 -0.052 0.116
ma.L205 -0.0635 0.049 -1.289 0.198 -0.160 0.033
ma.L206 0.0555 0.119 0.468 0.640 -0.177 0.288
ma.L207 -0.0185 0.112 -0.165 0.869 -0.238 0.201
ma.L208 -0.0088 0.047 -0.187 0.852 -0.101 0.084
ma.L209 -0.0700 0.034 -2.035 0.042 -0.137 -0.003
ma.L210 -0.0218 0.102 -0.213 0.832 -0.223 0.179
sigma2 1.5128 0.037 40.650 0.000 1.440 1.586
===================================================================================
Ljung-Box (L1) (Q): 0.01 Jarque-Bera (JB): 10205.17
Prob(Q): 0.93 Prob(JB): 0.00
Heteroskedasticity (H): 2.12 Skew: -0.96
Prob(H) (two-sided): 0.00 Kurtosis: 12.75
===================================================================================
Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
É importante notar que, com um parâmetro de $q = 210$, o treinamento do modelo levou 11 horas. Nesse caso, seria mais eficiente diferenciar os dados antes de inseri-los no modelo, o que reduziria o valor de $q$ e, consequentemente, diminuiria o tempo de treinamento.
8. Diagnóstico dos ResÃduos¶
A análise dos resÃduos é uma etapa crÃtica na modelagem de séries temporais, pois nos ajuda a avaliar a qualidade do modelo.
Os resÃduos ideais devem ser:
- Não correlacionados.
- resÃduos correlacionados indicam que o modelo não capturou toda a informação dos dados.
- Distribuição normal.
- ResÃduos com distribuição não normal indicam a presença de padrões que o modelo não conseguiu capturar.
- Variância constante.
- Se a variância dos resÃduos não for constante, temos não linearidade nos dados que precisam ser corrigidas.
- Média zero.
- Se a média dos resÃduos não for zero, o modelo precisa ser ajustado para capturar o viés (constante $c$).
- Não sazonais.
- Se houver padrões sazonais nos resÃduos, o modelo precisa ser ajustado com diferenciação sazonal.
Teste de Ljung-Box: Verifica se os resÃduos são aleatórios.
# ResÃduos do modelo ARIMA
resid_arima = resultado_arima.resid
# Plot dos resÃduos
plt.figure(figsize=(14, 7))
plt.plot(resid_arima)
plt.title("ResÃduos do Modelo ARIMA")
plt.xlabel("Data")
plt.ylabel("ResÃduos")
plt.show()
# Teste de Ljung-Box
lb_test = acorr_ljungbox(resid_arima, lags=[10], return_df=True)
lb_test
| lb_stat | lb_pvalue | |
|---|---|---|
| 10 | 1.094523 | 0.99974 |
Nossos resÃduos não estão correlacionados, seguem uma distribuição normal, apresentam variação constante e possuem média zero. Portanto, podemos considerá-los normais.
9. Previsão com o Modelo ARIMA¶
# Criando 30 dias de previsão
forecast_arima = resultado_arima.forecast(steps=30)
last_date = df.index[-1] # Última data da série original
forecast_index = pd.date_range(start=last_date, periods=31, freq='B')[1:] # Frequência 'B' para dias úteis
print(forecast_index)
DatetimeIndex(['2024-12-31', '2025-01-01', '2025-01-02', '2025-01-03',
'2025-01-06', '2025-01-07', '2025-01-08', '2025-01-09',
'2025-01-10', '2025-01-13', '2025-01-14', '2025-01-15',
'2025-01-16', '2025-01-17', '2025-01-20', '2025-01-21',
'2025-01-22', '2025-01-23', '2025-01-24', '2025-01-27',
'2025-01-28', '2025-01-29', '2025-01-30', '2025-01-31',
'2025-02-03', '2025-02-04', '2025-02-05', '2025-02-06',
'2025-02-07', '2025-02-10'],
dtype='datetime64[ns]', freq='B')
# Plot das previsões
plt.figure(figsize=(14, 7))
plt.plot(df["Último"].iloc[-350:], label="Dados Originais")
plt.plot(forecast_index, forecast_arima, color="red", label="Previsão ARIMA")
plt.title("Previsão com ARIMA")
plt.xlabel("Data")
plt.ylabel("Valor de Fechamento")
plt.legend()
plt.show()
10. Avaliação dos Modelos¶
Para realizar os testes, importei os dados dos próximos 30 meses do site Investing, garantindo que não houvesse viés nos resultados. Foi necessário manipular esses dados para que tivessem o mesmo formato da previsão, permitindo a execução correta dos testes de acuracidade.
# Importanto os dados de teste
df_test_data = pd.read_csv('data_teste.csv')
# Invertendo a ordem dos dados
df_test_data = df_test_data.iloc[::-1]
df_test_data.head()
| Data | Último | Abertura | Máxima | MÃnima | Vol. | Var% | |
|---|---|---|---|---|---|---|---|
| 21 | 02.01.2025 | 120.125 | 120.283 | 120.782 | 119.120 | 9,37M | -0,13% |
| 20 | 03.01.2025 | 118.533 | 120.125 | 120.356 | 118.404 | 9,80M | -1,33% |
| 19 | 06.01.2025 | 120.022 | 118.534 | 120.322 | 118.534 | 9,69M | 1,26% |
| 18 | 07.01.2025 | 121.163 | 120.022 | 121.713 | 120.022 | 11,12B | 0,95% |
| 17 | 08.01.2025 | 119.625 | 121.160 | 121.160 | 119.351 | 10,23B | -1,27% |
# Definindo conjunto de teste
test_data = df_test_data["Último"]
# Identifiando index da última data do nosso modelo
train_data = forecast_arima[-22:]
print(train_data)
2487 119.552630 2488 120.467149 2489 120.535934 2490 120.419304 2491 121.061430 2492 121.445930 2493 121.714878 2494 121.883767 2495 122.271074 2496 122.461738 2497 122.370495 2498 122.750636 2499 123.034948 2500 124.001928 2501 124.408968 2502 124.739857 2503 124.735286 2504 124.709540 2505 125.057245 2506 124.995565 2507 125.450082 2508 125.874183 Name: predicted_mean, dtype: float64
# Criando index com os dados de teste para que o mesmo possa ser concatenado com os dados de treino
test_data.index = range(2509, 2531)
print(test_data)
2509 120.125 2510 118.533 2511 120.022 2512 121.163 2513 119.625 2514 119.781 2515 118.856 2516 119.007 2517 119.299 2518 122.650 2519 121.234 2520 122.350 2521 122.855 2522 123.338 2523 122.972 2524 122.483 2525 122.447 2526 124.862 2527 124.056 2528 123.432 2529 126.913 2530 126.135 Name: Último, dtype: float64
Vamos utilizar o método de avaliação MAPE (Mean Absolute Percentage Error), pois é a métrica mais adequada para medir a acuracidade do modelo.
# Implementando MAPE
mape_arima = mean_absolute_percentage_error(test_data, train_data) * 100
print(f"MAPE ARIMA: {mape_arima}%")
MAPE ARIMA: 1.0724259267876892%
# Calculando a acuracidade
acuracidade = 100 - mape_arima
print(f'A acuracidade do modelo ARIMA é de {acuracidade:.2f}%')
A acuracidade do modelo ARIMA é de 98.93%
11. Conclusão¶
Com o modelo de previsão ARIMA, alcançamos uma acuracidade de 98,93%, indicando que o modelo teve um desempenho altamente preciso na previsão. No entanto, o principal desafio foi o tempo de treinamento, que se mostrou elevado. Apesar desse fator, o ARIMA demonstrou grande eficácia ao capturar e prever corretamente o comportamento do Ibovespa.
plt.figure(figsize=(14, 7))
# Plot dos dados originais
plt.plot(df["Último"].iloc[-350:], label="Dados Originais", color='gray')
# Plot dos dados de teste
plt.plot(forecast_index[-22:], test_data, color='gray', linestyle="--", label="Dados de Teste")
# Plot da previsão ARIMA
plt.plot(forecast_index, forecast_arima, color="blue", linewidth=2.5, label="Previsão ARIMA")
plt.title("Previsão com ARIMA")
plt.xlabel("Data")
plt.ylabel("Valor de Fechamento")
plt.legend()
plt.show()