Predição de ações na bolsa de valores com Python e Facebook Prophet
Usando Machine Learning para projetar valores futuros.
Em meio a pandemia da COVID 19 e a crise econômica muitas pessoas (inclusive eu) começaram a despertar o interesse por investir em renda variável na bolsa de valores, quantidade de pessoas essa que cresceu exponencialmente nos últimos 3 anos, e hoje em 2022 a B₃(BM&FBOVESPA) contabiliza a marca de 5 milhões de contas físicas cadastradas.
O que é bolsa de valores?
A bolsa de valores é um ambiente de negociação onde investidores podem **negociar **seus títulos emitidos por empresas, sejam elas com capitais públicos, mistos ou privados. Esse processo é intermediado com auxílio de correspondentes de negociações através de corretoras.
Operar nesse mercado sem conhecimento prévio pode ser bastante complexo, principalmente porque no papel de acionista é extremamente importante realizar o acompanhamento, fazer análises fundamentalistas sobre a empresa e o momento, para somente após isso decidir aonde colocará o seu suado dinheiro.
Cenário
Atualmente machine learning (e até mesmo redes neurais) tem entrado nos “trend topics” de investimento, pois investidores buscam automatizar não todo, mas parte do processo, pois é uma área que estuda a capacidade de aprendizado de um computador sobre conjuntos de dados e vem sendo muito utilizado para construção de robôs operacionais na área de investimentos.
Em minhas recentes pesquisas sobre o assunto encontrei alguns modelos de implementação utilizando a SciKit Learn (biblioteca para aprendizado de máquina em python) com métodos de regressão que buscam predizer valores de ações na bolsa.
O problema de todas as implementações e o maior erro em minha opinião é que, na verdade, não estão predizendo nada, deixe me explicar.
A maior parte dos exemplos faz a divisão dos dados de uma ação em treinamento e teste limitando a um simples aprendizado para “predições” de dados históricos. E quando falamos em predição de valores na bolsa, também temos em mente a projeção para datas futuras, pois queremos saber qual o valor que terá uma determinada ação em um determinado dia, o que não é abordado.
Em busca de uma solução para o problema encontrei o Prophet, uma biblioteca criada pelo Facebook com implementações em R e Python para fornecer previsões automatizadas, e é com ela que trabalharemos.
Pré processamento
Como requisito, precisaremos das bibliotecas listadas abaixo, as instruções de instalação das mesmas podem ser encontradas nos links.
Pandas: Manipulação de dataframes.
Pandas T.A: Biblioteca Pandas para análise técnica.
Yahoo Query: Busca de dados do mercado financeiro.
Plotly: Visualização de gráficos.
Prophet: Automação preditiva.
No primeiro passo, realizaremos a importação das bibliotecas necessárias.
from prophet import Prophet | |
from yahooquery import Ticker | |
import pandas as pd | |
from datetime import datetime | |
import pandas_ta as ta | |
import plotly.express as px | |
import plotly.graph_objs as go | |
from prophet.diagnostics import cross_validation | |
from prophet.diagnostics import performance_metrics |
Após então, utilizaremos o yahooquery que é uma biblioteca excelente para realizar extrações de dados do mercado financeiro do site Yahoo Finance e nos devolve informações extremamente úteis com rapidez e facilidade. Realizaremos uma busca dos dados de pregão de uma determinada ação na bolsa de valores e após isso o devido pré processamento.
# Name by which it is represented on the stock exchange. | |
symbol = "WEGE3.SA" | |
# Query stock in yahoo finance | |
stock = Ticker(symbol) | |
# Get 48 months data for enough sample terms | |
history = stock.history(period="48mo") | |
# Let only date as index | |
history.reset_index(level=["symbol"], inplace=True) | |
# Create date column | |
history['date'] = history.index | |
# Reindex data using a DatetimeIndex | |
history.set_index(pd.DatetimeIndex(history.index), inplace=True) | |
# select features that have interest to us | |
data = history[['date','adjclose']].copy(); | |
# use technical analyses using 21 one days and append to our dataset | |
data.ta.ema(close='adjclose', length=21, append=True) | |
# Drop empty values | |
data.dropna(inplace=True) |
**Linha 1: **A informação que está sendo atribuída a variável symbol é a identificação do ativo. Você irá achar esse código acessando o Yahoo Finance.
**Linha 8. **Realização da busca referente ao ativo em um período determinado, no caso escolhido foi 48 meses.
**Linha 10. **Esse dataframe possui dois índices, o primeiro é o código do ativo e o segundo a data do pregão, removeremos o primeiro.
**Linha 14. **Aqui copiamos o índex de data para uma coluna no nosso dataframe e por fim reindexamos novamente.
**Linha 20. **Cópia das features de interesse para um novo dataframe.
**Linha 23. **Existem diversas análises técnicas que podemos fazer para acompanhar o valor de uma ação, no nosso caso optaremos pela **Exponential Move Avarage (EMA) **utilizando 21 dias como amostra.
Exponential Move Avarage ou também conhecido por média móvel, é semelhante à média móvel simples (SMA), que mede a direção da tendência ao longo de um período. No entanto, enquanto a SMA simplesmente calcula uma média dos dados de preços, a EMA aplica mais peso aos dados mais atuais. Devido ao seu cálculo exclusivo, a EMA seguirá os preços mais de perto do que uma SMA.
Esse cálculo é feito pelo Pandas TA que é a biblioteca escolhida por conter a maioria das análises técnicas implementadas.
Após o pré processamento, este será o nosso dataframe com o qual trabalharemos.
Para obter uma melhor visualização, realizamos a plotagem da informação dos nossos dados em gráfico.
#Plot | |
fig = px.line(data, x='date', y='adjclose') | |
fig.update_xaxes( | |
rangeslider_visible=True, | |
rangeselector=dict( | |
buttons=list([ | |
dict(count=1, label="1m", step="month", stepmode="backward"), | |
dict(count=6, label="6m", step="month", stepmode="backward"), | |
dict(count=1, label="YTD", step="year", stepmode="todate"), | |
dict(count=1, label="1y", step="year", stepmode="backward"), | |
dict(step="all") | |
]) | |
) | |
) |
Aqui podemos observar mais nitidamente o desempenho do valor da ação temporalmente.
Processamento
Nessa segunda etapa prepararemos nosso modelo utilizando técnicas de Machine Learning para predizer os valores de ações com base em uma projeção de x dias.
# Add all dataset as training model | |
df_train = data[['date','adjclose','EMA_21']] | |
df_train = df_train.rename(columns={"date": "ds", "adjclose": "y"}) | |
# Fit model | |
m = Prophet(daily_seasonality=True) | |
# Train | |
m.fit(df_train) | |
# Get dates 30 days in the future | |
future = m.make_future_dataframe(periods=30) | |
# Drop weekends | |
future['day'] = future['ds'].dt.weekday | |
future = future[future['day'] <=4] | |
# Predict dates | |
forecast = m.predict(future) |
**Linha 2. **Seleciona features que vão compor o treinamento do modelo.
**Linha 7. **Instanciado o Prophet com parâmetro baseado em cálculo diário.
**Linha 14. **Treinamento no modelo com base no dataframe. Note que estamos usando todos os dados e não particionando-os, pois a ideia é que utilizemos todo como treinamento, e a projeção como teste.
Linha 16. Removemos os finais de semana da nossa projeção, pois não há pregões nesses dias e pode interferir no resultado.
Se acompanharmos no console enquanto o nosso modelo é treinado, podemos visualizar algumas informações úteis como o número de iterações realizadas no aprendizado.
**Linha 20. **Criação da projeção para os próximos 30 dias após a última data de pregão do dataset.
**Linha 23. **Nesse momento, o modelo irá tentar predizer os valores com base no que aprendeu previamente no seu treinamento e irá nos retornar o valor do ativo projetado para o tempo estipulado.
Para entendermos melhor, iremos plotar os resultados em um gráfico.
# Plot linear regression result | |
fig = go.Figure([ | |
go.Scatter(x=df_train['ds'], y=df_train['y'], name='Actual', mode='lines'), | |
go.Scatter(x=forecast['ds'], y=forecast['yhat'], name='Predicted', mode='lines'), | |
go.Scatter(x=forecast['ds'], y=df_train['EMA_21'], name='EMA', mode='lines') | |
]) | |
fig.update_xaxes( | |
rangeslider_visible=True, | |
rangeselector=dict( | |
buttons=list([ | |
dict(count=1, label="1d", step="day", stepmode="backward"), | |
dict(count=1, label="1m", step="month", stepmode="backward"), | |
dict(count=6, label="6m", step="month", stepmode="backward"), | |
dict(count=1, label="YTD", step="year", stepmode="todate"), | |
dict(count=1, label="1y", step="year", stepmode="backward"), | |
dict(step="all") | |
]) | |
) | |
) | |
fig.show() |
Após isso conduziremos uma breve análise.
Se olharmos atentamente ao gráfico veremos que nos próximos 30 dias não possuímos nenhuma comparação de valor atual, mas sim somente a linha de predição e é exatamente aqui que está contido a informação de valores futuros.
A variável forecast possui os dados da predição, utilizaremos ela para filtrar por registros com a data maiores que a atual a nossa projeção para um dataframe.
# Get all predictions | |
pred_df = forecast[forecast['ds'] > datetime.today()][['ds','yhat']] | |
# Reset Index | |
pred_df.reset_index(inplace=True) | |
# Drop index column | |
pred_df.drop(labels='index', axis=1,inplace=True) | |
# Rename columns | |
pred_df.rename(columns={'ds': 'date', 'yhat': 'predicted price'}, inplace=True) | |
# Show first elements | |
pred_df |
Aqui está o resultado:
Como removemos os finais de semana, sobraram na nossa projeção 20 dias úteis com o respectivo valor de fechamento.
Validação
Para avaliar o desempenho do nosso modelo é preciso elaborar algumas validações e destacar algumas métricas de assertividade.
A validação cruzada é uma técnica usada com frequência no machine learning para avaliar a variabilidade de um conjunto de dados e a confiabilidade dos modelos treinado com esses dados.
O Prophet inclui uma funcionalidade para validação cruzada de séries temporais para medir o erro de previsão usando dados históricos. Isso é feito selecionando pontos de corte no histórico, e para cada um deles ajustando o modelo usando dados somente até aquele ponto de corte. Assim, você pode entender se o modelo é suscetível a variações nos dados.
Executando o código abaixo obteremos o resultado da cross-validation e das métricas de desempenho do modelo.
# execute cross validation | |
# reference | |
# https://facebook.github.io/prophet/docs/diagnostics.html#:~:text=Cross%20validation,up%20to%20that%20cutoff%20point. | |
df_cv = cross_validation(m, initial='720 days', period='30 days', horizon = '365 days') | |
# visualize data | |
print(df_cv.head()) | |
# measure performance | |
df_p = performance_metrics(df_cv) | |
print(df_p.head()) |
E esses são os resultados de desempenho do nosso modelo.
As métricas geradas podem também ser visualizas em gráfico.
O código completo pode ser encontrado no meu Github:
GitHub — LucasDiogo96/Stock-Market-Prediction
O valor de mercado de uma ação na bolsa de valores também pode ser afetado por fatores externos ao de uma análise, como uma decisão política ou uma pandemia como foi o ano de 2020.
Da mesma forma, com o avanço constante na área de aprendizado de máquina, estamos munidos de cada vez mais ferramentas e técnicas para automatizar análises que nos ajudem a garantir um bom desempenho como acionistas.
Hora de ficar rico, até a próxima!
Referências:
O que é bolsa de valores e como funciona: https://www.btgpactualdigital.com/como-investir/artigos/investimentos/tudo-sobre-bolsa-de-valores
5 milhões de contas de investidores: https://www.b3.com.br/pt_br/noticias/5-milhoes-de-contas-de-investidores.htm
Exponential Moving Average: https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/ema
Cross-validation: evaluating estimator performance: https://scikit-learn.org/stable/modules/cross_validation.html
B3 atinge 5 milhões de contas de investidores em renda variável em janeiro: https://www.b3.com.br/pt_br/noticias/5-milhoes-de-contas-de-investidores.htm
Top comments (2)
Boa noite Lucas, tudo bem?
Eu estou apenas começando a aprender sobre data science e tenho grande interesse no mercado financeiro. Achei bem interessante seu artigo, mas me deixou com uma dúvida.
Pelo conhecimento que eu tenho, os dados normalmente são separados entre treinamento e teste para evitar o problema de overfitting. Ao treinar o modelo com todos os dados disponíveis, você não teme que os modelos fiquem "muito bons em prever o passado" e, portanto, sem confiabilidade para prever o futuro?
Bom dia Felipe, tudo ótimo e com você?
Obrigado pela contribuição!
Normalmente para predições é usado uma estratégia 80/20 o que pode variar com o tamanho do dataset, e com certeza, utilizar todo o dataset pode fazer com que os resultados sofram de overfitting.
Por trabalharmos com sazonalidade diária, também descontantando o finais de semana temos uma amostragem muito pequena, o que pode fazer com que sofra underfitting.
Um exemplo é se pegarmos a stock NU (Nubank) que fez IPO em dezembro, não teremos dados o suficiente para trabalharmos em cima.
Com base nisso, optei por nesse pequeno exemplo utilizar o meu dataframe de datas futuras como teste, assim como na documentação.
facebook.github.io/prophet/docs/qu...
Abaixo colei um pequeno trecho onde é usado as datas futuras como entrada para o ajuste.
"You can get a suitable dataframe that extends into the future a specified number of days using the helper method Prophet.make_future_dataframe. By default it will also include the dates from the history, so we will see the model fit as well."
O prophet é uma biblioteca especificamente construída para trabalhar com séries temporais e utiliza o sci kit learn por baixo dos panos e tem diversas funções que nos ajudam a trabalhar com esse categoria de dados.