Commit
·
76ff267
0
Parent(s):
Publicação inicial do dashboard de diagnóstico de séries temporais
Browse files- Dashboard_Diagnostico_ST.py +165 -0
- Dockerfile +23 -0
- Logo/MARCADOR.png +0 -0
- Logo/logo-est.png +0 -0
- Logo/logo_ppca.png +0 -0
- README.md +20 -0
- requirements.txt +9 -0
Dashboard_Diagnostico_ST.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 \
|
| 8 |
+
PYTHONDONTWRITEBYTECODE=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 |
+
|
Logo/MARCADOR.png
ADDED
|
Logo/logo-est.png
ADDED
|
Logo/logo_ppca.png
ADDED
|
README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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: "Dashboard com testes de diagnóstico em séries temporais financeiras"
|
| 19 |
+
license: mit
|
| 20 |
+
---
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit==1.32.0
|
| 2 |
+
pandas==2.2.2
|
| 3 |
+
numpy==1.26.4
|
| 4 |
+
plotly==5.18.0
|
| 5 |
+
yahooquery==2.3.7
|
| 6 |
+
scipy==1.13.1
|
| 7 |
+
pillow==9.4.0
|
| 8 |
+
statsmodels==0.14.1
|
| 9 |
+
|