Prevendo valores de ações com LSTM

Note: an english version of this article is available at Predicting stock prices with LSTM

Este artigo tem por objetivo mostrar a utilização de Redes Neurais, mais especificamente uma rede LSTM, para prever o comportamento de uma série temporal. Vamos adotar o clássico problema de prever o valor de uma ação do mercado de valores. Os dados e o código completo e detalhado estão disponibilizados neste repositório do GitHub.

Embora este problema não seja novo, ele continua sem uma solução definitiva. Isto porque o valor de uma ação é determinada por diversos fatores, muitos além de seu histórico de preços, o que torna muito difícil prever seu comportamento, a não ser que você seja um macaco jogando dardos.

Resumo

Primeiro começarei com uma breve visualização dos dados. Depois, vou abordar brevemente a dificuldade de predizer o comportamento de uma ação mostrando a limitação de algoritmos como o da média móvel. Em seguida, introduzirei o conceito de uma Rede Recorrente e uma LSTM. Em sequência, utilizarei uma rede LSTM para a previsão do valor de uma única ação. Por último, utilizarei uma LSTM para fazer a previsão de 4 ações diferentes ao mesmo tempo para verificar se a previsão melhora de forma geral.

Visualizando os dados

O dataset foi baixado do Yahoo Finance em CSV. Ele é composto pelo histórico do valor das ações de quatro empresas durante o período de 08/01/2010 até 07/01/2019. Vamos chamá-las de empresa A, B, C e D.

O primeiro passo é abrir os CSVs com o Pandas. Em uma primeira visão dos dados, temos:

df_A = pd.read_csv(‘data/company_A.csv’)
df_A[‘Date’] = pd.to_datetime(df_A[‘Date’])
df_A.tail()
plt.figure(figsize = (15,10))
plt.plot(df_A['Date'], df_A['Close'], label='Company A')
plt.plot(df_B['Date'], df_B['Close'], label='Company B')
plt.plot(df_C['Date'], df_C['Close'], label='Company C')
plt.plot(df_D['Date'], df_D['Close'], label='Company D')
plt.legend(loc='best')
plt.show()
Gráfico com todos os valores de fechamento das quatro empresas

Média Móvel

Um algoritmo clássico para este tipo de problema é o da Média Móvel. Ele consiste em utilizar a média de m dias observados para prever o próximo dia. Vamos utilizar esta técnica na empresa A para um m de 10 e de 20 dias.

df['MA_window_10'] = df['Close'].rolling(10).mean().shift() #shift so the day we want to predict won't be used
df['MA_window_20'] = df['Close'].rolling(20).mean().shift()
Valor previsto dia após dia do valor de fechamento da empresa A com usando a Média Móvel

Quando tentamos prever o comportamento do preço de fechamento de 10 em 10 dias com a média móvel, o resultado é:

Valor de fechamento previsto a cada 10 dias da empresa A usando a Média Móvel

Note que cada reta vermelha no gráfico representa uma previsão de 10 dias, baseado nos 10 dias anteriores. Por isso elas são descontínuas.

Quando utilizamos um algoritmo um pouco mais elaborado, a Média Móvel Exponencial (EMA), obtemos o seguinte resultado:

Valor previsto dia após dia do valor de fechamento da empresa A usando a Média Móvel Exponencial

Comparando os dois métodos, temos:

Comparação do valor previsto dia após dia do valor de fechamento da empresa A usando a Média Móvel e Média Móvel Exponencial

Este tipo de abordagem é muito simplista. Isto porque o verdadeiro objetivo é prever n dias a frente para ver qual será o comportamento da ação. E ambos algoritmos falham nessa função.

Rede Neural Recorrente (RNN)

Antes de avançar para LSTM, primeiro vamos introduzir o conceito de Redes Recorrentes. Elas são redes utilizadas para reconhecer padrões quando os resultados do passado influenciam no resultado atual. Um exemplo disso são as séries temporais, em que a ordem dos dados é muito importante.

Nesta arquitetura, um neurônio tem como entrada seu estado anterior, além das entradas da camada anterior. A imagem abaixo ilustra esta nova modelagem.

Arquitetura de uma Rede Recorrente

Observe que H representa o estado. Assim, no estado H_1, o neurônio recebe como parâmetro de entrada X_1 e, além disso, seu estado anterior H_0. O principal problema desta arquitetura é que os estados mais antigos são esquecidos muito rapidamente. Ou seja, para sequências em que precisamos lembrar além de um passado imediato, as redes RNNs são limitadas.

Rede LSTM

Uma rede LSTM tem origem em uma RNN (Rede Neural Recorrente). Mas ela resolve o problema de memória mudando sua arquitetura.

Arquitetura de um neurônio da rede LSTM

Nesta nova arquitetura, cada neurônio possui 3 gates, cada um com uma função diferente. São eles:

  • Input Gate
  • Output Gate
  • Forget Gate

Agora, um neurônio LSTM recebe entradas de seu estado anterior, assim como ocorria na Rede Recorrente:

Neurônio LSTM passando parâmetros para si mesmo do seu estado H_t-1 para H_t, e de H_t para H_t+1

LSTM para uma empresa

Vamos agora utilizar uma Rede LSTM para prever o comportamento da empresa A sozinha.

Primeiro, definimos alguns parâmetros. Queremos prever até n dias a frente (foward_days) observando m dias passados (look_back). Ou seja, para uma entrada de m dias passados, a saída da rede será os próximos n dias. Vamos testar com k períodos (num_periods), onde cada período é um conjunto de n dias previstos.

look_back = 40
forward_days = 10
num_periods = 20

Depois, abrimos o CSV com o Pandas e mantemos apenas as colunas que utilizaremos, que são a data e o preço de fechamento. O gráfico inicial do valor de fechamento da ação A é:

plt.figure(figsize = (15,10))
plt.plot(df)
plt.show()
Preço de fechamento da empresa A

Em seguida temos que normalizar as entradas, separar os dados em treino/validação e teste e preparar os dados no formato certo para o modelo. Todo este processo está detalhado no meu GitHub.

Agora construímos e treinamos o modelo.

NUM_NEURONS_FirstLayer = 128
NUM_NEURONS_SecondLayer = 64
EPOCHS = 220
#Build the model
model = Sequential()
model.add(LSTM(NUM_NEURONS_FirstLayer,input_shape=(look_back,1), return_sequences=True))
model.add(LSTM(NUM_NEURONS_SecondLayer,input_shape=(NUM_NEURONS_FirstLayer,1)))
model.add(Dense(foward_days))
model.compile(loss='mean_squared_error', optimizer='adam')
history = model.fit(X_train,y_train,epochs=EPOCHS,validation_data=(X_validate,y_validate),shuffle=True,batch_size=2, verbose=2)

Obtemos o seguinte resultado:

Previsão do modelo da empresa A para todos os dados

Observando a parte de teste mais de perto:

Previsão do modelo da empresa A para os dados de teste

Repare que cada linha vermelha no gráfico representa uma previsão de 10 dias (forward_days), baseado nos 40 dias anteriores (look_back), e há 20 linhas vermelhas, pois escolhemos prever 20 períodos (num_periods). Por isso elas são descontínuas.

Repetindo o mesmo processo para todas as empresas, o melhor resultado foi o da empresa C:

Previsão do modelo da empresa C para os dados de teste

E mesmo assim, o resultado ainda deixa muito a desejar. Os motivos que podem ter causado este resultado são vários. Dentre eles, temos:

  • Apenas o histórico de preços não é suficiente para prever o comportamento de uma ação
  • O modelo poderia ser melhorado

LSTM para quatro empresa

Vamos agora utilizar uma Rede LSTM para prever o comportamento das empresas A, B, C e D e comparar com os resultados de cada LSTM das empresas individualmente. A idéia é verificar se ter os dados de um conjunto de empresas melhora a previsão individual de cada uma.

Um ponto importante é verificar se as datas de fechamento dos CSVs das empresas batem. Foi feito um pré-tratamento para garantir que todas as empresas possuíssem as mesmas datas.

Inicialmente, temos os seguintes dados:

Preço de fechamento das quatro empresas

Depois, após normalizar os dados e deixá-los na formatação certa de entrada e saída para a rede, treinei o modelo:

NUM_NEURONS_FirstLayer = 100
NUM_NEURONS_SecondLayer = 50
EPOCHS = 200
#Build the model
model = Sequential()
model.add(LSTM(NUM_NEURONS_FirstLayer,input_shape=(look_back,num_companies), return_sequences=True))
model.add(LSTM(NUM_NEURONS_SecondLayer,input_shape=(NUM_NEURONS_FirstLayer,1)))
model.add(Dense(foward_days * num_companies))
model.compile(loss='mean_squared_error', optimizer='adam')
history = model.fit(X_train,y_train,epochs=EPOCHS,validation_data=(X_validate,y_validate),shuffle=True,batch_size=1, verbose=2)

Os resultados obtidos foram:

Resultado do modelo LSTM para as 4 empresas

Olhando apenas para o teste, temos:

Previsão dos dados de teste do modelo LSTM para as 4 empresas

Vamos comparar individualmente os resultados previstos dos dados de teste da LSTM de cada empresa com o modelo de LSTM para todas as 4. Com as fotos da esquerda sendo da LSTM individual, e da direita da LSTM das 4 empresas, temos:

Empresa A

Empresa B

Empresa C

Empresa D

Conclusão

Usar apenas uma LSTM não é suficiente para prever o comportamento de uma ação no mercado. Quando usamos apenas o histórico de preço de uma empresa, a previsão fica longe da realidade. Quando usamos o preço de diversas empresas simultaneamente, a previsão se torna mais imprecisa ainda.

Referências

https://www.datacamp.com/community/tutorials/lstm-python-stock-market
http://colah.github.io/posts/2015-08-Understanding-LSTMs/
https://towardsdatascience.com/train-validation-and-test-sets-72cb40cba9e7
https://machinelearningmastery.com/diagnose-overfitting-underfitting-lstm-models/
https://machinelearningmastery.com/reshape-input-data-long-short-term-memory-networks-keras/