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