Detectando histórico de movimentos no preço de uma ação com Python
Introdução
Grandes variações no preço de uma ação podem indicar o início de um movimento maior com relação a sua cotação. O propósito desse exercício é desenvolver uma maneira rápida de verificar qualquer tipo de variação desejada no preço de negociação uma empresa em um único dia nos dados históricos de mercado.
Desenvolvimento
Bibliotecas
Começando pelas bibliotecas, usamos sys
para executar o programa por parâmetros (uma vez que a ideia é agilizar o processo), requests
para obter os dados da API da AlphaVantage, pandas
para manipular os dados e matplotlib
para plotar o resultado final.
import sys
import requests
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.lines as mlines
Recebendo argumentos
Definimos a função main
da sequinte maneira:
def main():
if __name__ == "__main__":
main()
Logo após, define-se a função get_arguments()
cujo único propósito é receber os argumentos recebidos por parâmetro e devolver para a aplicação.
def get_arguments():
# Recebe os argumentos do vetor de argumentos passado
symbol = sys.argv[1]
operator = sys.argv[2]
reference = float(sys.argv[3])
# Retorna os parâmetros
return symbol, operator, reference
- Symbol é o ticker utilizado para pesquisa (FB, AMD, AMZN…)
- Operator é a referência de pesquisa (+, - ou =)
- Reference: é o número de ancoragem para a pesquisa (10, -5, 0…)
Dessa forma, o programa poderia ser executado da sequinte maneira:
main.py FB + 10
Essa chamada retornaria todas as vezes que a ação do Facebook teve uma variação maior que 10% no dia.
main.py AMZN - -20
Essa chamada retornaria todas as vezes que a ação da Amazon teve uma variação menor que 20% no dia.
Feita a função, podemos apenas retornar seu resultado na main()
def main():
# Recebe os argumentos passados
symbol, operator, reference = get_arguments()
Recebendo os dados
Para esse exemplo, utilizamos a API gratuita da AlphaVantage. Para utilizá-la basta entrar no site e requisitar uma chave que será usada como validação para suas consultas, de graça (na versão standart).
Para receber os dados, definimos a função get_cotations_data()
que tem como objetivo fazer a chamada para a API e retornar um dataframe com os dados tratados para a aplicação.
Primeiramente, precisamos montar o link de acesso com o seguinte código
# Monta a URL da requisição
url = 'https://www.alphavantage.co/query?function=' + function + '&symbol=' + \
symbol + '&outputsize=' + output_size + '&apikey=' + api_key
Note que essa requisição necessita de algumas variaveis ainda não definidas como function
, output_size
e api_key
(symbol
será passada por parâmetro para a função). Para resolver esse problema, definimos globais fixas no início do programa.
Inclusive, api_key
deve receber a chave que foi previamente gerada para que as pesquisas sejam realizadas.
# Variáveis padrão para realizar a consulta a API
api_key = '############'
function = 'TIME_SERIES_DAILY'
output_size = 'full'
Feito isso, a requisição deve ser realizada e montada em um dataframe
.
# Realiza a requisição
r = requests.get(url)
data = r.json()
# Cria o dataframe já invertendo índices e colunas
df = pd.DataFrame.from_dict(data['Time Series (Daily)']).transpose()
Como os dados estão ordenados de maneira que a data é descrescente, para montar o gráfico podemos inverter essa ordem.
# Inverte a ordem do dataframe (data mais antiga primero)
df = df.iloc[::-1]
Por último, é necessário transformar os indices da tabela de string
para datetime
.
# Converte o índice do dataframe de string para o objeto de data do Pandas
df = df.set_index(pd.to_datetime(df.index, format='%Y-%m-%d'))
Dessa forma, o resultado final é o seguinte:
def get_cotations_data(symbol):
# Monta a URL da requisição
url = 'https://www.alphavantage.co/query?function=' + function + '&symbol=' + \
symbol + '&outputsize=' + output_size + '&apikey=' + api_key
# Realiza a requisição
r = requests.get(url)
data = r.json()
# Cria o dataframe já invertendo índices e colunas
df = pd.DataFrame.from_dict(data['Time Series (Daily)']).transpose()
# Inverte a ordem do dataframe (data mais antiga primero)
df = df.iloc[::-1]
# Converte o índice do dataframe de string para o objeto de data do Pandas
df = df.set_index(pd.to_datetime(df.index, format='%Y-%m-%d'))
return df
Iterando pelo histórico
Uma vez que os dados estão tratados, basta iterar pela ocorrências para calcular as variações de cotação.
Primeiramente, define-se a variavel previous_value
para guardar o fechamento do dia anterior (atribuindo o fechamento do primeiro dia do histórico a ela).
# Recebe o valor de fechamento da menor data
previous_value = float(df.tail(1)['4. close'])
Após isso, precisamos definir 3 vetores auxiliares
# Cria os vetores auxiliares
closes = []
days = []
markers = []
Closes contém todos os valores de fechamento da cotação do ativo, Days contém todos os dias de negociação e Markers contém apenas os dias que atendem as critério de variação passado por parâmetro.
A seguir segue o algoritmo para iterar por todos os dias de negociação e preencher todos os vetores necessários.
# Itera por todos os dias disponíveis, pulando o menor deles
for index, row in df.iloc[1:].iterrows():
# Recebe o valor de fechamento do dia atual
close_value = float(row['4. close'])
# Recebe a variação atual, baseado no fechamento anterior e atual
variation = get_intraday_variation(previous_value, close_value)
# Trata todos os tipos de operando passados
if test_variation(operator, reference, variation):
# Caso a variação tenha passado no teste, salva ela no vetor auxiliar
markers.append(index)
# Salva o fechamento e dia atuais para plotas no gráfico
closes.append(close_value)
days.append(index)
# Prepara o valor anterior como o valor atual para a próxima iteração
previous_value = close_value
Primeiramente obtem-se o valor de fechamento do dia atual na variavel close_value
.
Após isso, a variável variation
recebe o resultado da função get_intraday_variation()
, que definiremos a seguir.
Obtida a variação, é necessário testar se esse dado atende ao critério utilizado, a função test_variation()
é responsável por isso. Caso o teste retorne positivo, o dia da variação é adicionado ao vetor de marcadores.
Por último, adicionamos o dia e o valor de fechamento aos seus vetores, necessários para montar o gráfico do ativo, e atualizamos o previous_value
para a próxima ocorrência.
Calculando a variação
A variação do ativo no dia pode ser calculada pelo valor de fechamento atual e do dia anterior com a seguinte fórmula:
def get_intraday_variation(previous_value, close_value):
# Variação = (valor atual / valor anterior - 1) * 100
variation = (close_value / previous_value - 1) * 100
return variation
Testando a veriação
Para testar se a oscilação atende ao critério passado, basta testar o valor obtido pelo operador escolhido.
def test_variation(operator, reference, variation):
# Identifica qual o operador passado e realiza o determinado teste
if operator == "=":
# Testa se são iguais
return reference == variation
elif operator == "+":
# Testa se a variação é maior que a referência
return reference < variation
elif operator == "-":
# Testa se a variação é menor que a referência
return reference > variation
else:
# Trata erro caso o operador não seja reconhecido
return False
Obtendo índices de marcação
Para conseguir marcar as datas de marcação no gráfico, precisamos encontrar a quais índicer no vetor de datas cada marcação corresponde, o que pode ser feito com o seguinte comando.
# Converte os dias salvos para marcação em índices do vetor de dias
markers = [days.index(day) for day in markers]
Montando o título do gráfico
Para montar o título do gráfico, selecionamos o menor e maior ano do histórico, em conjunto com o ticker passado como parâmetro.
def build_title(symbol, df):
# Busca o menor e maior ano de dados obtidos
min_date = df.index[0].year
max_date = df.index[-1].year
# Monta a string de título
title = symbol + " " + "stock performance" + " " + "(" + str(min_date) + " - " + str(max_date) + ")"
return title
Montando a legenda do gráfico
Da mesma forma para a legenda apenas traduzimos os argumentos passados para uma linguagem mais formal, o mesmo exercício realizado na apresentação dos argumentos mais acima.
def build_legend(operator, reference):
# Cria um dicionário para traduzir o operador passado
operator_dict = {
"=": "=",
"+": ">",
"-": "<",
}
# Monta a string de legenda
legend = "Variation" + " " + operator_dict[operator] + " " + str(reference)
return legend
Plotando o resultado final
Esse último passo se trata apenas de uma questão estética, qualquer mudança nos argumentos apresentado pode ser feita consultando a documentação da biblioteca Matplotlib.
def plot_graph(title, legend, days, closes, markers):
# Plota o gráfica relacionando datas e fechamentos, inserindo os marcadores
plt.plot(days, closes, markevery=markers, marker="o",
markerfacecolor='red', markersize=8)
# Define tamanhos de títulos e rotações de labels
plt.title(title, size=18, pad=24)
plt.xticks(rotation=90)
# Remove a margem dos lados e adiciona o grid horizontal
plt.grid(axis='y')
plt.margins(x=0)
# Adiciona label de performance ao gráfico
plt.ylabel("Performance", labelpad=15)
# Converte datas para anos
plt.gca().xaxis.set_major_locator(mdates.YearLocator())
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
# Cria a legenda do gráfico
red_square = mlines.Line2D([], [], color='red', marker='o', linestyle='None',
markersize=10, label=legend)
plt.legend(handles=[red_square])
# Mostra o resultado na tela
plt.show()
Exemplos
A seguir seguem alguns exemplos do argumento passado e o gráfico gerado pelo algoritmo.
FB
main.py FB - -10
AMD
main.py AMD + 15
Resultado final
O resultado final desse projeto pode ser encontrado no meu github pessoal.