Área do cliente

Ciência de Dados aplicada ao mercado financeiro – Análise de estratégia de retornos positivos com uso de Regressão Linear Simples

Analytics

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:

Visualização da fonte de dados

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.

Informações sobre os dados importados

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()
Gráfico de um período de 24horas dos valores horários de abertura (azul claro) e fechamento (azul escuro)

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:

Dados do gráfico para fins de comparação e verificação

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:

Exibição do dataframe com as variáveis calculadas

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()
Posição 13: rompimento para cima do valor da regressão linear, vindo valor de abertura para o valor de fechamento.
Segunda janela de 13 elementos
Exibição dos dados do dataframe temporário que contém os valores da reta da regressão linear.

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.

Valor de fechamento subsequente a operação de entrada menor. Variação negativa. Saída da operação.

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:

Parcial do dataframe result_be_df.

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.

Estatísticas e descrição dos resultados positivos.

Para os resultados negativos temos os seguintes resultados:

Estatísticas e descrição dos resultados negativos.

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.