Reimaginando Estratégias Clássicas em Python: MA Crossovers
Introdução
Muitas das estratégias de negociação atuais foram concebidas em cenários de mercado muito diferentes. Avaliar sua relevância em mercados contemporâneos dominados por algoritmos é crucial. Este artigo examina a estratégia de cruzamento de médias móveis para avaliar sua eficácia no ambiente financeiro atual.
Este artigo abordará os seguintes pontos:
- Há evidências quantitativas que apoiam o uso contínuo da estratégia?
- Quais vantagens a estratégia oferece em comparação à análise direta de preços?
- A estratégia ainda funciona de maneira eficaz em meio ao trading algorítmico moderno?
- Existem outros indicadores que podem melhorar a precisão da estratégia?
- A IA pode ser utilizada de maneira eficaz para prever cruzamentos de médias móveis antes que eles aconteçam?
A técnica de usar cruzamentos de médias móveis tem sido amplamente estudada ao longo de décadas. O conceito fundamental de usar essas médias para detectar tendências e sinais de negociação tem sido um pilar na análise técnica, embora sua origem exata seja incerta.
A estratégia de cruzamento de médias móveis geralmente envolve duas médias móveis com períodos diferentes, mas a condição crucial é que um período seja mais longo que o outro. Quando a média móvel de período mais curto cruza acima da média móvel de período mais longo, sinaliza uma tendência de alta em potencial, e o contrário para uma tendência de baixa.Analistas técnicos têm utilizado essa estratégia por décadas para identificar pontos de entrada e saída, avaliar o sentimento do mercado e para várias outras aplicações. Para determinar sua eficácia atual, submeteremos a estratégia a um teste quantitativo moderno. Nossa abordagem é detalhada a seguir.

Fig 1: Exemplo de cruzamentos de médias móveis aplicados ao par CADJPY.
Visão Geral
Estamos prestes a embarcar em uma jornada emocionante, onde vamos conectar nosso terminal MetaTrader5 com nosso ambiente Python. Primeiro, solicitaremos dados M15 para o par EURUSD de 1º de janeiro de 2020 a 25 de junho de 2024. Esse extenso conjunto de dados nos dará uma visão abrangente dos comportamentos recentes do mercado.
Nosso próximo passo é estabelecer dois alvos. O primeiro medirá nossa precisão em prever mudanças diretas de preço, servindo como nossa linha de base. Essa referência nos ajudará a comparar nosso desempenho ao prever cruzamentos de médias móveis. Ao longo do caminho, procuraremos indicadores técnicos adicionais para aumentar nossa precisão. Finalmente, pediremos aos nossos modelos computacionais que identifiquem as variáveis-chave para prever cruzamentos de médias móveis. Se o modelo não priorizar as duas médias móveis que usamos, isso pode indicar que nossas suposições iniciais estavam incorretas.
Antes de mergulhar nos números, vamos considerar os possíveis resultados:
-
Superação na Previsão Direta de Preço: Se prever mudanças de preço diretamente resulta em maior ou igual precisão em comparação com os cruzamentos de médias móveis, isso sugere que prever cruzamentos pode não oferecer nenhuma vantagem extra, questionando a validade da estratégia.
-
Superação na Previsão de Cruzamentos: Se alcançarmos melhor precisão ao prever cruzamentos de médias móveis, isso nos motivaria a buscar mais dados para aprimorar nossas previsões, destacando o valor potencial da estratégia.
-
Irrelevância das Médias Móveis: Se nossos modelos não identificarem nenhuma das médias móveis como crucial para prever cruzamentos, isso implica que outras variáveis podem ser mais significativas, sugerindo que a relação assumida entre as duas médias móveis não se sustenta.
-
Relevância das Médias Móveis: Se uma ou ambas as médias móveis forem identificadas como importantes para prever cruzamentos, isso confirma uma relação substancial entre elas, permitindo-nos construir modelos confiáveis para previsões informadas.
Esta análise nos ajudará a entender os pontos fortes e fracos do uso de cruzamentos de médias móveis em nossa estratégia de negociação, orientando-nos em direção a métodos de previsão mais eficazes.
O Experimento: Os Cruzamentos de Médias Móveis Ainda São Confiáveis?
Vamos começar importando as bibliotecas padrão do Python de que precisamos.
import pandas as pd import pandas_ta as ta import numpy as np import MetaTrader5 as mt5 from datetime import datetime import seaborn as sns import time
Em seguida, inserimos nossos dados de login.
account = 123436536 password = "Enter Your Password" server = "Enter Your Broker"
Prosseguindo, tentaremos agora fazer login em nossa conta de negociação.
if(mt5.initialize(login=account,password=password,server=server)): print("Logged in succesfully") else: print("Failed to login")
Login realizado com sucesso.
A seguir, definiremos algumas variáveis globais.
timeframe = mt5.TIMEFRAME_M15 deviation = 1000 volume = 0 lot_multiple = 10 symbol = "EURUSD"
Em seguida, buscaremos dados de mercado sobre o símbolo que desejamos negociar.
#Setup trading volume symbols = mt5.symbols_get() for index,symbol in enumerate(symbols): if symbol.name == "EURUSD": print(f"{symbol.name} has minimum volume: {symbol.volume_min}") volume = symbol.volume_min * lot_multiple
EURUSD tem volume mínimo: 0,01
Agora nos prepararemos para buscar dados de treinamento.
#Specify date range of data to be modelled date_start = datetime(2020,1,1) date_end = datetime.now()
Em seguida, definiremos o quão distante no futuro desejamos prever.
#Define how far ahead we are looking look_ahead = 20
Podemos então proceder para buscar dados de mercado em nosso terminal MetaTrader5 e, em seguida, rotular os dados. Nosso esquema de rotulagem usa "1" para codificar um movimento de alta e "0" para movimentos de baixa.
#Fetch market data market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end)) market_data["time"] = pd.to_datetime(market_data["time"],unit='s') #Add simple moving average technical indicator market_data.ta.sma(length=5,append=True) #Add simple moving average technical indicator market_data.ta.sma(length=50,append=True) #Delete missing rows market_data.dropna(inplace=True) #Add a column for the target market_data["target"] = 0 market_data["close_target"] = 0 #Encoding the target ma_cross_conditions = [ (market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead)), (market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead)) ] #Encoding pattern ma_cross_choices = [ #Fast MA above Slow MA 1, #Fast MA below Slow MA 0 ] price_conditions = [ (market_data["close"] > market_data["close"].shift(-look_ahead)), (market_data["close"] < market_data["close"].shift(-look_ahead)) ] #Encoding pattern price_choices = [ #Price fell 0, #Price rose 1 ] market_data["target"] = np.select(ma_cross_conditions,ma_cross_choices) market_data["close_target"] = np.select(price_conditions,price_choices) #The last rows do not have answers market_data = market_data[:-look_ahead] market_data

Fig 2: Nosso dataframe com nossos dados de mercado em sua forma atual.
Agora importaremos as bibliotecas de machine learning de que precisamos.
#XGBoost from xgboost import XGBClassifier #Catboost from catboost import CatBoostClassifier #Random forest from sklearn.ensemble import RandomForestClassifier #LDA and QDA from sklearn.discriminant_analysis import LinearDiscriminantAnalysis , QuadraticDiscriminantAnalysis #Logistic regression from sklearn.linear_model import LogisticRegression #Neural network from sklearn.neural_network import MLPClassifier #Time series split from sklearn.model_selection import TimeSeriesSplit #Accuracy metrics from sklearn.metrics import accuracy_score #Visualising performance from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt from sklearn.model_selection import learning_curve
Preparando-se para realizar uma divisão de série temporal no conjunto de dados.
#Time series split splits = 10 gap = look_ahead models = ["Logistic Regression","Linear Discriminant Analysis","Quadratic Discriminant Analysis","Random Forest Classifier","XGB Classifier","Cat Boost Classifier","Neural Network Small","Neural Network Large"]
Vamos avaliar a precisão de vários modelos diferentes, e armazenaremos a precisão obtida de cada modelo em um data frame. Um data frame armazenará nossa precisão ao prever cruzamentos de médias móveis, e o segundo medirá nossa precisão ao prever mudanças diretas de preço.
error_ma_crossover = pd.DataFrame(index=np.arange(0,splits),columns=models)
error_price = pd.DataFrame(index=np.arange(0,splits),columns=models) Agora vamos proceder para medir a precisão de cada modelo. Mas primeiro devemos definir as entradas que nossos modelos usarão.
predictors = ["open","high","low","close","tick_volume","spread","SMA_5","SMA_50"]
Para medir a precisão de cada modelo, treinaremos nossos modelos em uma fração do conjunto de dados e depois os testaremos no restante do conjunto que não foi visto durante o treinamento. A biblioteca TimeSeriesSplit particiona nosso data frame para nós e facilita esse processo.
tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
#Training each model to predict changes in the moving average cross over for i,(train,test) in enumerate(tscv.split(market_data)): model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True) model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"target"] ) error_ma_crossover.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))
#Training each model to predict changes in the close price for i,(train,test) in enumerate(tscv.split(market_data)): model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True) model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"close_target"] ) error_price.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"close_target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))
Vamos primeiro ver o data frame que mede nossa precisão ao prever mudanças diretas de preço.
error_price

Fig 3: Nossa precisão ao prever mudanças diretas de preço.
Vamos interpretar os resultados antes de prosseguir. O primeiro comentário que podemos fazer é que nenhum dos modelos teve um bom desempenho na tarefa; alguns modelos demonstraram menos de 50% de precisão ao prever o preço diretamente. Esse desempenho é decepcionante, o que implica que poderíamos ter um desempenho semelhante a esses modelos apenas adivinhando aleatoriamente. Nossos modelos estão organizados em ordem de complexidade crescente, com regressão logística simples à esquerda e redes neurais profundas à direita. Nossos modelos estão organizados em ordem de complexidade crescente, com regressão logística simples à esquerda e redes neurais profundas à direita. Vamos agora ver se há alguma melhoria ao prever cruzamentos de médias móveis.
error_ma_crossover

Fig 4: Nossa precisão ao prever cruzamentos de médias móveis.
Como podemos ver no data frame acima, a Análise Discriminante Linear (LDA) teve um desempenho excepcional nessa tarefa. Foi o modelo com melhor desempenho por uma ampla margem. Além disso, ao contrastar o desempenho aprimorado do LDA com o quão mal ele se saiu na primeira tarefa, podemos ver claramente que cruzamentos de médias móveis podem ser mais confiáveis para prever do que mudanças diretas de preço. Os benefícios de prever cruzamentos de médias móveis são indiscutíveis nesse caso.
Visualizando os Resultados
Vamos visualizar os resultados obtidos acima.

Fig 5: Visualização dos resultados obtidos.
A melhora no algoritmo LDA é visivelmente notável em boxplots, indicando um aprendizado significativo do modelo. Adicionalmente, houve uma leve, mas perceptível, melhora no desempenho da Regressão Logística. Notavelmente, a Análise Discriminante Linear (LDA) consistentemente produziu pontuações agrupadas de forma bem próxima nos boxplots ao prever cruzamentos de médias móveis, demonstrando precisão e consistência desejáveis. Esse agrupamento sugere que as previsões do modelo eram estáveis, com resíduos provavelmente estacionários, indicando uma relação confiável aprendida pelo modelo.
Agora, vamos analisar os tipos de erros cometidos pelo modelo. Nosso objetivo é determinar se ele apresenta melhor desempenho em identificar movimentos de alta, movimentos de baixa ou se o desempenho é equilibrado entre ambas as tarefas.

Fig 6: Uma matriz de confusão do desempenho do nosso modelo LDA.
A matriz de confusão acima exibe a classificação verdadeira à esquerda e a previsão do modelo na parte inferior. Os dados mostram que o modelo cometeu mais erros ao prever movimentos de alta, classificando incorretamente um movimento de alta como baixa em 47% das vezes. Por outro lado, o modelo teve um bom desempenho ao prever movimentos de baixa, confundindo um movimento de baixa verdadeiro como alta apenas 25% das vezes. Portanto, podemos observar claramente que o modelo é melhor em prever movimentos de baixa do que movimentos de alta.
Podemos visualizar o progresso de aprendizado do modelo à medida que ele encontra quantidades crescentes de dados de treinamento. O gráfico abaixo serve para avaliar se o modelo está superajustando ou subajustando os dados de treinamento. O superajuste ocorre quando o modelo aprende o ruído dos dados, falhando em capturar relações significativas. O subajuste, por outro lado, é indicado por uma diferença significativa entre a precisão de treinamento (representada pela linha azul) e a precisão de validação (a linha laranja) no gráfico. No gráfico atual, observamos uma diferença perceptível, mas não extensa, entre as pontuações de treinamento e validação, sugerindo que o modelo LDA está de fato superajustando os dados de treinamento. No entanto, a escala no lado esquerdo indica que esse superajuste não é severo.

Fig 7: A curva de aprendizado do nosso classificador LDA.
Por outro lado, o subajuste é caracterizado por baixa precisão de treinamento e validação. Como exemplo, incluímos a curva de aprendizado de um de nossos modelos de baixo desempenho, a pequena rede neural. No gráfico abaixo, observamos uma relação instável entre o desempenho do modelo e a quantidade de dados de treinamento a que foi exposto. Inicialmente, o desempenho de validação do modelo se deteriora com o aumento dos dados, até atingir um ponto de inflexão e começar a melhorar conforme o tamanho do treinamento se aproxima de 10.000 amostras. Subsequentemente, a melhoria atinge um platô, com apenas incrementos marginais, apesar do contínuo aumento na quantidade de dados de treinamento disponíveis.

Fig 8: A curva de aprendizado da nossa pequena rede neural.
Eliminação de Recursos
Na maioria dos projetos de aprendizado de máquina, é incomum que todas as entradas estejam diretamente relacionadas à variável alvo. Tipicamente, apenas um subconjunto das entradas disponíveis é relevante para prever o alvo. Eliminar entradas irrelevantes oferece várias vantagens, tais como:
- Melhoria na eficiência computacional durante o treinamento do modelo e a engenharia de características.
- Maior precisão do modelo, especialmente se as características removidas eram ruidosas.
Em seguida, precisamos determinar se há uma relação significativa entre as médias móveis. Utilizaremos algoritmos de eliminação de recursos para validar a relação assumida. Se esses algoritmos não eliminarem as médias móveis da lista de entradas, isso indica que existe uma relação significativa. Por outro lado, se eles conseguirem remover essas características, isso sugere que não há uma relação significativa entre as médias móveis e o cruzamento de médias móveis.
Empregaremos uma técnica de seleção de características conhecida como seleção regressiva. Este método começa ajustando um modelo linear usando todas as entradas disponíveis e, em seguida, mede a precisão do modelo. Posteriormente, uma característica é removida por vez, e o impacto na precisão do modelo é observado. A característica que causa a menor redução na precisão é eliminada em cada etapa até que nenhuma característica reste. Nesta fase, o algoritmo seleciona automaticamente os recursos mais importantes que identificou e os recomenda para uso.
Uma desvantagem significativa da eliminação de características que vale a pena mencionar é que, quando colunas ruidosas e sem importância estão presentes no conjunto de dados, colunas importantes podem parecer não informativas. Consequentemente, o algoritmo de seleção regressiva pode, inadvertidamente, eliminar uma característica importante porque ela parece não informativa devido ao ruído no sistema.
Agora, vamos ver quais colunas o computador considera importantes. Começamos importando uma biblioteca chamada mlxtend que contém implementações do algoritmo de seleção regressiva.
from mlxtend.feature_selection import SequentialFeatureSelector
Aplicamos então o algoritmo ao nosso conjunto de dados. Vamos prestar atenção especial a 3 dos parâmetros que passamos:
- "k_features=" instrui o algoritmo sobre quantas colunas selecionar. Podemos instruir o algoritmo a selecionar apenas as colunas que ele acredita serem necessárias passando um intervalo que começa em 1 e vai até o número total de colunas no conjunto de dados.
- "forward=" instrui o algoritmo se ele deve usar seleção progressiva ou regressiva; queremos usar seleção regressiva, portanto definimos este parâmetro como "False".
- "n_jobs=" instrui o algoritmo se ele deve realizar cálculos em paralelo; passamos "-1" para dar ao algoritmo permissão para usar todos os núcleos disponíveis, reduzindo significativamente o tempo gasto.
backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(), k_features=(1,market_data.loc[:,predictors].shape[1]), forward=False, verbose=2, scoring="accuracy", cv=5, n_jobs=-1 ).fit(market_data.loc[:,predictors],market_data.loc[:,"target"])
[Parallel(n_jobs=-1)]: Usando o backend LokyBackend com 8 workers simultâneos.
[Parallel(n_jobs=-1)]: Concluído 3 de 8 | tempo decorrido: 8,0s tempo restante: 13,3s
[Parallel(n_jobs=-1)]: Concluído 8 de 8 | tempo decorrido: 8,0s tempo restante: 0.0s
[Parallel(n_jobs=-1)]: Concluído 8 de 8 | tempo decorrido: 8,0s finalizado
Após a conclusão do processo, podemos obter uma lista das entradas que o algoritmo considera importantes usando o seguinte comando:
backward_feature_selector.k_feature_names_
('open', 'high', 'close', 'SMA_5', 'SMA_50')
E como podemos ver, o algoritmo de seleção regressiva incluiu nossas 2 médias móveis na lista de características importantes. Isso é uma ótima notícia para nós, pois valida que nossa estratégia de negociação não é apenas resultado de uma regressão espúria.
Engenharia de Características
Agora que estabelecemos uma relação significativa entre nossas duas médias móveis que justifica esforços adicionais de melhoria, vamos explorar se indicadores técnicos adicionais podem melhorar nossa precisão na previsão de cruzamentos de médias móveis. Aqui é onde o aprendizado de máquina se inclina mais para a arte do que para a ciência, já que prever quais entradas serão benéficas antecipadamente é desafiador. Nossa abordagem envolverá adicionar várias características que acreditamos serem úteis e avaliar seu impacto real.
Coletaremos dados de mercado do mesmo mercado utilizado anteriormente, mas desta vez incorporaremos indicadores adicionais:
- Moving Average Convergence Divergence (MACD): Um indicador técnico poderoso de confirmação de tendências, que pode ajudar a observar mudanças nos regimes subjacentes do mercado.
- Awesome Oscillator: Renomado por fornecer sinais de saída muito confiáveis, além de mostrar claramente mudanças no momento de uma tendência.
- Aroon: Usado para identificar o início de novas tendências.
- Chaikin's Commodity Index: Atua como um barômetro para medir se um ativo financeiro está sobrecomprado ou sobrevendido.
- Percent Return: Ajuda a observar o crescimento do preço e se ele está aumentando positivamente ou negativamente.
#Fetch market data market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end)) market_data["time"] = pd.to_datetime(market_data["time"],unit='s') #Add simple moving average technical indicator market_data.ta.sma(length=5,append=True) #Add simple moving average technical indicator market_data.ta.sma(length=50,append=True) #Add macd market_data.ta.macd(append=True) #Add awesome oscilator market_data.ta.ao(append=True) #Add aroon market_data.ta.aroon(append=True) #Add chaikins comodity index market_data.ta.cci(append=True) #Add percent return market_data.ta.percent_return(append=True) #Delete missing rows market_data.dropna(inplace=True) #Add the target market_data["target"] = 0 market_data.loc[market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead),"target"] = 1 market_data.loc[market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead),"target"] = 0 #The last rows do not have answers market_data = market_data[:-look_ahead] market_data

Fig 9: Algumas das novas linhas adicionais adicionadas ao nosso DataFrame.
Após realizar a seleção de características, nosso algoritmo de seleção regressiva identificou as seguintes variáveis como importantes:
backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(), k_features=(1,market_data.loc[:,predictors].shape[1]), forward=False, verbose=2, scoring="accuracy", cv=5 ).fit(market_data.iloc[:,1:-1],market_data.loc[:,"target"])
backward_feature_selector.k_feature_names_
('close', 'tick_volume', 'spread', 'SMA_5', 'SMA_50', 'MACDh_12_26_9', 'AO_5_34')
Construindo Nossa Estratégia de Negociação
Agora estamos prontos para consolidar tudo que aprendemos em uma estratégia de negociação.
Primeiro, ajustamos nosso modelo com todos os dados de treinamento disponíveis, usando apenas as colunas que identificamos como úteis.
predictors = ['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34'] model = LinearDiscriminantAnalysis() model.fit(market_data.loc[:,predictors],market_data.loc[:,"target"])
Em seguida, definimos funções para buscar dados de mercado no terminal MetaTrader 5.
def get_prices(): start = datetime(2024,6,1) end = datetime.now() data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,start,end)) #Add simple moving average technical indicator data.ta.sma(length=5,append=True) data.ta.sma(length=50,append=True) #Add awesome oscilator data.ta.ao(append=True) #Add macd data.ta.macd(append=True) #Delete missing rows data.dropna(inplace=True) data['time'] = pd.to_datetime(data['time'],unit='s') data.set_index('time',inplace=True) data = data.loc[:,['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']] data = data.iloc[-2:,:] return(data)
Subsequentemente, precisamos de outro método para obter previsões do nosso modelo LDA.
#Get signals LDA model def ai_signal(input_data,_model): #Get a forecast forecast = _model.predict(input_data) return forecast[1]
Agora podemos construir nossa estratégia de negociação.
#Now we define the main body of our Python Moving Average Crossover Trading Bot if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal(get_prices(),model) #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 mt5.Buy(symbol,volume) #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 mt5.sell(symbol,volume) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
Previsão de IA: venda
time: 2024-06-25 14:35:37.954923
-------

Fig 10: Nossa estratégia de negociação em ação.
Implementação no MQL5
Seguindo em frente, vamos utilizar a API do MQL5 para desenvolver nosso próprio classificador do zero. Há inúmeras vantagens em criar um classificador personalizado no MQL5. Como autor, acredito firmemente que as soluções nativas em MQL5 oferecem flexibilidade incomparável.
Se exportássemos nosso modelo para o formato ONNX, precisaríamos de um modelo separado para cada mercado que desejamos negociar. Além disso, negociar em diferentes períodos de tempo exigiria múltiplos modelos ONNX para cada mercado. Ao construir nosso classificador diretamente em MQL5, ganhamos a capacidade de negociar em qualquer mercado sem essas limitações.
Vamos criar um novo projeto.

Fig 11: Criando um EA para implementar nossa estratégia.
Nossa primeira tarefa é definir algumas variáveis globais que usaremos ao longo do programa.
//Global variables int ma_5,ma_50; double bid, ask; double min_volume; double ma_50_reading[],ma_5_reading[]; int size; double current_prediction; int state = -1; matrix ohlc; vector target; double b_nort = 0; double b_one = 0; double b_two = 0; long min_distance,atr_stop;
Também teremos entradas ajustáveis pelo usuário final.
//Inputs int input lot_multiple = 20; int input positions = 2; double input sl_width = 0.4;
Por último, importaremos a biblioteca de negociação para nos ajudar a gerenciar nossas posições.
//Libraries #include <Trade\Trade.mqh> CTrade Trade;
Em seguida, precisamos definir funções auxiliares que nos ajudem a buscar dados, rotular os dados de treinamento, treinar nosso modelo e obter previsões. Vamos começar definindo uma função para buscar dados de treinamento e rotular o alvo para nosso classificador.
//+----------------------------------------------------------------------+ //|This function is responsible for getting our training data ready | //+----------------------------------------------------------------------+ void get_training_data(void) { //How much data are we going to use? size = 100; //Copy price data ohlc.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,size); //Get indicator data ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE); ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE); CopyBuffer(ma_50,0,0,size,ma_50_reading); CopyBuffer(ma_5,0,0,size,ma_5_reading); ArraySetAsSeries(ma_50_reading,true); ArraySetAsSeries(ma_5_reading,true); //Label the target target = vector::Zeros(size); for(int i = 0; i < size; i++) { if(ma_5_reading[i] > ma_50_reading[i]) { target[i] = 1; } else if(ma_5_reading[i] < ma_50_reading[i]) { target[i] = 0; } } //Feedback Print("Done getting training data."); }
Nosso modelo possui três coeficientes que utiliza para fazer previsões. Esses coeficientes precisam ser otimizados. Usaremos uma equação de atualização amigável para iniciantes para ajustar esses coeficientes. Medindo o erro nas previsões do modelo, modificaremos iterativamente os coeficientes para minimizar o erro e melhorar a precisão do sistema. Mas antes de começarmos a otimizar o modelo, precisamos primeiro definir como ele faz previsões.
//+----------------------------------------------------------------------+ //|This function is responsible for making predictions using our model | //+----------------------------------------------------------------------+ double model_predict(double input_one,double input_two) { //We simply return the probability that the shorter moving average will rise above the slower moving average double prediction = 1 / (1 + MathExp(-(b_nort + (b_one * input_one) + (b_two * input_two)))); return prediction; }
Agora que nosso modelo pode fazer previsões, podemos medir o erro nas suas previsões e iniciar o processo de otimização. Inicialmente, todos os três coeficientes serão definidos como 0. Ajustaremos iterativamente os coeficientes em pequenos passos para minimizar o erro total no sistema.
//+----------------------------------------------------------------------+ //|This function is responsible for training our model | //+----------------------------------------------------------------------+ bool train_model(void) { //Update the coefficients double learning_rate = 0.3; for(int i = 0; i < size; i++) { //Get a prediction from the model current_prediction = model_predict(ma_5_reading[i],ma_50_reading[i]); //Update each coefficient b_nort = b_nort + learning_rate * (target[i] - current_prediction) * current_prediction * (1 - current_prediction) * 1; b_one = b_one + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_5_reading[i]; b_two = b_two + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_50_reading[i]; Print(current_prediction); } //Show updated coefficient values Print("Updated coefficient values"); Print(b_nort); Print(b_one); Print(b_two); return(true); }
Após treinar o modelo com sucesso, seria benéfico ter uma função que recupere previsões do modelo. Essas previsões servirão como nossos sinais de negociação. Lembre-se: uma previsão de 1 é um sinal de compra, indicando que o modelo espera que a média móvel de período curto suba acima da média móvel de período longo. Por outro lado, uma previsão de 0 é um sinal de venda, indicando que o modelo espera que a média móvel de período curto caia abaixo da média móvel de período longo.
//Get the model's current forecast void current_forecast() { //Get indicator data ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE); ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE); CopyBuffer(ma_50,0,0,1,ma_50_reading); CopyBuffer(ma_5,0,0,1,ma_5_reading); //Get model forecast model_predict(ma_5_reading[0],ma_50_reading[0]); interpret_forecast(); }
Queremos que nosso Expert Advisor (EA) atue com base nas previsões do modelo. Portanto, escreveremos uma função para interpretar as previsões do modelo e tomar a ação apropriada: comprar quando o modelo prevê 1 e vender quando prevê 0.
//+----------------------------------------------------------------------+ //|This function is responsible for taking action on our model's forecast| //+----------------------------------------------------------------------+ void interpret_forecast(void) { if(current_prediction > 0.5) { state = 1; Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,0,0,"Volatitlity Doctor AI"); } if(current_prediction < 0.5) { state = 0; Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume * lot_multiple,bid,0,0,"Volatitlity Doctor AI"); } }
Agora que nossa aplicação pode aprender com dados, fazer previsões e agir com base nessas previsões, precisamos criar funções adicionais para gerenciar posições abertas. Especificamente, queremos que nosso programa adicione trailing stop losses (stop móvel) e take profits (alvos) para cada posição, gerenciando nossos níveis de risco. Não queremos posições abertas sem um limite de risco definido. A maioria das estratégias de negociação recomenda um tamanho fixo de stop loss de 100 pips, mas queremos garantir que nossos níveis de stop loss e take profit sejam definidos dinamicamente com base na volatilidade atual do mercado. Portanto, usaremos o Average True Range (ATR) para calcular quão amplos ou estreitos nossos stops devem ser. Usaremos um múltiplo do ATR para determinar esses níveis.
//+----------------------------------------------------------------------+ //|This function is responsible for calculating our SL & TP values | //+----------------------------------------------------------------------+ void CheckAtrStop() { //First we iterate over the total number of open positions for(int i = PositionsTotal() -1; i >= 0; i--) { //Then we fetch the name of the symbol of the open position string symbol = PositionGetSymbol(i); //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading if(_Symbol == symbol) { //Now we get information about the position ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price long type = PositionGetInteger(POSITION_TYPE); //Position Type double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value //If the position is a buy if(type == POSITION_TYPE_BUY) { //The new stop loss value is just the ask price minus the ATR stop we calculated above double atr_stop_loss = NormalizeDouble(ask - ((min_distance * sl_width)/2),_Digits); //The new take profit is just the ask price plus the ATR stop we calculated above double atr_take_profit = NormalizeDouble(ask + (min_distance * sl_width),_Digits); //If our current stop loss is less than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } //If the position is a sell else if(type == POSITION_TYPE_SELL) { //The new stop loss value is just the ask price minus the ATR stop we calculated above double atr_stop_loss = NormalizeDouble(bid + ((min_distance * sl_width)/2),_Digits); //The new take profit is just the ask price plus the ATR stop we calculated above double atr_take_profit = NormalizeDouble(bid - (min_distance * sl_width),_Digits); //If our current stop loss is greater than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } } } }
Em seguida, precisamos de uma função que será chamada sempre que quisermos calcular novos valores para stop loss e take profit.
//+------------------------------------------------------------------+ //|This function is responsible for updating our SL&TP values | //+------------------------------------------------------------------+ void ManageTrade() { CheckAtrStop(); }
Agora que definimos nossas funções auxiliares, podemos começar a chamá-las dentro dos nossos manipuladores de eventos. Quando nosso programa carregar pela primeira vez, queremos iniciar o processo de treinamento. Portanto, chamaremos a função auxiliar responsável pelo treinamento dentro do manipulador de eventos OnInit.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //Define important global variables min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //Train the model get_training_data(); if(train_model()) { interpret_forecast(); } return(INIT_SUCCEEDED); }
Após treinar o modelo, podemos começar a negociação real.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //Get updates bid and ask prices bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(PositionsTotal() == 0) { current_forecast(); } if(PositionsTotal() > 0) { ManageTrade(); } }

Fig 12: Um exemplo da saída do nosso Expert Advisor.

Fig 13: Nosso Expert Advisor em ação.
Conclusão
Neste artigo, demonstramos que é computacionalmente mais fácil para nosso modelo prever cruzamentos de médias móveis do que prever mudanças no preço diretamente.
Como em todos os meus artigos, prefiro fornecer explicações técnicas no final, enquanto demonstro o princípio primeiro. Existem várias possíveis razões para esta observação. Uma razão potencial é que, dependendo dos períodos escolhidos, médias móveis podem não cruzar tão frequentemente quanto os preços mudam de direção de forma errática. Em outras palavras, nas últimas duas horas, o preço pode ter subido, depois caído, ou mudado de direção duas vezes. No entanto, durante o mesmo período, as médias móveis podem não ter cruzado. Portanto, cruzamentos de médias móveis podem ser mais fáceis de prever porque não mudam de direção tão rapidamente quanto o próprio preço. Essa é apenas uma possível explicação. Sinta-se à vontade para pensar por conta própria, tirar suas próprias conclusões e compartilhá-las nos comentários abaixo.
Seguindo em frente, empregamos a seleção regressiva para eliminação de características, uma técnica onde um modelo linear é treinado iterativamente com uma característica removida a cada passo, com base no impacto sobre a precisão do modelo. Essa abordagem ajuda a identificar e reter as características mais informativas, embora seja suscetível a eliminar características importantes que possam parecer não informativas devido ao ruído.
Tendo validado uma relação significativa entre duas médias móveis, exploramos a integração de indicadores técnicos adicionais: MACD, Awesome Oscillator, Aroon, Chaikins Commodity Index e Percent Return. Esses indicadores visam aprimorar nossa capacidade de prever cruzamentos de médias móveis com precisão. No entanto, a seleção desses indicadores permanece algo artístico devido à natureza imprevisível de seu impacto no desempenho do modelo.
Em suma, nossa abordagem combina validação empírica com seleção estratégica de características para provar quantitativamente que cruzamentos de médias móveis podem ser previstos e que qualquer esforço para melhorar essa estratégia de negociação definitivamente não seria um desperdício de tempo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15160
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Ferramentas econométricas para previsão de volatilidade: Modelo GARCH
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 5): Sistema de Notificação (Parte III)
Negociação com spreads no mercado Forex usando o fator de sazonalidade
Data Science e Machine Learning (Parte 25): Previsão de Séries Temporais de Forex Usando uma Rede Neural Recorrente (RNN)
- 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
Qualquer ajuda com este erro
O pacote PyPI 'sklearn' está obsoleto, use 'scikit-learn'
em vez de 'sklearn' para comandos pip.
Alguma ajuda com esse erro?
O pacote PyPI 'sklearn' está obsoleto, use 'scikit-learn'
em vez de 'sklearn' para comandos pip.