Previsão de Tendência com LSTM para Estratégias de Seguimento de Tendência
Introdução
Memória de Curto e Longo Prazo (LSTM) é um tipo de rede neural recorrente (RNN) projetada para modelar dados sequenciais, capturando de forma eficaz dependências de longo prazo e resolvendo o problema do gradiente desvanecente. Neste artigo, exploraremos como utilizar LSTM para prever tendências futuras, aprimorando o desempenho de estratégias de seguimento de tendência. O artigo abordará a introdução de conceitos-chave e a motivação por trás do desenvolvimento, a obtenção de dados do MetaTrader 5, o uso desses dados para treinar o modelo em Python, a integração do modelo de aprendizado de máquina no MQL5 e a reflexão sobre os resultados e aspirações futuras com base em backtesting estatístico.
Motivação
Intuitivamente, estratégias de seguimento de tendência capitalizam ganhos em mercados direcionais, mas têm desempenho fraco em mercados laterais, onde a estratégia acaba comprando a um preço elevado e vendendo a um preço descontado. Pesquisas acadêmicas demonstraram que estratégias clássicas de seguimento de tendência, como o golden cross, funcionam em múltiplos mercados e períodos de tempo ao longo de longos períodos históricos. Embora essas estratégias possam não ser altamente lucrativas, elas demonstraram ganhos consistentes. Estratégias de seguimento de tendência normalmente lucram com valores extremos atípicos, que geram lucros significativamente maiores do que a perda média. A abordagem da estratégia, com stop-loss apertado e “deixar os lucros correrem”, resulta em uma baixa taxa de acerto, mas em uma alta relação risco-retorno por operação.
LSTM (Memória de Curto e Longo Prazo) é um tipo especializado de rede neural recorrente (RNN) projetado para capturar dependências de longo alcance em dados sequenciais. Ela utiliza células de memória que podem manter informações por longos períodos, superando o problema do gradiente desvanecente que normalmente afeta RNNs tradicionais. Essa capacidade de armazenar e acessar informações anteriores na sequência torna o LSTM particularmente eficaz para tarefas como previsão de séries temporais e previsão de tendências. Para problemas de regressão, o LSTM pode modelar as relações temporais entre as variáveis de entrada e prever saídas contínuas com alta precisão, tornando-o ideal para aplicações de previsão.
A motivação para este artigo é aproveitar o poder do LSTM para regressão de tendência, prevendo tendências futuras e potencialmente filtrando operações ruins resultantes de baixa tendência. Essa motivação baseia-se na hipótese de que estratégias de seguimento de tendência têm melhor desempenho em mercados com tendência em comparação a mercados sem tendência.
Utilizaremos o ADX (Average Directional Index) para indicar a força da tendência, pois é um dos indicadores mais populares para avaliar a tendência atual. Nosso objetivo é prever seu valor futuro em vez de usar o valor atual, já que um ADX elevado normalmente indica que uma tendência já ocorreu ou está terminando, tornando nosso ponto de entrada tarde demais para obter benefícios.
O ADX é calculado por:

Preparação e Pré-processamento dos Dados
Antes de buscar os dados, primeiro precisamos esclarecer quais dados são necessários. Planejamos usar diversas variáveis para treinar um modelo de regressão que prevê valores futuros do ADX. Essas variáveis incluem o RSI, que indica a força relativa atual do mercado, a porcentagem de retorno do último candle para servir como valor estacionário do preço de fechamento, e o próprio ADX, que é diretamente relevante para o valor que buscamos prever. Observe que acabamos de explicar a intuição por trás da escolha dessas variáveis. Você pode decidir as variáveis por conta própria, mas certifique-se de que sejam razoáveis e estacionárias. Planejamos treinar o modelo usando dados horários de 01.01.2020 a 01.01.2024 e testar o desempenho do modelo de 01.01.2024 a 01.01.2025 como um teste fora da amostra.
Agora que esclarecemos os dados que queremos buscar, vamos construir um expert advisor para recuperar esses dados.
Usaremos a classe CFileCSV, introduzida neste artigo, para salvar o array como uma string em um arquivo CSV. O código para esse processo é bastante simples, conforme mostrado abaixo.
#include <FileCSV.mqh> CFileCSV csvFile; int barsTotal = 0; int handleRsi; int handleAdx; string headers[] = { "time", "ADX", "RSI", "Stationary" }; string data[1000000][4]; int indexx = 0; vector xx; input string fileName = "XAU-1h-2020-2024.csv"; input bool SaveData = true; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() {//Initialize model handleRsi = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE); handleAdx = iADX(_Symbol,PERIOD_CURRENT,14); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (!SaveData) return; if(csvFile.Open(fileName, FILE_WRITE|FILE_ANSI)) { //Write the header csvFile.WriteHeader(headers); //Write data rows csvFile.WriteLine(data); //Close the file csvFile.Close(); } else { Print("File opening error!"); } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; double rsi[]; double adx[]; CopyBuffer(handleAdx,0,1,1,adx); CopyBuffer(handleRsi,0,1,1,rsi); data[indexx][0] =(string)TimeTradeServer(); data[indexx][1] = DoubleToString(adx[0], 2); data[indexx][2] = DoubleToString(rsi[0], 2); data[indexx][3] = DoubleToString((iClose(_Symbol,PERIOD_CURRENT,1)-iOpen(_Symbol,PERIOD_CURRENT,1))/iClose(_Symbol,PERIOD_CURRENT,1),3); indexx++; } }
Este expert advisor (EA) foi projetado para rastrear e registrar os valores do Índice de Força Relativa (RSI) e do Índice Direcional Médio (ADX) para um determinado símbolo. O EA utiliza as funções iRSI e iADX para obter os valores atuais de RSI e ADX, e os armazena junto com um carimbo de data e hora em um arquivo CSV. O arquivo CSV é criado com cabeçalhos para "time", "ADX", "RSI" e "Stationary". Se a opção SaveData estiver habilitada, ele grava os dados em um arquivo (especificado por fileName) ao ser desinicializado. Ele rastreia novos dados a cada tick e os armazena quando há uma mudança no número de barras.
Execute o Expert Advisor (EA) no Strategy Tester com um único teste. Após a execução do teste, o arquivo deverá ser salvo no seguinte caminho: /Tester/Agent-sth000/MQL5/Files.
Em seguida, passamos para o Python para o pré-processamento dos dados em preparação para o treinamento do nosso modelo de aprendizado de máquina.
Planejamos utilizar uma abordagem de aprendizado supervisionado, na qual o modelo é treinado para prever um resultado desejado com base em dados rotulados. O processo de treinamento envolve o ajuste dos pesos em várias operações aplicadas às variáveis, a fim de minimizar a perda de erro e produzir a saída final.
Para o rótulo, sugiro usar o valor médio do ADX dos próximos 10 valores de ADX. Essa abordagem garante que a tendência ainda não esteja totalmente estabelecida no momento em que entramos na operação, ao mesmo tempo em que evita que a tendência esteja muito distante do sinal atual. Usar a média dos próximos 10 valores de ADX é uma ótima maneira de garantir que a tendência permaneça ativa nas próximas barras, permitindo que nossa entrada capture os lucros dos movimentos direcionais futuros.
import pandas as pd data = pd.read_csv('XAU-1h-2020-2024.csv', sep=';') data= data.dropna().set_index('time') data['output'] = data['ADX'].shift(-10) data = data[:-10] data['output']= data['output'].rolling(window=10).mean() data = data[9:]
Este código lê o arquivo CSV e separa os dados em diferentes colunas, já que os valores estão agrupados por um ponto e vírgula (';'). Em seguida, remove quaisquer linhas vazias e define a coluna 'time' como índice para garantir que os dados estejam ordenados cronologicamente para o treinamento. Depois disso, uma nova coluna chamada "output" é criada, que calcula a média dos próximos 10 valores de ADX. Após isso, o código remove quaisquer linhas vazias restantes, pois algumas linhas podem não ter valores futuros de ADX suficientes para calcular a saída.
Treinamento do Modelo

Este diagrama ilustra o que o LSTM está tentando realizar durante nosso processo de treinamento. Queremos que a entrada tenha o formato (quantidade_amostras, etapas_de_tempo, quantidade_variáveis), onde etapa_de_tempo representa quantos pontos de tempo anteriores de dados queremos usar para prever o próximo valor. Por exemplo, podemos usar dados de segunda a quinta-feira para prever algum resultado para sexta-feira. O LSTM utiliza algoritmos para identificar padrões na série temporal e a relação entre as variáveis e o resultado. Ele cria uma ou mais camadas de redes neurais, cada uma consistindo em muitas unidades de peso (neurônios), que aplicam pesos a cada variável e a cada etapa de tempo para, por fim, gerar a previsão final.
Por simplicidade, você pode simplesmente executar o código a seguir, e ele cuidará do processo de treinamento para você.
import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense # Assume data is your DataFrame already loaded with the specified columns and a time-based index # data.columns should include ['ADX', 'RSI', 'Stationary', 'output'] # --- Step 1: Data Preparation --- time_step = 5 # Select features and target features = ['ADX', 'RSI', 'Stationary'] target = 'output' # --- Step 2: Create sequences for LSTM input --- def create_sequences(data, target_col, time_step): """ Create sequences of length time_step from the DataFrame. data: DataFrame of input features and target. target_col: Name of the target column. Returns: X, y arrays suitable for LSTM. """ X, y = [], [] feature_cols = data.columns.drop(target_col) for i in range(len(data) - time_step): seq_x = data.iloc[i:i+time_step][feature_cols].values # predict target at the next time step after the sequence seq_y = data.iloc[i+time_step][target_col] X.append(seq_x) y.append(seq_y) return np.array(X), np.array(y) # Create sequences X, y = create_sequences(data, target_col=target, time_step=time_step) # --- Step 3: Split into training and evaluation sets --- # Use a simple 80/20 split for training and evaluation X_train, X_eval, y_train, y_eval = train_test_split(X, y, test_size=0.2, shuffle=False) # --- Step 4: Build the LSTM model --- n_features = len(features) # number of features per time step model = Sequential() model.add(LSTM(50, input_shape=(time_step, n_features))) # LSTM layer with 50 units model.add(Dense(1)) # output layer for regression model.compile(optimizer='adam', loss='mse') model.summary() # --- Step 5: Train the model --- epochs = 50 batch_size = 100 history = model.fit( X_train, y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_eval, y_eval) )
Aqui estão algumas considerações importantes ao treinar com o código acima:
-
Pré-processamento dos Dados: Certifique-se de que seus dados estejam em ordem temporal durante a fase de pré-processamento. Não fazer isso pode resultar em viés de antecipação ao dividir os dados em blocos de etapas_de_tempo.
-
Divisão Treino-Teste: Ao dividir os dados em conjuntos de treinamento e teste, não embaralhe os dados. A ordem temporal deve ser preservada para evitar viés de antecipação.
-
Complexidade do Modelo: Para análise de séries temporais, especialmente com pontos de dados limitados, não há necessidade de construir muitas camadas, neurônios ou épocas. Complicar excessivamente o modelo pode levar a overfitting ou a parâmetros com alto viés. As configurações usadas no exemplo devem ser suficientes.
# --- Step 6: Evaluate the model --- eval_loss = model.evaluate(X_eval, y_eval) print(f"Evaluation Loss: {eval_loss}") # --- Step 7: Generate Predictions and Plot --- # Generate predictions on the evaluation set predictions = model.predict(X_eval).flatten() # Create a plot for predictions vs actual values plt.figure(figsize=(12, 6)) plt.plot(predictions, label='Predicted Output', color='red') plt.plot(y_eval, label='Actual Output', color='blue') plt.title('LSTM Predictions vs Actual Output') plt.xlabel('Sample Index') plt.ylabel('Output Value') plt.legend() plt.show()
Este código deve gerar o erro quadrático médio do conjunto de avaliação em comparação com a previsão do modelo, como mostrado abaixo.

Perda de Avaliação: 57.405677795410156 Ela é calculada por:

Onde n é o tamanho da amostra, yi é o valor previsto para cada amostra e y^i é o valor real para cada resultado da avaliação.
Como você pode ver pelo cálculo, é possível comparar a perda do modelo com o quadrado dos valores médios daquilo que você está prevendo para verificar se a perda relativa é excessivamente alta. Além disso, certifique-se de que a perda seja semelhante à dos conjuntos de treinamento, o que indica que o modelo não está sobreajustado aos dados de treinamento.
Por fim, para tornar o modelo compatível com o MQL5, queremos salvá-lo no formato ONNX. Como modelos LSTM não oferecem suporte direto a transições para ONNX, primeiro precisamos salvar o modelo como um modelo funcional, definindo explicitamente o formato de sua entrada e saída. Depois disso, podemos salvá-lo como um arquivo ONNX, tornando-o adequado para uso futuro com o MQL5.
import tensorflow as tf import tf2onnx # Define the input shape based on your LSTM requirements: (time_step, n_features) time_step = 5 n_features = 3 # Create a new Keras Input layer matching the shape of your data inputs = tf.keras.Input(shape=(time_step, n_features), name="input") # Pass the input through your existing sequential model outputs = model(inputs) functional_model = tf.keras.Model(inputs=inputs, outputs=outputs) # Create an input signature that matches the defined input shape input_signature = ( tf.TensorSpec((None, time_step, n_features), dtype=tf.float32, name="input"), ) output_path = "regression2024.onnx" # Convert the functional model to ONNX format onnx_model, _ = tf2onnx.convert.from_keras( functional_model, input_signature=input_signature, # matching the input signature opset=15, output_path=output_path ) print(f"Model successfully converted to ONNX at {output_path}")
Observe que "None" como formato de entrada aqui significa que o modelo pode aceitar qualquer número de amostras. Ele produzirá automaticamente as previsões correspondentes para cada amostra, tornando-o flexível para diferentes tamanhos de lote.
Construção do Expert Advisor
Agora que salvamos o arquivo do modelo ONNX, queremos copiá-lo para o diretório /MQL5/Files para uso posterior.
Voltamos ao MetaEditor. Vamos construir sobre uma estratégia clássica de seguimento de tendência baseada na lógica de sinal do golden cross. Esta é a mesma que implementei em meu artigo anterior sobre aprendizado de máquina. A lógica básica envolve duas médias móveis: uma rápida e uma lenta. Um sinal de operação é gerado quando as duas MAs se cruzam, e a direção da operação segue a média móvel rápida, daí o termo “seguimento de tendência”. O sinal de saída ocorre quando o preço cruza a média móvel lenta, permitindo mais espaço para trailing stops. O código completo é o seguinte:
#include <Trade/Trade.mqh> //XAU - 1h. CTrade trade; input int MaPeriodsFast = 15; input int MaPeriodsSlow = 25; input int MaPeriods = 200; input double lott = 0.01; ulong buypos = 0, sellpos = 0; input int Magic = 0; int barsTotal = 0; int handleMaFast; int handleMaSlow; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(Magic); handleMaFast =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsFast,0,MODE_SMA,PRICE_CLOSE); handleMaSlow =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsSlow,0,MODE_SMA,PRICE_CLOSE); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); //Beware, the last element of the buffer list is the most recent data, not [0] if (barsTotal!= bars){ barsTotal = bars; double maFast[]; double maSlow[]; CopyBuffer(handleMaFast,BASE_LINE,1,2,maFast); CopyBuffer(handleMaSlow,BASE_LINE,1,2,maSlow); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); //The order below matters if(buypos>0&& lastClose<maSlow[1]) trade.PositionClose(buypos); if(sellpos>0 &&lastClose>maSlow[1])trade.PositionClose(sellpos); if (maFast[1]>maSlow[1]&&maFast[0]<maSlow[0]&&buypos ==sellpos)executeBuy(); if(maFast[1]<maSlow[1]&&maFast[0]>maSlow[0]&&sellpos ==buypos) executeSell(); if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; } } } //+------------------------------------------------------------------+ //| Expert trade transaction handling function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if (trans.type == TRADE_TRANSACTION_ORDER_ADD) { COrderInfo order; if (order.Select(trans.order)) { if (order.Magic() == Magic) { if (order.OrderType() == ORDER_TYPE_BUY) { buypos = order.Ticket(); } else if (order.OrderType() == ORDER_TYPE_SELL) { sellpos = order.Ticket(); } } } } } //+------------------------------------------------------------------+ //| Execute sell trade function | //+------------------------------------------------------------------+ void executeSell() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); trade.Sell(lott,_Symbol,bid); sellpos = trade.ResultOrder(); } //+------------------------------------------------------------------+ //| Execute buy trade function | //+------------------------------------------------------------------+ void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); trade.Buy(lott,_Symbol,ask); buypos = trade.ResultOrder(); }
Não entrarei em mais detalhes sobre a validação e as sugestões para selecionar sua estratégia de backtest. Mais detalhes podem ser encontrados em meu artigo anterior sobre aprendizado de máquina, que está vinculado aqui.
Agora, tentaremos executar nosso modelo LSTM com base nessa estrutura.
Primeiramente, declaramos as variáveis globais que especificam o formato de nossa entrada e saída, bem como dois multi-arrays para armazenar os dados de entrada e saída. Além disso, declaramos um handle do modelo que gerenciará o processo de buscar dados para o modelo e extrair previsões dele. Essa configuração garante o fluxo adequado de dados e a interação entre o modelo e as variáveis de entrada/saída.
#resource "\\Files\\regression2024.onnx" as uchar lstm_onnx[] float data[1][5][3]; float out[1][1]; long lstmHandle = INVALID_HANDLE; const long input_shape[] = {1,5,3}; const long output_shape[]={1,1};
Em seguida, na função OnInit(), inicializamos os indicadores relevantes, como RSI e ADX, bem como o modelo ONNX. Durante essa inicialização, verificamos se o formato de entrada e o formato de saída declarados no MQL5 correspondem àqueles especificados anteriormente no modelo funcional em Python. Essa etapa garante consistência e evita erros durante a inicialização do modelo, assegurando que o modelo possa processar corretamente os dados no formato esperado.
int handleMaFast; int handleMaSlow; int handleAdx; // Average Directional Movement Index - 3 int handleRsi; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() {//Initialize model trade.SetExpertMagicNumber(Magic); handleMaFast =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsFast,0,MODE_SMA,PRICE_CLOSE); handleMaSlow =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsSlow,0,MODE_SMA,PRICE_CLOSE); handleAdx=iADX(_Symbol,PERIOD_CURRENT,14);//Average Directional Movement Index - 3 handleRsi = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE); // Load the ONNX model lstmHandle = OnnxCreateFromBuffer(lstm_onnx, ONNX_DEFAULT); //--- specify the shape of the input data if(!OnnxSetInputShape(lstmHandle,0,input_shape)) { Print("OnnxSetInputShape failed, error ",GetLastError()); OnnxRelease(lstmHandle); return(-1); } //--- specify the shape of the output data if(!OnnxSetOutputShape(lstmHandle,0,output_shape)) { Print("OnnxSetOutputShape failed, error ",GetLastError()); OnnxRelease(lstmHandle); return(-1); } if (lstmHandle == INVALID_HANDLE) { Print("Error creating model OnnxCreateFromBuffer ", GetLastError()); return(INIT_FAILED); } return(INIT_SUCCEEDED); }
Em seguida, declaramos uma função para atualizar os dados de entrada a cada novo candle. Essa função percorre o time_step (neste caso, 5) para armazenar os dados correspondentes no multi-array global. Ela converte os dados para o tipo float para garantir que atendam ao requisito de 32 bits esperado pelo modelo ONNX. Além disso, a função garante que a ordem do multi-array esteja correta, com os dados mais antigos primeiro e os dados mais recentes adicionados em sequência. Isso garante que os dados sejam fornecidos ao modelo na ordem temporal adequada.
void getData(){ double rsi[]; double adx[]; CopyBuffer(handleAdx,0,1,5,adx); CopyBuffer(handleRsi,0,1,5,rsi); for (int i =0; i<5; i++){ data[0][i][0] = (float)adx[i]; data[0][i][1] = (float)rsi[i]; data[0][i][2] = (float)((iClose(_Symbol,PERIOD_CURRENT,5-i)-iOpen(_Symbol,PERIOD_CURRENT,5-i))/iClose(_Symbol,PERIOD_CURRENT,5-i)); } }
Por fim, na função OnTick(), implementamos a lógica de negociação.
Essa função garante que a lógica de negociação subsequente seja verificada apenas quando um novo candle for formado. Isso evita recálculos ou ações de negociação desnecessárias durante o mesmo candle e garante que as previsões do modelo sejam baseadas em dados completos para cada novo passo de tempo.
int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars;
Este código restaura as variáveis buypos e sellpos para 0 quando não há posições restantes com o número mágico do EA. As variáveis buypos e sellpos são usadas para garantir que tanto as posições de compra quanto de venda estejam vazias antes de gerar um sinal de entrada. Ao redefinir essas variáveis quando não há posições abertas, garantimos que o sistema não tente acidentalmente abrir novas posições se uma já existir.
if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; }
Ao redefinir essas variáveis quando não há posições abertas, garantimos que o sistema não tente acidentalmente abrir novas posições se uma já existir. Essa operação é executada apenas quando o sinal inicial de entrada é formado, e não a cada novo candle. Essa abordagem ajuda a conservar poder computacional e torna o backtest mais eficiente, pois evitamos avaliações desnecessárias do modelo durante períodos em que nenhum sinal de entrada está presente.
OnnxRun(lstmHandle, ONNX_NO_CONVERSION, data, out);
A lógica de negociação agora se torna: quando ocorre o cruzamento das MAs e nenhuma posição atual está aberta, executamos o modelo para obter o valor previsto do ADX. Se o valor for menor que um determinado limite, consideramos isso como baixa tendência e evitamos a operação; se for maior, entramos na operação. Aqui está a função OnTick() completa:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; double maFast[]; double maSlow[]; double adx[]; CopyBuffer(handleMaFast,BASE_LINE,1,2,maFast); CopyBuffer(handleMaSlow,BASE_LINE,1,2,maSlow); CopyBuffer(handleAdx,0,1,1,adx); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); //The order below matters if(buypos>0&& lastClose<maSlow[1]) trade.PositionClose(buypos); if(sellpos>0 &&lastClose>maSlow[1])trade.PositionClose(sellpos); if(maFast[1]<maSlow[1]&&maFast[0]>maSlow[0]&&sellpos == buypos){ getData(); OnnxRun(lstmHandle, ONNX_NO_CONVERSION, data, out); if(out[0][0]>threshold)executeSell();} if(maFast[1]>maSlow[1]&&maFast[0]<maSlow[0]&&sellpos == buypos){ getData(); OnnxRun(lstmHandle, ONNX_NO_CONVERSION, data, out); if(out[0][0]>threshold)executeBuy();} if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; } } }
Backtest Estatístico
Após implementar tudo, agora podemos compilar o EA e testar os resultados no strategy tester. Conduziremos um teste fora da amostra para o XAUUSD no período de 1 hora, de 1º de janeiro de 2024 a 1º de janeiro de 2025. Primeiro, executaremos nossa estratégia original de backtest como linha de base. Esperamos que o EA com a implementação LSTM supere a linha de base durante esse período.




Agora, vamos executar o backtest no EA com a implementação LSTM, usando um limite de 30, pois um ADX de 30 é amplamente reconhecido como indicativo de forte força de tendência.




Ao comparar os dois resultados, observamos que a implementação do LSTM filtrou cerca de 70% das operações originais e melhorou o fator de lucro de 1,48 para 1,52. Ela também apresentou uma correlação LR mais alta do que a linha de base, sugerindo que contribuiu para um desempenho geral mais estável.
Ao realizar backtests de modelos de aprendizado de máquina, é importante reconhecer que os parâmetros internos do modelo são determinantes-chave, ao contrário de estratégias mais simples, em que os parâmetros têm menos impacto. Como resultado, diferentes dados de treinamento podem levar a resultados de parâmetros muito diferentes. Além disso, treinar com todo o conjunto de dados históricos de uma só vez não é ideal, pois resultaria em muitas amostras, a maioria das quais careceria de atualidade. Por esse motivo, recomendo o uso do método de janela deslizante para backtests nesses casos. Se tivermos amostras limitadas ao longo de todo o histórico do backtest, conforme discutido em meu artigo anterior sobre o modelo CatBoost, um backtest com janela expansiva é mais adequado.
Aqui estão as imagens de demonstração:


Backtest com Janela Expansiva começa com uma janela de dados inicial de tamanho fixo, mas, à medida que novos pontos de dados se tornam disponíveis, a janela se expande para incluir os novos dados, testando a estratégia em um conjunto de dados cada vez maior ao longo do tempo.
Para realizar o backtest deslizante, simplesmente repetimos o processo descrito neste artigo e mesclamos os resultados em um único conjunto de dados. Aqui está o desempenho do backtest com janela deslizante de 1º de janeiro de 2015 a 1º de janeiro de 2025:

Métricas:
Profit Factor: 1.24 Maximum Drawdown: -250.56 Average Win: 12.02 Average Loss: -5.20 Win Rate: 34.81%
O resultado é impressionante, com espaço para melhorias adicionais.
Reflexão
O desempenho do EA se correlaciona diretamente com a previsibilidade demonstrada pelo modelo. Para melhorar seu EA, há alguns fatores-chave a serem considerados:
- A vantagem da sua estratégia de backtest: Em última análise, a maioria dos seus sinais originais precisa ter uma vantagem para justificar filtragens adicionais.
- Os dados utilizados: Ineficiências de mercado são reveladas ao analisar a importância das variáveis de entrada e identificar características menos conhecidas que podem fornecer uma vantagem.
- O modelo usado para o treinamento: Pense se você está tentando resolver um problema de classificação ou de regressão. E escolher os parâmetros de treinamento corretos também é crucial.
- As coisas que você está tentando prever: Em vez de prever diretamente o resultado de uma operação, concentre-se em algo indiretamente relacionado ao resultado final, como demonstro neste artigo.
Ao longo dos meus artigos anteriores, experimentei várias técnicas de aprendizado de máquina acessíveis a traders de varejo. Meu objetivo é inspirar os leitores a adotarem essas ideias e desenvolverem suas próprias abordagens inovadoras, pois a criatividade nesse campo é ilimitada. O aprendizado de máquina não é inerentemente complexo ou inacessível — é uma mentalidade. Trata-se de compreender a vantagem, construir modelos preditivos e testar hipóteses de forma rigorosa. À medida que você continua experimentando, esse entendimento se tornará gradualmente mais claro.
Conclusão
Neste artigo, primeiro apresentamos a motivação para usar LSTM na previsão de tendências, ao mesmo tempo em que explicamos os conceitos por trás do ADX e do LSTM. Em seguida, buscamos dados do MetaTrader 5, processamos esses dados e treinamos o modelo em Python. Depois, percorremos o processo de construção do Expert Advisor e a revisão dos resultados do backtest. Por fim, apresentamos os conceitos de backtest com janela deslizante e backtest com janela expansiva, e concluímos o artigo com algumas reflexões.
Tabela de Arquivos
| Nome do Arquivo | Uso dos Arquivos |
|---|---|
| FileCSV.mqh | O arquivo include para armazenar dados em CSV |
| LSTM_Demonstration.ipynb | O arquivo Python para treinar o modelo LSTM |
| LSTM-TF-XAU.mq5 | O EA de negociação com implementação LSTM |
| OHLC Getter.mq5 | O EA para buscar dados |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16940
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.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Técnicas do Assistente MQL5 que você deve conhecer (Parte 53): Índice de Facilitação de Mercado
Está chegando o novo MetaTrader 5 e MQL5
EA autoaprendente com rede neural baseada em matriz de estados
- 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
Não entendo: onde está o modelo regression2024.onnx propriamente dito no arquivo zip?
Olá an_tar.
Conforme mencionado no artigo, esse tipo de sistema deve ser validado por meio de um backtest de janela móvel. Eu não queria incluir todo o meu modelo treinado desde 2008 para não tornar o arquivo pesado.
É aconselhável usar a estrutura apresentada no artigo para treinar seu próprio modelo para que seja compatível com seu método de validação pessoal.