joaogabrielsouza commited on
Commit
4628e83
·
1 Parent(s): 9a85282

🚀 Versão inicial do dashboard interativo com ARIMA e SARIMA + Bootstrap

Browse files
Files changed (4) hide show
  1. Dashboard_ARIMA_SARIMA.py +149 -0
  2. Dashboard_Diagnostico_ST.py +0 -165
  3. Dockerfile +6 -6
  4. README.md +11 -8
Dashboard_ARIMA_SARIMA.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ # ![MARCADOR.png](attachment:1d2ba8b3-2364-4376-8dfb-789c18bb0d24.png)
5
+
6
+ # # Dashboard - Séries Temporais Financeiras (Previsão)
7
+
8
+ # In[5]:
9
+
10
+
11
+ import streamlit as st
12
+ import pandas as pd
13
+ import numpy as np
14
+ import matplotlib.pyplot as plt
15
+ import seaborn as sns
16
+ import plotly.graph_objects as go
17
+
18
+ from yahooquery import Ticker
19
+ from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
20
+ from pmdarima import auto_arima
21
+ from statsmodels.tsa.statespace.sarimax import SARIMAX
22
+ from statsmodels.stats.diagnostic import acorr_ljungbox
23
+ import io
24
+ from PIL import Image
25
+
26
+ import warnings
27
+
28
+ warnings.filterwarnings("ignore")
29
+
30
+ import subprocess
31
+
32
+
33
+ # ## Dashboard
34
+
35
+ # In[12]:
36
+
37
+
38
+ # Função com cache
39
+ @st.cache_data(ttl=3600)
40
+ def carregar_dados(ativo):
41
+ ticker = Ticker(ativo)
42
+ df_raw = ticker.history(start='2012-01-01', interval='1d').reset_index()
43
+ df_raw['date'] = pd.to_datetime(df_raw['date'], utc=True, errors='coerce').dt.tz_convert(None)
44
+ df_prices = df_raw[df_raw['adjclose'].notna()][['date', 'adjclose']].rename(columns={'date': 'Data', 'adjclose': 'Preco'})
45
+ df_prices.set_index('Data', inplace=True)
46
+ df_prices = df_prices[~df_prices.index.duplicated()]
47
+ return df_prices
48
+
49
+ # Função para diagnosticar FAC/FACP
50
+ def plot_diagnostico(serie):
51
+ fig, axs = plt.subplots(2, 1, figsize=(10, 6))
52
+ plot_acf(serie.dropna(), lags=36, ax=axs[0])
53
+ axs[0].set_title("FAC")
54
+ plot_pacf(serie.dropna(), lags=36, ax=axs[1])
55
+ axs[1].set_title("FACP")
56
+ fig.tight_layout()
57
+ return fig
58
+
59
+ # Função para simulação bootstrap
60
+ def bootstrap_previsao(modelo_base, treino, steps, order, seasonal_order, n_sim=10):
61
+ resid = modelo_base.resid.dropna()
62
+ sim_matrix = []
63
+ for _ in range(n_sim):
64
+ shocks = np.random.choice(resid, size=steps, replace=True)
65
+ history = list(treino)
66
+ sim = []
67
+ for i in range(steps):
68
+ mod = SARIMAX(history, order=order, seasonal_order=seasonal_order, trend='c').filter(modelo_base.params)
69
+ next_val = mod.forecast(steps=1)[0]
70
+ val = next_val + shocks[i]
71
+ sim.append(val)
72
+ history.append(val)
73
+ sim_matrix.append(sim)
74
+ sim_df = pd.DataFrame(sim_matrix).T
75
+ return sim_df
76
+
77
+ # Configuração da página
78
+ st.set_page_config(page_title="Previsão SARIMA com Bootstrap", layout="wide")
79
+ st.title("📈 Dashboard de Séries Temporais com SARIMA + Bootstrap")
80
+
81
+ # Sidebar
82
+ st.sidebar.header("Configurações do Modelo")
83
+ ativo = st.sidebar.selectbox("Escolha o ativo:", ["PETR4.SA", "NG=F", "WEAT", "AMZN", "WMT"])
84
+ auto = st.sidebar.radio("Seleção do modelo:", ["AutoARIMA", "Manual"])
85
+
86
+ # Carregar série
87
+ df_diaria = carregar_dados(ativo)
88
+ serie = df_diaria['Preco'].resample('M').mean().dropna()
89
+
90
+ st.subheader("📊 Série histórica (mensal)")
91
+ st.line_chart(serie)
92
+
93
+ # Diagnóstico FAC/FACP
94
+ if st.button("🔍 Ver Diagnóstico FAC/FACP"):
95
+ st.subheader("FAC e FACP da série diferenciada")
96
+ fig_diag = plot_diagnostico(serie.diff().dropna())
97
+ st.pyplot(fig_diag)
98
+
99
+ # Definição do modelo
100
+ if auto == "AutoARIMA":
101
+ model_arima = auto_arima(serie, seasonal=True, m=12, trace=False, suppress_warnings=True)
102
+ order = model_arima.order
103
+ seasonal_order = model_arima.seasonal_order[:3] + (12,)
104
+ else:
105
+ order = tuple(map(int, st.sidebar.text_input("Order (p,d,q):", "1,1,1").split(",")))
106
+ seasonal_order = tuple(map(int, st.sidebar.text_input("Seasonal Order (P,D,Q,s):", "1,1,1,12").split(",")))
107
+
108
+ modelo = SARIMAX(serie, order=order, seasonal_order=seasonal_order, trend='c').fit(disp=False)
109
+
110
+ # Ljung-Box
111
+ if st.button("📈 Testar Ljung-Box nos resíduos"):
112
+ st.subheader("📈 Teste de Ljung-Box nos resíduos")
113
+ alpha = st.slider("Escolha o nível de significância (α)", 0.01, 0.10, 0.05, step=0.01)
114
+ lb_test = acorr_ljungbox(modelo.resid.dropna(), lags=[12], return_df=True)
115
+ st.dataframe(lb_test)
116
+ pval = lb_test['lb_pvalue'].values[0]
117
+ if pval < alpha:
118
+ st.error(f"p-valor = {pval:.4f} < {alpha:.2f} → Rejeitamos H₀: resíduos não são ruído branco.")
119
+ else:
120
+ st.success(f"p-valor = {pval:.4f} ≥ {alpha:.2f} → Não rejeitamos H₀: resíduos são ruído branco.")
121
+
122
+ # Parâmetros de previsão
123
+ n_passos = st.sidebar.slider("Horizonte de previsão (meses)", 6, 36, 12)
124
+ n_sim = st.sidebar.slider("Nº simulações bootstrap", 5, 50, 10)
125
+
126
+ # Previsão e gráfico final
127
+ if st.button("📈 Gerar Previsão com IC (Bootstrap)"):
128
+ forecast_index = pd.date_range(serie.index[-1] + pd.DateOffset(months=1), periods=n_passos, freq="M")
129
+ sim_df = bootstrap_previsao(modelo, list(serie), n_passos, order, seasonal_order, n_sim=n_sim)
130
+ ci_lower = sim_df.quantile(0.025, axis=1)
131
+ ci_upper = sim_df.quantile(0.975, axis=1)
132
+ median = sim_df.median(axis=1)
133
+
134
+ fig = go.Figure()
135
+ fig.add_trace(go.Scatter(x=serie.index, y=serie, name="Histórico", line=dict(color="blue")))
136
+ fig.add_trace(go.Scatter(x=forecast_index, y=median, name="Previsão", line=dict(color="red")))
137
+ fig.add_trace(go.Scatter(
138
+ x=list(forecast_index) + list(forecast_index[::-1]),
139
+ y=list(ci_lower) + list(ci_upper[::-1]),
140
+ fill='toself',
141
+ fillcolor='rgba(255,182,193,0.3)',
142
+ line=dict(color='rgba(255,255,255,0)'),
143
+ name='IC 95%',
144
+ hoverinfo='skip'
145
+ ))
146
+ fig.update_layout(title=f"Previsão SARIMA + Bootstrap ({ativo})",
147
+ xaxis_title="Data", yaxis_title="Preço Ajustado",
148
+ template="plotly_white", height=500)
149
+ st.plotly_chart(fig, use_container_width=True)
Dashboard_Diagnostico_ST.py DELETED
@@ -1,165 +0,0 @@
1
- #!/usr/bin/env python
2
- # coding: utf-8
3
-
4
- import streamlit as st
5
- import pandas as pd
6
- import numpy as np
7
- from yahooquery import Ticker
8
- import plotly.graph_objects as go
9
- from statsmodels.tsa.stattools import adfuller, kpss, acf, pacf
10
- from statsmodels.stats.diagnostic import acorr_ljungbox, het_arch
11
- from statsmodels.stats.stattools import jarque_bera
12
- from PIL import Image
13
-
14
- # Configuração da página
15
- st.set_page_config(
16
- page_title="Diagnóstico de Séries Temporais Financeiras",
17
- layout="wide",
18
- initial_sidebar_state="expanded"
19
- )
20
-
21
- # Estilo do slider
22
- st.markdown("""
23
- <style>
24
- .stSlider > div > div > div > div > div > div {
25
- background-color: #4CAF50 !important;
26
- }
27
- </style>
28
- """, unsafe_allow_html=True)
29
-
30
- # Logo
31
- logo_unb = Image.open("Logo/MARCADOR.png")
32
- col1, col2, col3 = st.columns([1, 6, 1])
33
- with col1:
34
- st.image(logo_unb, use_column_width=True)
35
- with col2:
36
- st.markdown("<h1 style='text-align: center; color: #003366;'>Diagnóstico de Séries Temporais Financeiras</h1>", unsafe_allow_html=True)
37
- st.markdown("<h3 style='text-align: center; color: #003366;'>Professor João Gabriel de Moraes Souza</h3>", unsafe_allow_html=True)
38
- with col3:
39
- st.image(logo_unb, use_column_width=True)
40
-
41
- # Interface
42
- ativos = ['ITUB4.SA', 'BBAS3.SA', 'BBDC4.SA', 'PETR3.SA', 'CSNA3.SA']
43
- ativo = st.selectbox("Escolha um ativo:", ativos)
44
- tipo_serie = st.radio("Tipo de série:", ["Preços", "Retornos"])
45
- alpha = st.slider("Nível de significância (α):", min_value=0.01, max_value=0.10, value=0.01, step=0.01)
46
- diagnostico = st.selectbox("Escolha o tipo de diagnóstico:", [
47
- "Estacionariedade",
48
- "Ergodicidade",
49
- "Autocorrelação",
50
- "Heterocedasticidade",
51
- "Normalidade"
52
- ])
53
-
54
- # Cache para download de dados
55
- @st.cache_data(ttl=3600)
56
- def carregar_dados(ativo):
57
- ticker = Ticker(ativo)
58
- df_raw = ticker.history(start='2012-01-01', interval='1d').reset_index()
59
- df_raw['date'] = pd.to_datetime(df_raw['date'], utc=True, errors='coerce').dt.tz_convert(None)
60
- df_prices = df_raw[df_raw['adjclose'].notna()][['date', 'adjclose']].rename(columns={'date': 'Data', 'adjclose': 'Preco'})
61
- df_prices.set_index('Data', inplace=True)
62
- df_prices = df_prices[~df_prices.index.duplicated()]
63
- return df_prices
64
-
65
- # Carregar dados cacheados
66
- df_prices = carregar_dados(ativo)
67
-
68
- # Calcular série conforme tipo
69
- if tipo_serie == "Retornos":
70
- serie = np.log(df_prices / df_prices.shift(1)).dropna()
71
- else:
72
- serie = df_prices.copy()
73
-
74
- # Visualização da série
75
- st.markdown("### Dados Históricos")
76
- st.line_chart(serie)
77
-
78
- # Diagnóstico
79
- st.markdown(f"### Diagnóstico: {diagnostico}")
80
-
81
- if diagnostico == "Estacionariedade":
82
- try:
83
- adf_stat, adf_p, *_ = adfuller(serie.values.flatten())
84
- if adf_p < alpha:
85
- st.success(f"ADF: p-valor = {adf_p:.4f} < α → estacionária ao nível de {alpha*100:.0f}%")
86
- else:
87
- st.warning(f"ADF: p-valor = {adf_p:.4f} ≥ α → não estacionária ao nível de {alpha*100:.0f}%")
88
- except Exception as e:
89
- st.error(f"Erro no teste ADF: {e}")
90
-
91
- try:
92
- kpss_stat, kpss_p, *_ = kpss(serie.values.flatten(), nlags='auto')
93
- if kpss_p < alpha:
94
- st.warning(f"KPSS: p-valor = {kpss_p:.4f} < α → rejeita estacionariedade ao nível de {alpha*100:.0f}%")
95
- else:
96
- st.success(f"KPSS: p-valor = {kpss_p:.4f} ≥ α → estacionariedade não rejeitada")
97
- except Exception as e:
98
- st.error(f"Erro no teste KPSS: {e}")
99
-
100
- elif diagnostico == "Ergodicidade":
101
- expanding_mean = serie.expanding().mean()
102
- fig = go.Figure([go.Scatter(x=expanding_mean.index, y=expanding_mean.values.flatten())])
103
- fig.update_layout(title="Média Acumulada (Ergodicidade)", xaxis_title="Data", yaxis_title="Média")
104
- st.plotly_chart(fig)
105
-
106
- elif diagnostico == "Autocorrelação":
107
- try:
108
- serie_valida = serie.values.flatten()
109
- serie_valida = serie_valida[~np.isnan(serie_valida)]
110
- serie_valida = serie_valida[~np.isinf(serie_valida)]
111
-
112
- if len(serie_valida) > 30 and np.std(serie_valida) > 0:
113
- lb_p = acorr_ljungbox(serie_valida, lags=[10], return_df=True)['lb_pvalue'].iloc[0]
114
- if lb_p < alpha:
115
- st.warning(f"Ljung-Box: p-valor = {lb_p:.4f} < α → autocorrelação presente")
116
- else:
117
- st.success(f"Ljung-Box: p-valor = {lb_p:.4f} ≥ α → sem evidência de autocorrelação")
118
- else:
119
- st.warning("Série inválida para o teste de Ljung-Box: tamanho insuficiente ou variância nula.")
120
- except Exception as e:
121
- st.error(f"Erro no teste Ljung-Box: {e}")
122
-
123
- try:
124
- acf_vals = acf(serie_valida, nlags=30)
125
- pacf_vals = pacf(serie_valida, nlags=30)
126
- lags = list(range(len(acf_vals)))
127
-
128
- fig_acf = go.Figure([go.Bar(x=lags, y=acf_vals)])
129
- fig_acf.update_layout(title="Autocorrelação (ACF)", xaxis_title="Lag", yaxis_title="ACF")
130
- st.plotly_chart(fig_acf)
131
-
132
- fig_pacf = go.Figure([go.Bar(x=lags, y=pacf_vals)])
133
- fig_pacf.update_layout(title="Autocorrelação Parcial (PACF)", xaxis_title="Lag", yaxis_title="PACF")
134
- st.plotly_chart(fig_pacf)
135
-
136
- except Exception as e:
137
- st.error(f"Erro nos gráficos ACF/PACF: {e}")
138
-
139
- elif diagnostico == "Heterocedasticidade":
140
- try:
141
- serie_valida = serie.values.flatten()
142
- serie_valida = serie_valida[~np.isnan(serie_valida)]
143
- serie_valida = serie_valida[~np.isinf(serie_valida)]
144
-
145
- if len(serie_valida) > 30 and np.std(serie_valida) > 0:
146
- p_arch = het_arch(serie_valida)[1]
147
- if p_arch < alpha:
148
- st.warning(f"ARCH: p-valor = {p_arch:.4f} < α → heterocedasticidade condicional presente")
149
- else:
150
- st.success(f"ARCH: p-valor = {p_arch:.4f} ≥ α → sem evidência de heterocedasticidade")
151
- else:
152
- st.warning("Série inválida para o teste ARCH: tamanho insuficiente ou variância nula.")
153
- except Exception as e:
154
- st.error(f"Erro no teste ARCH: {e}")
155
-
156
- elif diagnostico == "Normalidade":
157
- try:
158
- jb_stat, jb_p, *_ = jarque_bera(serie.values.flatten())
159
- if jb_p < alpha:
160
- st.warning(f"Jarque-Bera: p-valor = {jb_p:.4f} < α → distribuição não normal")
161
- else:
162
- st.success(f"Jarque-Bera: p-valor = {jb_p:.4f} ≥ α → distribuição aproximadamente normal")
163
- except Exception as e:
164
- st.error(f"Erro no teste Jarque-Bera: {e}")
165
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile CHANGED
@@ -1,7 +1,6 @@
1
- # Dockerfile para Diagnóstico de Séries Temporais com Streamlit
2
 
3
  # syntax=docker/dockerfile:1
4
-
5
  FROM python:3.10-slim
6
 
7
  ENV PYTHONUNBUFFERED=1 \
@@ -9,15 +8,16 @@ ENV PYTHONUNBUFFERED=1 \
9
 
10
  WORKDIR /app
11
 
12
- # Copiar e instalar dependências
13
  COPY requirements.txt .
14
  RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
15
 
16
- # Copiar código e recursos
17
  COPY Logo ./Logo
18
- COPY Dashboard_Diagnostico_ST.py .
19
 
 
20
  EXPOSE 8501
21
 
22
- CMD ["streamlit", "run", "Dashboard_Diagnostico_ST.py", "--server.port=8501", "--server.enableCORS=false", "--server.address=0.0.0.0"]
23
 
 
1
+ # Dockerfile para Dashboard ARIMA + SARIMA com Bootstrap (Streamlit)
2
 
3
  # syntax=docker/dockerfile:1
 
4
  FROM python:3.10-slim
5
 
6
  ENV PYTHONUNBUFFERED=1 \
 
8
 
9
  WORKDIR /app
10
 
11
+ # Instalar dependências
12
  COPY requirements.txt .
13
  RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
14
 
15
+ # Copiar arquivos da aplicação
16
  COPY Logo ./Logo
17
+ COPY Dashboard_ARIMA_SARIMA.py .
18
 
19
+ # Porta padrão do Streamlit
20
  EXPOSE 8501
21
 
22
+ CMD ["streamlit", "run", "Dashboard_ARIMA_SARIMA.py", "--server.port=8501", "--server.enableCORS=false", "--server.address=0.0.0.0"]
23
 
README.md CHANGED
@@ -1,20 +1,23 @@
1
  ---
2
- title: "Diagnóstico de Séries Temporais"
3
  emoji: "📊"
4
- colorFrom: "indigo"
5
- colorTo: "green"
6
  sdk: "docker"
7
  app_file: "Dockerfile"
8
  app_port: 8501
9
  tags:
10
  - streamlit
11
  - séries temporais
12
- - econometria
13
- - testes estatísticos
14
- - machine-learning
 
 
15
  - finanças
16
  - UnB
17
- pinned: false
18
- short_description: "Diagnóstico de séries temporais financeiras"
19
  license: mit
20
  ---
 
 
1
  ---
2
+ title: "📈 Dashboard ARIMA + SARIMA + Bootstrap"
3
  emoji: "📊"
4
+ colorFrom: "red"
5
+ colorTo: "black"
6
  sdk: "docker"
7
  app_file: "Dockerfile"
8
  app_port: 8501
9
  tags:
10
  - streamlit
11
  - séries temporais
12
+ - previsão
13
+ - ARIMA
14
+ - SARIMA
15
+ - bootstrap
16
+ - estatística
17
  - finanças
18
  - UnB
19
+ pinned: true
20
+ short_description: "Previsão com ARIMA e SARIMA"
21
  license: mit
22
  ---
23
+