Ciência de Dados aplicada ao mercado financeiro – Análise de estratégia de retornos positivos com uso de Regressão Linear Simples
- Por rodrigo.azevedo
Todos os analistas que atuam no mercado financeiro buscam, de alguma forma, predizer os próximos movimentos do mercado e conseguir retornos positivos em suas atividades. Para isso utilizam-se de dados e informações para subsidiar análises e definir estratégias de entrada e saída para suas negociações.
A minha contribuição neste post será um estudo de análise dos dados de negociações da paridade EUR x USD para a determinação de uma estratégia de rompimento do preço em relação a uma regressão linear em determinada janela de tempo. Para isso vou utilizar os dados de valor dessa paridade por hora e a linguagem de programação Python.
Importando módulos essenciais
Vamos começar importando as bibliotecas necessárias para a análise dos dados:
import pathlib as ph import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.linear_model import LinearRegression from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
Essas bibliotecas serão utilizadas para encontrar e importar o arquivo de dados no repositório local, plotar, calcular e multiprocessar os dados ingeridos.
Visualização dos Dados
Vamos seguir para um teste de conexão e visualização do arquivo de dados.
# Atribui o endereço do arquivos a uma variável filename = ph.Path(r'C:\Users\Dell\OneDrive\Desktop\My GitHub\FOREX-Data-Science\EURUSD_H1.csv') # Abre e atribui o arquivo a uma variável pairs = open(filename, mode="r", encoding="utf-8", newline='\r') # Exibe xxx caracteres do arquivo print(pairs.read(500)) # Fecha o arquivo pairs.close()
O arquivo chama-se EURUSD_H1.csv. A visualização nos traz informações importantes como cabeçalho, separação, tipo de dados e formatação. No nosso caso as dados no arquivo .csv tem a seguinte disposição:
Criação do DataFrame
Conhecemos o arquivo e agora, realizaremos a importação para um pandas dataframe. Vamos aplicar algumas formatações para facilitar a manipulação dos dados e verificar sua estrutura:
# Atribui o endereço do arquivos a uma variável filename = ph.Path(r'C:\Users\Dell\OneDrive\Desktop\My GitHub\FOREX-Data-Science\EURUSD_H1.csv') # Importa o arquivo como um dataframe raw = pd.read_csv(filename, delimiter='\t', header=0, usecols=[0,2,1,5], parse_dates={'DATETIME' : [0, 1]}, index_col=["DATETIME"]) # Renomeia as colunas e exibe os dados do dataframe raw = raw.rename(columns={"DATETIME": "DATETIME" ,"<OPEN>": "OPEN", "<CLOSE>": "CLOSE"}) # Imprime a quantidade de linhas e colunas print(raw.shape, "\n") # Verifica se há valores nulos no dataframe e imprime o resultado print(raw.isnull().sum(), "\n") # Imprime metadados do dataframe print(raw.info(), "\n")
Temos um dataframe de 100039 linhas e duas colunas, com dados dos preços de fechamento de abertura e fechamento para o período entre 3 de Março de 2005 as 00:00:00 horas e 26 de Fevereiro de 2021 as 23:00:00 horas.
Criação de métricas iniciais
Agora vamos tornar a coisa um pouco mais interessante, vamos delimitar nossa amostra em um período de um ano (2021) e criar mais algumas informações e métricas para utilização da análise da estratégia.
# Cria novo dataframe com base em filtros no RAW dataframe df = raw.query("DATETIME >= '2020.01.01 00:00:00' & DATETIME <= '2020.12.31 23:00:00'") # Cria coluna de número de linhas df.loc[:,"ROW_N"] = [*range(0,df.shape[0])] # Cálcula o valor de escala do eixo x em função da diferença dos preços de fechamento df.loc[:,"EIXO_X"] = np.array([*range(0,df.shape[0])]) * np.mean(np.abs(df.CLOSE.diff(periods=1))) # Reordena colunas df = df.loc[:,["ROW_N", "EIXO_X", "OPEN", "CLOSE"]]
Adicionamos ao dataframe uma nova coluna com o número de linhas e uma outra coluna com um valor numérico que é o somatório acumulado da média das diferenças do preço fechamento atual e do dia anterior. Nesta última, criamos uma escala para utilizar como eixo horizontal quando calcularmos a regressão linear.
Plotando nosso dataframe temos a seguinte visualização:
myStyle = { "figure.figsize": (25,10), "figure.titlesize": 20, "axes.titlesize" : 20, "axes.labelsize" : 18, "legend.fontsize": 18, "lines.linewidth" : 3, "lines.markersize" : 10, "xtick.labelsize" : 16, "ytick.labelsize" : 16} plt.style.use(['seaborn', myStyle]) plt.plot('ROW_N', "OPEN", data=df, linestyle=':', marker='o', markerfacecolor='skyblue', color='skyblue') plt.plot('ROW_N', "CLOSE", data=df, linestyle=':', marker='o', markerfacecolor='blue', color='blue') plt.title("EUR X USD") plt.legend() plt.show()
A linha azul escuro traz os preços de fechamento e a azul clara os preços de abertura. Vamos utilizar o preço de abertura como preço de fechamento do dia anterior. No momento 15, note a diferença entre o preço de fechamento do dia anterior e o deste dia em questão. Compare agora olhando os dados diretamente no data frame:
Chegamos ao ponto principal do estudo: definir uma estratégia de entrada e saída de negociação. Vamos avaliar os casos em que o preço de fechamento atual rompe (cruza) a regressão linear registrada neste mesmo momento
Criação de métricas da regressão linear simples
Precisaremos então calcular a regressão linear simples (que é uma reta) para cada preço de fechamento (CLOSE). Teremos também que definir uma janela de tempo para a regressão. O código a seguir vai calcular os coeficientes angular (LRa), linear (LRb), o valor da regressão linear (PRC) e o ângulo em graus da reta. Todas essas variáveis serão calculadas para cada janela de 13 elementos do dataframe utilizando com pontos os campos EIXO_X e CLOSE:
wd = 13 # Janela de tempo para cálculo das estatistícas móveis # Cálculo da regressão linear lm_model = LinearRegression() # objeto do modelo estatístico - regressão Linear iterator1 = [*range(0,df.shape[0])] iterator2 = [*range(0+wd-1,df.shape[0])] def _calcula_coeficientes(a, b): eixo_xt = np.array(df.EIXO_X[a : b+1]) eixo_yt = np.array(df.CLOSE[a : b+1]) # cálcula os coeficientes do modelo lm_model.fit(eixo_xt.reshape(-1, 1), eixo_yt) # extrair e registrar o coeficiente angular (a) convertido em ângulo (graus) cx = lm_model.coef_[0] # extrair e registrar o coeficiente angular (a) convertido em ângulo (graus) cxa = np.arctan(lm_model.coef_[0]) * 180 / np.pi # extrair e registrar coeficiente linear (b) cy = lm_model.intercept_ prc = cx * np.array(df.EIXO_X[b]) + cy return cx, cxa, cy, prc with ProcessPoolExecutor() as executor: cx, cxa, cy, prc = zip(*executor.map(_calcula_coeficientes, iterator1, iterator2)) tmpf = pd.DataFrame({"PRC_RL": prc, "LRa":np.array(cx), "LRag":np.array(cxa), "LRb":np.array(cy)}, index=np.array(df.index[wd-1:])) df = df.join(tmpf, how="outer") df = df.fillna(0)
O dataframe agora contém as informações de 12 retas de regressão linear de 13 elementos. Usando os rótulos do nosso dataframe, a equação de cada reta de regressão linear seria: PRC_RL = LRa * EIXO_X + LRb. Já o ângulo da reta é dado por: arco-tangente(LRa) * 180 / Pi. Confira com os dados abaixo:
Exemplificação da estratégia: entrada na operação
Para exemplificar a estratégia de forma gráfica, a regressão linear abaixo (em vermelho) foi criada a partir da segunda janela de 13 elementos do dataframe. A coluna PRC traz todos os preços da regressão linear que estão plotadas nessa curva. Compare os valores de OPEN, CLOSE e PRC e veja que, o valor de fechamento do dia anterior (OPEN) está abaixo do valor do preço da regressão linear (PRC) e, o valor do preços de fechamento atual (CLOSE) está acima do valor do preço da regressão linear (PRC). Desta forma temos um rompimento da regressão linear para cima. Repare também que o ângulo desta regressão é de 8,11 graus (coluna LRag)
df_tmp = df[1:14] df_tmp["PRC"] = (df_tmp.LRa[12] * np.array(df_tmp.EIXO_X)) + df_tmp.LRb[12] plt.plot('ROW_N', "OPEN", data=df, linestyle=':', marker='o', markerfacecolor='skyblue', color='skyblue') plt.plot('ROW_N', "CLOSE", data=df, linestyle=':', marker='o', markerfacecolor='blue', color='blue') plt.plot('ROW_N', "PRC", data=df_tmp, linestyle='-', color='red') plt.title("EUR X USD") plt.legend()
Definimos então que a nossa estratégia de entrada será o rompimento da reta da regressão linear. Para saber quais registros no nosso dataframe atendem a essa condição fazemos:
df.query(“OPEN <= PRC_RL & CLOSE >= PRC_RL" | “OPEN >= PRC_RL & CLOSE <= PRC_RL)"
Isso será útil para levantarmos todas a possíveis operações de entrada em uma amostragem bem maior. Vamos usá-la posteriormente.
Exemplificação da estratégia: saída da operação
Agora vamos para a estratégia de saída da operação. Consideraremos que a saída será qualquer preço de fechamento (CLOSE [x]) menor que o preço de fechamento (CLOSE [0]) registrado no início da operação. Resumindo, após a entrada, no primeiro prejuízo (variação negativa) saímos da operação.
Olhe novamente o gráfico anterior. Note que ocorrido o rompimento da regressão linear para cima, o preço de fechamento subsequente é menor que o que o preço de entrada da operação. Dessa forma nossa operação de entrada seria interrompida logo em seguida.
Bem, você já deve ter entendido o que vamos fazer. Após a operação de entrada, vamos guardar todas as variações dos preços subsequentes ao preço de entrada (CLOSE [0]) até que a operação seja interrompida quando ocorrer o primeiro prejuízo (variação (-)). Assim, teremos uma amostragem de todos os possíveis retornos a partir de um rompimento da regressão linear e todos os prejuízos também.
Obtenção dos resultados
Pegaremos um período de 1 ano e somente os rompimentos para baixo para gerar nossa amostra. Criaremos um novo dataframe chamado results_be_df:
# ângulo da reta da regressão linear LRag_be = 0 # index de todos os cruzamentos do preço contra o preço da rl no Bear Market ind_xbe = [*df.query("OPEN >= PRC_RL & CLOSE <= PRC_RL & LRag < %s" %(LRag_be)).ROW_N] def _Calc_Var_BearM(i): df_tmp = df.query("ROW_N >= %s" %(i)) cnt = 0 cnt_tmp = [] row_n= [] close_tmp = [] var_tmp = [] varp_tmp = [] for x in range(len(df_tmp)): row_n.append(df_tmp.ROW_N[x]) close_tmp.append(df_tmp.CLOSE[x]) var_tmp.append(df_tmp.CLOSE[0] - df_tmp.CLOSE[x]) cnt_tmp.append(cnt) cnt += 1 if (df_tmp.CLOSE[0] - df_tmp.CLOSE[x]) < 0: break return pd.DataFrame({"ROW_N": row_n, "COUNT": cnt_tmp, "CLOSE": close_tmp, "CLOSE_X": df_tmp.CLOSE[0], "VAR": var_tmp}) with ProcessPoolExecutor(4) as executor: r2 = executor.map(_Calc_Var_BearM, ind_xbe) results_be_df = pd.concat(r2, ignore_index=True)
Vamos analisar uma parte dos dados gerados:
A coluna COUNT mostra a contagem do início até o final da negociação, ou seja, a entrada, as possibilidades de retorno e a saída da operação, enquanto que, a coluna VAR, traz a variação de cada valor do preço de fechamento (CLOSE) em relação ao preço de entrada na operação (CLOSE_X). Portanto, a operação iniciada no registro 27, ofereceu 7 oportunidades de ganho antes de ser interrompida com prejuízo no oitavo registro.
Análise dos resultados
Com base nesse raciocínio, vamos olhar todos os resultados de uma só vez de forma gráfica. No período de 1 ano (2021), temos 487 rompimentos para baixo da regressão linear. Vamos separar a amostragem dos resultados positivos dos resultados negativos.
rownp3 = results_be_df.query("VAR > 0").ROW_N.count() rownn4 = results_be_df.query("VAR < 0").ROW_N.count() plt.figure(figsize=(25,8)) plt.subplot(2,2,1, title=r"BOX PLOT BEAR MARKET - EUR x USD - VAR(-) | Sample: %s" %(rownn4)) plt.boxplot([*results_be_df.query("VAR < 0").VAR], showfliers=False) plt.subplot(2,2,2, title=r"BOX PLOT BEAR MARKET - EUR x USD - VAR(+) | Sample: %s" %(rownp3)) plt.boxplot([*results_be_df.query("VAR > 0").VAR], showfliers=False) plt.subplot(2,2,3, title=r"HISTOGRAM BEAR MARKET - EUR x USD - VAR(-) | Sample: %s" %(rownn4)) plt.hist(results_be_df.query("VAR < 0").VAR, bins=100, density=False) plt.subplot(2,2,4, title=r"HISTOGRAM BEAR MARKET - EUR x USD - VAR(+) | Sample: %s" %(rownp3)) plt.hist(results_be_df.query("VAR > 0").VAR, bins=100, density=False) plt.show()
Para os resultados positivos temos 16250 possibilidades de retorno em 487 operações iniciadas. Abaixo temos a descrição e a distribuição da amostra.
Para os resultados negativos temos os seguintes resultados:
Pela comparação dos dados positivos e negativos, pela amplitude e quantidade de retornos positivos e negativos, podemos concluir que há reais possibilidades de retornos financeiros positivos com essa estratégia. Podemos expandir a simulação para um período muito maior e, ou ainda utilizar o ângulo da reta da regressão linear como mais uma variável a ser inserida nessa modelagem.
Até aqui acredito que as informações foram suficientes para prosseguir com um estudo mais aprofundado e também aplicação de Machine Learning. Esses tópicos serão abordados em posts futuros.
Conheça a Rox School
Somos especialistas em cuidar dos seus dados, oferecendo soluções inovadoras e parcerias com os maiores nomes da tecnologia para manter você sempre à frente.