English Русский 中文 Español Deutsch 日本語
preview
Previsão de Tendência com LSTM para Estratégias de Seguimento de Tendência

Previsão de Tendência com LSTM para Estratégias de Seguimento de Tendência

MetaTrader 5Sistemas de negociação |
21 2
Zhuo Kai Chen
Zhuo Kai Chen

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:

Equação do ADX


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

Diagrama do LSTM

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:

  1. 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.

  2. 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.

  3. 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. 

Em seguida, podemos avaliar a precisão do modelo usando o conjunto de avaliação para verificar seu desempenho em dados não vistos.

    # --- 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.

    Visualização da Avaliação

    Perda de Avaliação: 57.405677795410156

    Ela é calculada por:

    Erro Quadrático Médio

    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. 

    Configuração do Backtest

    Parâmetros do Backtest

    Curva de capital do backtest

    Resultados do backtest

    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.

    Configurações do LSTM

    Parâmetros do LSTM

    Curva de capital do LSTM

    Resultados do LSTM

    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:

    Janela deslizante

    Backtest com Janela Deslizante envolve o uso de uma janela de dados históricos de tamanho fixo que avança ao longo do tempo. À medida que novos pontos de dados são adicionados, os pontos de dados mais antigos são descartados, mantendo um tamanho de janela constante para testar o desempenho da estratégia em diferentes períodos.

    Janela expansiva

    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:

    Backtest com janela deslizante

    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:

    1. 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.
    2. 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.
    3. 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.
    4. 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

    Arquivos anexados |
    LSTM-Trend.zip (132.27 KB)
    Últimos Comentários | Ir para discussão (2)
    an_tar
    an_tar | 17 jul. 2025 em 15:47
    Não entendo: onde está o modelo regression2024.onnx propriamente dito no arquivo zip?
    Zhuo Kai Chen
    Zhuo Kai Chen | 17 jul. 2025 em 16:01
    an_tar #:
    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.

    Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
    Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
    Técnicas do Assistente MQL5 que você deve conhecer (Parte 53): Índice de Facilitação de Mercado Técnicas do Assistente MQL5 que você deve conhecer (Parte 53): Índice de Facilitação de Mercado
    O Índice de Facilitação de Mercado é outro indicador de Bill Williams que tem como objetivo medir a eficiência do movimento de preços em conjunto com o volume. Como sempre, analisamos os vários padrões desse indicador dentro dos limites de uma classe de sinal de montagem do assistente e apresentamos uma variedade de relatórios de teste e análises para os diversos padrões.
    Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
    Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
    EA autoaprendente com rede neural baseada em matriz de estados EA autoaprendente com rede neural baseada em matriz de estados
    EA autoaprendente com rede neural baseada em matriz de estados. Combinamos cadeias de Markov com uma rede neural multicamadas MLP, escrita com a biblioteca ALGLIB MQL5. Como cadeias de Markov e redes neurais podem ser combinadas para a previsão no Forex?