
Ganhe uma Vantagem sobre Qualquer Mercado (Parte II): Previsão de Indicadores Técnicos
Introdução
Investidores que buscam aplicar aprendizado de máquina em ambientes de negociação eletrônica enfrentam inúmeros desafios, e a realidade é que muitos não alcançam os resultados desejados. Este artigo tem como objetivo destacar algumas razões pelas quais, na minha opinião, um aspirante a trader algorítmico pode não obter retornos satisfatórios em relação à complexidade de suas estratégias. Demonstrar por que a previsão do preço de um valor mobiliário muitas vezes tem dificuldade em ultrapassar 50% de precisão e como, ao focar na previsão dos valores de indicadores técnicos, a precisão pode melhorar para cerca de 70%. Este guia fornecerá instruções passo a passo sobre as melhores práticas para análise de séries temporais.
Ao final deste artigo, você terá uma compreensão sólida de como aprimorar a precisão dos seus modelos de aprendizado de máquina e descobrir indicadores antecipados de mudanças de mercado de maneira mais eficaz do que outros participantes usando Python e MQL5.
Previsão de Valores de Indicadores
Vamos buscar dados históricos do nosso terminal MetaTrader 5 e analisá-los usando bibliotecas padrão do Python. Esta análise mostrará que prever mudanças nos valores dos indicadores é mais eficaz do que prever mudanças nos preços de valores mobiliários. Isso é verdade porque só podemos observar parcialmente os fatores que influenciam o preço de um valor mobiliário. Na realidade, não podemos modelar todas as variáveis que afetam o preço de um símbolo devido ao seu grande número e complexidade. No entanto, podemos observar completamente todos os fatores que afetam o valor de um indicador técnico.
Primeiro, demonstrarei o princípio e depois explicarei por que essa abordagem funciona melhor ao final da nossa discussão. Ao ver o princípio em ação primeiro, a explicação teórica será mais fácil de entender. Vamos começar selecionando o ícone de lista de símbolos no menu logo acima do gráfico.
Nossos objetivos aqui estão focados em buscar dados:
- Abra o seu terminal MetaTrader 5.
- Selecione o ícone de lista de símbolos no menu acima do gráfico.
- Escolha o símbolo desejado e o período de tempo para sua análise.
- Exporte os dados históricos para um arquivo separado por vírgula (csv).
Fig 1: Obtendo dados históricos.
Pesquise o símbolo que você gostaria de modelar.
Fig 2: Pesquisando o símbolo desejado.
Depois, selecione o ícone 'barras' no menu e certifique-se de solicitar o máximo de dados possível.
Fig 3: Solicitando dados históricos.
Selecione exportar barras no menu inferior para que possamos começar a analisar nossos dados no Python.
,
Fig 4: Exportando nossos dados históricos.
Como de costume, começamos primeiro importando as bibliotecas de que precisaremos.
#Load libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt
Em seguida, lemos nossos dados históricos de mercado. Note que o terminal MetaTrader 5 exporta arquivos csv delimitados por tabulação, portanto, passamos a notação de tabulação para o parâmetro de separador em nossa chamada para pandas read csv.
#Read the data csv = pd.read_csv("/content/Volatility 75 Index_M1_20190101_20240131.csv",sep="\t") csv
Depois de ler nossos dados históricos, eles parecerão assim. Precisamos reformular os títulos das colunas e também adicionar um indicador técnico.
Fig 5: Nossos dados históricos do terminal MetaTrader 5.
Agora renomearemos as colunas.
#Format the data csv.rename(columns={"<DATE>":"date","<TIME>":"time","<TICKVOL>":"tickvol","<VOL>":"vol","<SPREAD>":"spread","<OPEN>":"open","<HIGH>":"high","<LOW>":"low","<CLOSE>":"close"},inplace=True) csv.ta.sma(length= 60,append=True) csv.dropna(inplace=True) csv
Fig 6: Formatando nossos dados.
Agora podemos definir nossos insumos.
#Define the inputs predictors = ["open","high","low","close","SMA_60"]
Em seguida, escalaremos nossos dados para que nosso modelo possa treinar adequadamente.
#Scale the data
csv["open"] = csv["open"] /csv.loc[0,"open"]
csv["high"] = csv["high"] /csv.loc[0,"high"]
csv["low"] = csv["low"] /csv.loc[0,"low"]
csv["close"] = csv["close"] /csv.loc[0,"close"]
csv["SMA_60"] = csv["SMA_60"] /csv.loc[0,"SMA_60"]
Abordaremos esta tarefa como um problema de classificação. Nosso alvo será categórico. Um valor alvo de 1 significa que o preço do ativo aumentou em mais de 60 candles, e um valor alvo de 0 significa que o preço caiu no mesmo horizonte. Observe que temos dois alvos. Um alvo monitora a mudança no preço de fechamento, enquanto o outro monitora a mudança na média móvel.
Usaremos o mesmo padrão de codificação nas mudanças da média móvel. Um valor alvo de 1 significa que o valor futuro da média móvel nos próximos 60 candles será maior, e, inversamente, um valor alvo de 0 significa que a média móvel cairá nos próximos 60 candles.
#Define the close csv["Target Close"] = 0 csv["Target MA"] = 0
Defina o horizonte de previsão.
#Define the forecast horizon look_ahead = 60
Codificar os valores alvo.
#Set the targets
csv.loc[csv["close"] > csv["close"].shift(-look_ahead) ,"Target Close"] = 0
csv.loc[csv["close"] < csv["close"].shift(-look_ahead) ,"Target Close"] = 1
csv.loc[csv["SMA_60"] > csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 0
csv.loc[csv["SMA_60"] < csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 1
csv = csv[:-look_ahead]
Ajustaremos o mesmo grupo de modelos no mesmo conjunto de dados. Lembre-se de que a única diferença é que, da primeira vez, nossos modelos tentarão prever a mudança no preço de fechamento, enquanto no segundo teste tentarão prever a mudança em um indicador técnico, em nosso exemplo, a média móvel.
Depois de definir nossos alvos, podemos prosseguir para importar os modelos necessários para nossa análise.
#Get ready from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.linear_model import LogisticRegression from xgboost import XGBClassifier from sklearn.neural_network import MLPClassifier from sklearn.metrics import accuracy_score from sklearn.decomposition import PCA from sklearn.model_selection import TimeSeriesSplit
Prepararemos uma divisão de séries temporais para avaliar onde nosso erro de validação é menor. Além disso, transformaremos nossos dados de entrada usando as funções de Análise de Componentes Principais (PCA) do sklearn. Esse passo é necessário porque nossas colunas de entrada podem estar correlacionadas, o que poderia prejudicar o processo de aprendizado do nosso modelo. Ao realizar a PCA, transformamos nosso conjunto de dados em uma forma que garante a ausência de correlação entre as entradas, melhorando assim o desempenho do nosso modelo.
#Time series split splits = 10 gap = look_ahead models_close = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] models_ma = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] #Prepare the data pca = PCA() csv_reduced = pd.DataFrame(pca.fit_transform(csv.loc[:,predictors]))
Agora, vamos observar nossos níveis de precisão, usando uma rede neural que tenta prever mudanças diretamente no preço de fechamento.
#Fit the neural network predicting close price model_close = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_close.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target Close"]) print("Close accuracy: ",accuracy_score(csv.loc[300070:,"Target Close"], model_close.predict(csv_reduced.loc[300070:,:])))
Nossa precisão ao prever mudanças no preço de fechamento foi de 49,9%. Isso não é impressionante, considerando a quantidade de complexidade que aceitamos. Poderíamos ter obtido o mesmo nível de precisão com um modelo mais simples, que seria mais fácil de manter e entender. Além disso, se estivermos certos apenas 49% do tempo, continuaremos em uma posição não lucrativa. Vamos contrastar isso com nossa precisão ao prever mudanças no indicador de média móvel.
#Fit the model predicting the moving average model_ma = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_ma.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target MA"]) print("MA accuracy: ",accuracy_score(csv.loc[300070:,"Target MA"], model_ma.predict(csv_reduced.loc[300070:,:])))
A precisão do nosso modelo foi de 68,8% ao prever as mudanças na média móvel, em comparação com 49,9% ao prever as mudanças no preço. Esse é um nível aceitável de precisão em relação à complexidade da técnica de modelagem que estamos utilizando.
Agora ajustaremos uma variedade de modelos e veremos qual modelo pode prever melhor as mudanças no preço e qual modelo pode prever melhor as mudanças na média móvel.
#Error metrics tscv = TimeSeriesSplit(n_splits=splits,gap=gap) error_close_df = pd.DataFrame(index=np.arange(0,splits),columns=models_close) error_ma_df = pd.DataFrame(index=np.arange(0,splits),columns=models_ma)
Vamos primeiro avaliar a precisão de cada um dos nossos modelos selecionados ao tentar prever o preço de fechamento.
#Training each model to predict changes in the close price for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target Close"]) error_close_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target Close"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
Fig 7: Os resultados de precisão de diferentes modelos tentando classificar mudanças no preço.
Fig 8: Uma visualização do desempenho de cada um dos nossos modelos.
Podemos avaliar a maior precisão registrada por cada modelo ao prever o preço de fechamento.
for i in enumerate(np.arange(0,error_close_df.shape[1])): print(error_close_df.columns[i[0]]," ", error_close_df.iloc[:,i[0]].max())
LDA 0.5192457894678943
XGB 0.5119523008041539
Rede Neural Simples 0.5234700724948571
Rede Neural Grande 0.5186627504042771
Como podemos ver, nenhum de nossos modelos teve um desempenho excepcional. Todos ficaram dentro de uma faixa de 50%, mas nosso modelo de Análise Discriminante Linear (LDA) teve o melhor desempenho entre o grupo.
Por outro lado, agora estabelecemos que nossos modelos apresentam melhor precisão ao prever mudanças em certos indicadores técnicos. Agora queremos determinar, entre nosso grupo de candidatos, qual modelo apresenta o melhor desempenho ao prever mudanças na média móvel.
#Treinando cada modelo para prever mudanças em um indicador técnico (neste exemplo a média móvel simples) em vez do preço de fechamento. for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target MA"]) error_ma_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target MA"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
Fig 9: A precisão dos nossos modelos tentando prever mudanças na média móvel.
Fig 10: Uma visualização da precisão dos nossos modelos ao prever mudanças na média móvel.
Vamos avaliar a maior precisão registrada por cada tipo de modelo.
for i in enumerate(np.arrange(0,error_ma_df.shape[1])): print(error_ma_df.columns[i[0]]," ", error_ma_df.iloc[:,i[0]].max())
Regressão Logística 0.6927054112625546
LDA 0.696401658911147
XGB 0.6932664488520731
Rede Neural Simples 0.6947955513019373
Rede Neural Grande 0.6965006655445914
Observe que, embora a rede neural grande tenha atingido o maior nível de precisão, não desejaríamos usá-la em produção porque seu desempenho foi instável. Podemos observar isso pelos 2 pontos no gráfico do desempenho da rede neural grande que estão bem abaixo de sua média de desempenho. Portanto, podemos observar pelos resultados que, dado nosso conjunto de dados atual, o modelo ideal deve ser mais complexo do que uma simples regressão logística e menos complicado do que uma rede neural grande.
Prosseguiremos agora construindo uma estratégia de negociação que prevê movimentos futuros no indicador de média móvel como um sinal de negociação. Nosso modelo escolhido será a pequena rede neural porque parece ser muito mais estável.
Primeiro, importamos as bibliotecas necessárias.
#Import the libraries we need import MetaTrader5 as mt5 import pandas_ta as ta import pandas as pd
Em seguida, configuramos nosso ambiente de negociação.
#Trading global variables MARKET_SYMBOL = 'Volatility 75 Index' #This data frame will store the most recent price update last_close = pd.DataFrame() #We may not always enter at the price we want, how much deviation can we tolerate? DEVIATION = 10000 #We will always enter at the minimum volume VOLUME = 0 #How many times the minimum volume should our positions be LOT_MUTLIPLE = 1 #What timeframe are we working on? TIMEFRAME = mt5.TIMEFRAME_M1 #Which model have we decided to work with? neural_network_model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1)
Vamos determinar o volume mínimo permitido no símbolo que desejamos negociar.
#Determine the minimum volume for index,symbol in enumerate(symbols): if symbol.name == MARKET_SYMBOL: print(f"{symbol.name} has minimum volume: {symbol.volume_min}") VOLUME = symbol.volume_min * LOT_MULTIPLE
Agora podemos criar uma função que realizará nossas ordens de mercado.
# function to send a market order def market_order(symbol, volume, order_type, **kwargs): #Fetching the current bid and ask prices tick = mt5.symbol_info_tick(symbol) #Creating a dictionary to keep track of order direction order_dict = {'buy': 0, 'sell': 1} price_dict = {'buy': tick.ask, 'sell': tick.bid} request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_dict[order_type], "price": price_dict[order_type], "deviation": DEVIATION, "magic": 100, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result
Além disso, também precisamos de outra função que nos ajude a fechar nossas ordens de mercado.
# Closing our order based on ticket id def close_order(ticket): positions = mt5.positions_get() for pos in positions: tick = mt5.symbol_info_tick(pos.symbol) #validating that the order is for this symbol type_dict = {0: 1, 1: 0} # 0 represents buy, 1 represents sell - inverting order_type to close the position price_dict = {0: tick.ask, 1: tick.bid} #bid ask prices if pos.ticket == ticket: request = { "action": mt5.TRADE_ACTION_DEAL, "position": pos.ticket, "symbol": pos.symbol, "volume": pos.volume, "type": type_dict[pos.type], "price": price_dict[pos.type], "deviation": DEVIATION, "magic": 10000, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result return 'Ticket does not exist'
Além disso, devemos definir o intervalo de datas a partir do qual queremos solicitar dados.
#Update our date from and date to date_from = datetime(2024,1,1) date_to = datetime.now()
Antes de podermos passar os dados solicitados do corretor, devemos primeiro pré-processar os dados no mesmo formato observado por nosso modelo durante o treinamento.
#Let's create a function to preprocess our data def preprocess(df): #Calculating 60 period Simple Moving Average df.ta.sma(length=60,append=True) #Drop any rows that have missing values df.dropna(axis=0,inplace=True)
Prosseguindo, devemos ser capazes de obter uma previsão de nossa rede neural e interpretar essa previsão como um sinal de negociação para abrir uma posição longa ou curta.
#Get signals from our model def ai_signal(): #Fetch OHLC data df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to)) #Process the data df['time'] = pd.to_datetime(df['time'],unit='s') df['target'] = (df['close'].shift(-1) > df['close']).astype(int) preprocess(df) #Select the last row last_close = df.iloc[-1:,1:] #Remove the target column last_close.pop('target') #Use the last row to generate a forecast from our moving average forecast model #Remember 1 means buy and 0 means sell forecast = neural_network_model.predict(last_close) return forecast[0]Finalmente, juntamos tudo isso para criar nossa estratégia de negociação.
#Now we define the main body of our trading algorithm if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal() #Decoding model prediction into an action if signal == 1: direction = 'buy' elif signal == 0: direction = 'sell' print(f'AI Forecast: {direction}') #Opening A Buy Trade #But first we need to ensure there are no opposite trades open on the same symbol if direction == 'buy': #Close any sell positions for pos in mt5.positions_get(): if pos.type == 1: #This is an open sell order, and we need to close it close_order(pos.ticket) if not mt5.positions_totoal(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) #Opening A Sell Trade elif direction == 'sell': #Close any buy positions for pos in mt5.positions_get(): if pos.type == 0: #This is an open buy order, and we need to close it close_order(pos.ticket) if not mt5.positions_get(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
Fig 11: Nosso modelo em ação.
Explicação Teórica
Na opinião do autor, uma das razões pelas quais podemos observar maior precisão ao prever mudanças em indicadores técnicos é o fato de que nunca podemos observar todas as variáveis que afetam o preço de um ativo. No melhor dos casos, só podemos observá-las parcialmente, enquanto ao prever mudanças em um indicador técnico, estamos cientes de todas as entradas que influenciam o indicador. Lembre-se de que até conhecemos a fórmula precisa de qualquer indicador técnico.
Fig 12: Sabemos a descrição matemática de todos os indicadores técnicos, mas não existe fórmula matemática do preço de fechamento.
Por exemplo, o indicador técnico Multiplicador de Fluxo de Dinheiro (MFM) é calculado usando a fórmula acima. Portanto, se quisermos prever mudanças no MFM, só precisamos dos componentes de sua fórmula: os preços de fechamento, baixa e alta.
Em contraste, ao prever o preço de fechamento, não temos uma fórmula específica que nos diga quais entradas o afetam. Isso muitas vezes resulta em menor precisão, sugerindo que nosso conjunto atual de entradas pode não ser informativo ou que introduzimos muito ruído ao escolher entradas ruins.
Fundamentalmente, o objetivo do aprendizado de máquina é encontrar um alvo determinado por um conjunto de entradas. Quando usamos um indicador técnico como nosso alvo, estamos essencialmente afirmando que o indicador técnico é influenciado pelos preços de abertura, fechamento, mínima e máxima, o que é verdade. No entanto, como desenvolvedores algorítmicos, muitas vezes usamos nossas ferramentas de maneira inversa. Usamos uma coleção de dados de preços e indicadores técnicos para prever o preço, implicando que os indicadores técnicos influenciam o preço, o que não é o caso e nunca será.
Ao tentar aprender um alvo cuja função subjacente não é conhecida, podemos cair na armadilha do que é conhecido como regressão espúria, que discutimos longamente em nossa discussão anterior. Em termos simples, é possível que seu modelo aprenda uma relação que não existe na vida real. Além disso, essa falha pode ser mascarada por taxas de erro enganosamente baixas durante a validação, fazendo parecer que o modelo aprendeu o suficiente, quando na verdade ele não aprendeu nada sobre o mundo real.
Para ilustrar o que é uma regressão espúria, imagine que você e eu estamos caminhando por uma colina e logo depois do horizonte vemos uma forma vaga. Estamos muito longe para ver o que é, mas com base no que vi, grito "há um cachorro lá embaixo". Agora, ao chegarmos, encontramos um arbusto, mas atrás do arbusto há um cachorro.
Fig 13: Eu poderia ter visto o cachorro?
Você já pode ver o problema? Obviamente, eu adoraria reivindicar a vitória como um testemunho da minha visão perfeita, mas você sabe que, fundamentalmente falando, não havia como eu ter visto o cachorro de onde estávamos quando fiz a declaração, estávamos simplesmente muito longe e a figura era muito vaga de onde estávamos.
Não havia simplesmente nenhuma relação entre os dados de entrada que vi no topo da montanha e a conclusão a que cheguei. Ou seja, os dados de entrada e os dados de saída eram independentes entre si. Sempre que um modelo analisa dados de entrada que não têm relação com os dados de saída, mas consegue produzir a resposta correta, chamamos isso de regressão espúria. Regressões espúrias acontecem o tempo todo!
Como não temos fórmulas técnicas que descrevam o que afeta o preço de um ativo, somos propensos a cometer regressões espúrias usando entradas que não têm influência sobre nosso alvo, que é o preço de fechamento. Tentar provar que uma regressão não é espúria pode ser desafiador, é mais fácil usar um alvo que tenha uma relação conhecida com as entradas.
Conclusão
Este artigo demonstrou por que a prática de prever o preço de fechamento diretamente deve ser potencialmente descontinuada em favor da previsão de mudanças em indicadores técnicos. Mais pesquisas são necessárias para descobrir se existem outros indicadores técnicos que podemos prever com mais precisão do que a média móvel. No entanto, os leitores também devem ser alertados de que, embora nossa precisão ao prever a média móvel seja relativamente alta, ainda há um atraso entre as mudanças da média móvel e as mudanças no preço.
Em outras palavras, é possível que a média móvel esteja caindo enquanto o preço está subindo, no entanto, se trabalharmos coletivamente como comunidade MQL5 para melhorar este algoritmo, estou confiante de que eventualmente poderemos alcançar novos níveis de precisão.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/14936





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso