English Русский 中文 Español Deutsch 日本語
preview
Automatizando Estratégias de Trading em MQL5 (Parte 7): Construindo um EA de Grid Trading com Escalonamento Dinâmico de Lote

Automatizando Estratégias de Trading em MQL5 (Parte 7): Construindo um EA de Grid Trading com Escalonamento Dinâmico de Lote

MetaTrader 5Negociação |
24 12
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior (Parte 6), desenvolvemos um Sistema Automatizado de Detecção de Order Block em MetaQuotes Language 5 (MQL5). Agora, na Parte 7, focamos no grid trading, uma estratégia que posiciona negociações em intervalos fixos de preço, combinada com escalonamento dinâmico de lote para otimizar risco e retorno. Essa abordagem adapta o dimensionamento das posições com base nas condições de mercado, visando aumentar a lucratividade e a gestão de risco. Abordaremos:

  1. Plano Estratégico
  2. Implementação em MQL5
  3. Backtesting
  4. Conclusão

Ao final, você terá um programa de Grid Trading totalmente funcional com escalonamento dinâmico de lote, pronto para testes e otimização. Vamos começar!


Plano Estratégico

O grid trading é uma abordagem sistemática que posiciona ordens de compra e venda em intervalos de preço predeterminados, permitindo que os traders capitalizem sobre as flutuações do mercado sem exigir previsões precisas de tendência. Essa estratégia se beneficia da volatilidade do mercado ao abrir e fechar negociações continuamente dentro de uma faixa de preço definida. Para aprimorar seu desempenho, integraremos o escalonamento dinâmico de lote, que ajustará os tamanhos das posições com base em condições predefinidas, como saldo da conta, volatilidade ou resultados de negociações anteriores. Nosso sistema de Grid Trading operará com os seguintes componentes principais:

  • Estrutura do Grid – Definiremos o espaçamento entre as ordens.
  • Regras de Entrada e Execução – Determinaremos quando abrir negociações no grid com base em distâncias fixas utilizando uma estratégia com o indicador Média Móvel.
  • Escalonamento Dinâmico de Lote – Implementaremos um mecanismo adaptativo de dimensionamento de lote que ajusta os tamanhos das posições com base nas condições de mercado ou parâmetros de risco predefinidos.
  • Gestão de Negociações – Incorporaremos stop-loss, take-profit e mecanismos opcionais de breakeven para gerenciar o risco de forma eficaz.
  • Estratégia de Saída – Desenvolveremos uma lógica para fechar posições com base em metas de lucro, limites de risco ou reversões de tendência.

Em resumo, aqui está a visualização completa do blueprint da estratégia para facilitar o entendimento.

LAYOUT DO GRID

Ao combinar um sistema de grid estruturado com dimensionamento adaptativo de lote, criaremos um EA que maximiza os retornos enquanto gerencia o risco de forma eficaz. Em seguida, implementaremos esses conceitos em MQL5.


Implementação em MQL5

Para criar o programa em MQL5, abra o MetaEditor, vá até o Navigator, localize a pasta Indicators, clique na aba "New" e siga as instruções para criar o arquivo. Após criá-lo, no ambiente de codificação, precisaremos declarar algumas variáveis globais que utilizaremos ao longo do programa.

//+------------------------------------------------------------------+
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA trades based on Grid Strategy"
#property strict

#include <Trade/Trade.mqh>                     //--- Include trading library
CTrade obj_Trade;                            //--- Trading object instance

//--- Closure Mode Enumeration and Inputs
enum ClosureMode {
   CLOSE_BY_PROFIT,      //--- Use total profit (in currency) to close positions
   CLOSE_BY_POINTS       //--- Use a points threshold from breakeven to close positions
};

input group "General EA Inputs"
input ClosureMode closureMode = CLOSE_BY_POINTS;    //Select closure mode

double breakevenPoints = 50 * _Point;                 //--- Points offset to add/subtract to/from breakeven

//--- Global Variables
double TakeProfit;                           //--- Current take profit level
double initialLotsize      = 0.1;            //--- Initial lot size for the first trade
double takeProfitPts       = 200 * _Point;    //--- Take profit distance in points
double profitTotal_inCurrency = 100;          //--- Profit target (in currency) to close positions

double gridSize;                             //--- Price level at which grid orders are triggered
double gridSize_Spacing   = 500 * _Point;      //--- Grid spacing in points
double LotSize;                              //--- Current lot size (increased with grid orders)

bool isTradeAllowed      = true;              //--- Flag to allow trade on a new bar
int totalBars            = 0;                 //--- Count of bars seen so far
int handle;                                  //--- Handle for the Moving Average indicator
double maData[];                             //--- Array for Moving Average data

Aqui, incluímos a biblioteca "Trade/Trade.mqh" utilizando #include e instanciamos o objeto "obj_Trade" para gerenciar nossas negociações. Definimos uma enumeração "ClosureMode" com opções para fechar posições e configuramos entradas do usuário como "closureMode" e "breakevenPoints". Em seguida, declaramos variáveis para gerenciar nossos níveis de take profit, tamanho de lote inicial, espaçamento do grid e escalonamento dinâmico de lote, juntamente com flags e contadores para controle de negociações e dados do indicador de média móvel. Depois, precisamos declarar os protótipos das nossas funções principais que estruturarão o programa da seguinte forma.

//--- Function Prototypes
void   CheckAndCloseProfitTargets();         //--- Closes all positions if total profit meets target
void   ExecuteInitialTrade(double ask, double bid); //--- Executes the initial BUY/SELL trade (initial positions)
void   ManageGridPositions(double ask, double bid); //--- Adds grid orders when market moves to grid level (grid positions)
void   UpdateMovingAverage();                //--- Updates MA indicator data from its buffer
bool   IsNewBar();                           //--- Checks if a new bar has formed
double CalculateWeightedBreakevenPrice();    //--- Calculates the weighted average entry price for positions
void   CheckBreakevenClose(double ask, double bid); //--- Closes positions if price meets breakeven+/- threshold
void   CloseAllPositions();                  //--- Closes all open positions

Para as funções, implementaremos "CheckAndCloseProfitTargets" para monitorar a lucratividade geral e fechar posições assim que nossa meta for atingida, e "ExecuteInitialTrade" para iniciar a estratégia com a ordem inicial de BUY ou SELL. "ManageGridPositions" adicionará ordens adicionais em intervalos definidos do grid conforme o mercado se move, enquanto "UpdateMovingAverage" garante que os dados do indicador estejam atualizados para a tomada de decisão. "IsNewBar" detecta novas barras para evitar múltiplas negociações no mesmo candle, "CalculateWeightedBreakevenPrice" calcula o preço médio de entrada entre as posições, e "CheckBreakevenClose" utiliza essa informação para encerrar negociações quando condições favoráveis forem atendidas. Por fim, "CloseAllPositions" fechará metodicamente todas as negociações abertas quando necessário.

Após configurar tudo isso no "escopo global", estamos prontos para continuar com a inicialização do programa, que ocorre no manipulador de evento "OnInit".

//+------------------------------------------------------------------+
//--- Expert initialization function
//+------------------------------------------------------------------+
int OnInit(){
   //--- Initialize the Moving Average indicator (Period: 21, SMA, Price: Close)
   handle = iMA(_Symbol, _Period, 21, 0, MODE_SMA, PRICE_CLOSE);
   if (handle == INVALID_HANDLE){
      Print("ERROR: UNABLE TO INITIALIZE THE INDICATOR. REVERTING NOW!");
      return (INIT_FAILED);
   }
   ArraySetAsSeries(maData, true);            //--- Ensure MA data array is in series order
   return(INIT_SUCCEEDED);
}

Aqui, inicializamos o programa configurando nosso indicador de Média Móvel utilizando a função iMA com período 21, tipo SMA e PRICE_CLOSE para capturar os preços de fechamento. Verificamos se o handle do indicador é válido — se não for (INVALID_HANDLE), imprimimos uma mensagem de erro e retornamos INIT_FAILED para impedir a execução do programa. Por fim, chamamos a função ArraySetAsSeries no array "maData" para garantir que os dados da Média Móvel estejam organizados na ordem correta antes de retornar INIT_SUCCEEDED para confirmar a inicialização bem-sucedida. Uma vez inicializado corretamente, podemos prosseguir para o manipulador de evento OnTick para construir a lógica de abertura e gerenciamento das posições.

//+------------------------------------------------------------------+
//--- Expert tick function
//+------------------------------------------------------------------+
void OnTick(){
   
   //--- Allow new trade signals on a new bar
   if(IsNewBar())
      isTradeAllowed = true;
   
   //--- Update the Moving Average data
   UpdateMovingAverage();
   
}

Como não queremos verificar negociações a cada tick, mas sim a cada barra, chamamos a função "IsNewBar" e a utilizamos para definir a variável "isTradeAllowed" como true quando uma nova barra é formada. Em seguida, chamamos a função responsável por obter os valores da média móvel. As definições das funções são as seguintes.

//+-------------------------------------------------------------------+
//--- Function: UpdateMovingAverage
//--- Description: Copies the latest data from the MA indicator buffer.
//+-------------------------------------------------------------------+
void UpdateMovingAverage(){
   if(CopyBuffer(handle, 0, 1, 3, maData) < 0)
      Print("Error: Unable to update Moving Average data.");
}
  
//+-------------------------------------------------------------------+
//--- Function: IsNewBar
//--- Description: Checks if a new bar has been formed.
//+-------------------------------------------------------------------+
bool IsNewBar(){
   int bars = iBars(_Symbol, _Period);
   if(bars > totalBars){
      totalBars = bars;
      return true;
   }
   return false;
}

Aqui, implementamos "UpdateMovingAverage" para atualizar os dados do indicador copiando os valores mais recentes do buffer da Média Móvel utilizando a função CopyBuffer. Se essa chamada de função falhar, imprimimos uma mensagem de erro para alertar que a atualização não foi bem-sucedida. Na função "IsNewBar", verificamos se uma nova barra foi formada comparando o número atual de barras, obtido por meio da função iBars, com nossa contagem armazenada em "totalBars"; se o número tiver aumentado, atualizamos "totalBars" e retornamos "true", indicando que uma nova barra está disponível para decisões de negociação. Em seguida, continuamos com a função de tick para executar negociações com base nos valores do indicador recuperados.

//--- Reset lot size if no positions are open
if(PositionsTotal() == 0)
   LotSize = initialLotsize;

//--- Retrieve recent bar prices for trade signal logic
double low1  = iLow(_Symbol, _Period, 1);
double low2  = iLow(_Symbol, _Period, 2);
double high1 = iHigh(_Symbol, _Period, 1);
double high2 = iHigh(_Symbol, _Period, 2);

//--- Get current Ask and Bid prices (normalized)
double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);

//--- If no positions are open and trading is allowed, check for an initial trade signal
if(PositionsTotal() == 0 && isTradeAllowed){
   ExecuteInitialTrade(ask, bid);
}

Aqui, primeiro verificamos se não há posições abertas utilizando a função PositionsTotal e, em caso afirmativo, redefinimos o "LotSize" para "initialLotsize". Em seguida, recuperamos os preços das barras recentes chamando iLow e iHigh para capturar as máximas e mínimas das duas barras anteriores, o que ajudará a formar nossos sinais de negociação. Depois, obtemos os preços atuais de "ask" e "bid" utilizando SymbolInfoDouble, normalizando-os com NormalizeDouble para garantir precisão. Por fim, se a negociação estiver permitida (conforme indicado por "isTradeAllowed") e não houver posições abertas no momento, chamamos a função "ExecuteInitialTrade" com os preços de "ask" e "bid" para iniciar nossa primeira negociação. A definição da função é apresentada abaixo.

//+---------------------------------------------------------------------------+
//--- Function: ExecuteInitialTrade
//--- Description: Executes the initial BUY or SELL trade based on MA criteria.
//---              (These are considered "initial positions.")
//+---------------------------------------------------------------------------+
void ExecuteInitialTrade(double ask, double bid){
   //--- BUY Signal: previous bar's low above MA and bar before that below MA
   if(iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){
      gridSize = ask - gridSize_Spacing;     //--- Set grid trigger below current ask
      TakeProfit = ask + takeProfitPts;      //--- Set TP for BUY
      if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Initial Buy"))
         Print("Initial BUY order executed at ", ask, " with LotSize: ", LotSize);
      else
         Print("Initial BUY order failed at ", ask);
      isTradeAllowed = false;
   }
   //--- SELL Signal: previous bar's high below MA and bar before that above MA
   else if(iHigh(_Symbol, _Period, 1) < maData[1] && iHigh(_Symbol, _Period, 2) > maData[1]){
      gridSize = bid + gridSize_Spacing;     //--- Set grid trigger above current bid
      TakeProfit = bid - takeProfitPts;      //--- Set TP for SELL
      if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Initial Sell"))
         Print("Initial SELL order executed at ", bid, " with LotSize: ", LotSize);
      else
         Print("Initial SELL order failed at ", bid);
      isTradeAllowed = false;
   }
}

Aqui, implementamos a função "ExecuteInitialTrade" para abrir uma negociação inicial com base nos valores de "maData". Recuperamos os preços mínimos das duas barras anteriores utilizando a função iLow e os preços máximos utilizando a função iHigh. Para um sinal de BUY, verificamos se a mínima da barra anterior está acima de "maData", enquanto a barra anterior a essa estava abaixo. Se essa condição for atendida, definimos "gridSize" abaixo do "ask" atual utilizando "gridSize_Spacing" para determinar o próximo nível do grid, calculamos "TakeProfit" adicionando "takeProfitPts" ao "ask" e executamos uma negociação de BUY utilizando o método "obj_Trade.Buy".

Para um sinal de SELL, verificamos se a máxima da barra anterior está abaixo de "maData", enquanto a barra anterior a essa estava acima. Se for verdadeiro, definimos "gridSize" acima do "bid", determinamos "TakeProfit" subtraindo "takeProfitPts" do "bid" e tentamos executar uma negociação de SELL utilizando "obj_Trade.Sell". Uma vez que uma negociação é executada, definimos "isTradeAllowed" como false para evitar entradas adicionais até que novas condições sejam atendidas. Aqui está o resultado.

EXECUÇÃO DE NEGOCIAÇÕES

Pela imagem, podemos ver que temos as negociações confirmadas sendo executadas. Agora precisamos avançar para gerenciar as negociações abrindo as posições do grid.

//--- If positions exist, manage grid orders
if(PositionsTotal() > 0){
   ManageGridPositions(ask, bid);
}

Verificamos se há posições abertas utilizando a função PositionsTotal. Se o número de posições for maior que zero, chamamos a função "ManageGridPositions" para gerenciar negociações adicionais do grid. A função recebe "ask" e "bid" como parâmetros para determinar os níveis de preço apropriados para posicionar novas ordens do grid com base no movimento do mercado. A implementação do trecho de código da função está abaixo.

//+------------------------------------------------------------------------+
//--- Function: ManageGridPositions
//--- Description: When an initial position exists, grid orders are added 
//---              if the market moves to the grid level. (These orders are 
//---              considered "grid positions.") The lot size is doubled 
//---              with each grid order.
//+------------------------------------------------------------------------+
void ManageGridPositions(double ask, double bid){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         int positionType = (int)PositionGetInteger(POSITION_TYPE);
         //--- Grid management for BUY positions
         if(positionType == POSITION_TYPE_BUY){
            if(ask <= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Grid Position BUY"))
                  Print("Grid BUY order executed at ", ask, " with LotSize: ", LotSize);
               else
                  Print("Grid BUY order failed at ", ask);
               gridSize = ask - gridSize_Spacing; //--- Update grid trigger
            }
         }
         //--- Grid management for SELL positions
         else if(positionType == POSITION_TYPE_SELL){
            if(bid >= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Grid Position SELL"))
                  Print("Grid SELL order executed at ", bid, " with LotSize: ", LotSize);
               else
                  Print("Grid SELL order failed at ", bid);
               gridSize = bid + gridSize_Spacing; //--- Update grid trigger
            }
         }
      }
   }
}

Implementamos a função "ManageGridPositions" para gerenciar as ordens do grid. Iteramos por todas as posições abertas em ordem reversa utilizando um for loop e recuperamos o ticket de cada posição com a função PositionGetTicket. Em seguida, selecionamos a posição usando PositionSelectByTicket e determinamos se é uma negociação BUY ou SELL utilizando PositionGetInteger com o parâmetro POSITION_TYPE. Se a posição for BUY, verificamos se o preço de mercado "ask" atingiu ou caiu abaixo de "gridSize". Se for verdadeiro, dobramos o "LotSize" e executamos uma nova ordem BUY do grid utilizando a função "obj_Trade.Buy". Se a ordem for bem-sucedida, imprimimos uma mensagem de confirmação; caso contrário, imprimimos uma mensagem de erro. Em seguida, atualizamos "gridSize" para o próximo nível do grid abaixo.

Da mesma forma, se a posição for SELL, verificamos se o "bid" atingiu ou excedeu "gridSize". Se for verdadeiro, dobramos o "LotSize" e posicionamos uma nova ordem SELL do grid utilizando "obj_Trade.Sell". O gatilho do grid "gridSize" é então atualizado para o próximo nível acima. Após abrir as posições do grid, precisamos acompanhar e gerenciar as posições fechando-as assim que atingirmos o definido conforme abaixo.

//--- Check if total profit meets the target (only used if closureMode == CLOSE_BY_PROFIT)
if(closureMode == CLOSE_BY_PROFIT)
   CheckAndCloseProfitTargets();

Se "closureMode" estiver configurado como "CLOSE_BY_PROFIT", chamamos a função "CheckAndCloseProfitTargets" para verificar se o lucro total atingiu a meta predefinida e fechar todas as posições adequadamente. A declaração da função está abaixo.

//+----------------------------------------------------------------------------+
//--- Function: CheckAndCloseProfitTargets
//--- Description: Closes all positions if the combined profit meets or exceeds
//---              the user-defined profit target.
//+----------------------------------------------------------------------------+
void CheckAndCloseProfitTargets(){
   if(PositionsTotal() > 1){
      double totalProfit = 0;
      for(int i = PositionsTotal()-1; i >= 0; i--){
         ulong tkt = PositionGetTicket(i);
         if(PositionSelectByTicket(tkt))
            totalProfit += PositionGetDouble(POSITION_PROFIT);
      }
      if(totalProfit >= profitTotal_inCurrency){
         Print("Profit target reached (", totalProfit, "). Closing all positions.");
         CloseAllPositions();
      }
   }
}

Para garantir que todas as posições sejam fechadas se o lucro total acumulado atingir ou exceder a meta de lucro predefinida, primeiro verificamos se há mais de uma posição aberta utilizando PositionsTotal. Inicializamos "totalProfit" para acompanhar o lucro combinado de todas as posições. Em seguida, fazemos um loop por todas as posições abertas, recuperando o ticket de cada posição usando PositionGetTicket e selecionando-a com PositionSelectByTicket. Para cada posição selecionada, recuperamos seu lucro utilizando PositionGetDouble com o parâmetro POSITION_PROFIT e o adicionamos a "totalProfit". Se "totalProfit" atingir ou exceder "profitTotal_inCurrency", imprimimos uma mensagem indicando que a meta de lucro foi alcançada e chamamos a função "CloseAllPositions", cuja definição está abaixo, para fechar todas as negociações abertas.

//+------------------------------------------------------------------+
//--- Function: CloseAllPositions
//--- Description: Iterates through and closes all open positions.
//+------------------------------------------------------------------+
void CloseAllPositions(){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong posTkt = PositionGetTicket(i);
      if(PositionSelectByTicket(posTkt)){
         if(obj_Trade.PositionClose(posTkt))
            Print("Closed position ticket: ", posTkt);
         else
            Print("Failed to close position ticket: ", posTkt);
      }
   }
}

A função simplesmente itera sobre todas as posições abertas e, para cada posição selecionada, ela é fechada utilizando o método "obj_Trade.PositionClose". Por fim, definimos a lógica para fechar as posições no breakeven.

//--- If using CLOSE_BY_POINTS and more than one position exists (i.e. grid), check breakeven closure
if(closureMode == CLOSE_BY_POINTS && PositionsTotal() > 1)
   CheckBreakevenClose(ask, bid);

Se "closureMode" estiver configurado como "CLOSE_BY_POINTS" e houver mais de uma posição aberta, chamamos a função "CheckBreakevenClose" com os parâmetros "ask" e "bid" para determinar se o preço atingiu o limite de breakeven, permitindo que as posições sejam fechadas com base em pontos predefinidos a partir do breakeven. A seguir está a definição da função.

//+----------------------------------------------------------------------------+
//--- Function: CalculateWeightedBreakevenPrice
//--- Description: Calculates the weighted average entry price (breakeven)
//---              of all open positions (assumed to be in the same direction).
//+----------------------------------------------------------------------------+
double CalculateWeightedBreakevenPrice(){
   double totalCost = 0;
   double totalVolume = 0;
   int posType = -1;
   //--- Determine the type from the first position
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   //--- Sum the cost and volume for positions matching the type
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         if(PositionGetInteger(POSITION_TYPE) == posType){
            double price = PositionGetDouble(POSITION_PRICE_OPEN);
            double volume = PositionGetDouble(POSITION_VOLUME);
            totalCost += price * volume;
            totalVolume += volume;
         }
      }
   }
   if(totalVolume > 0)
      return(totalCost / totalVolume);
   else
      return(0);
}
  
//+-----------------------------------------------------------------------------+
//--- Function: CheckBreakevenClose
//--- Description: When using CLOSE_BY_POINTS and multiple positions exist,
//---              calculates the weighted breakeven price and checks if the
//---              current price has moved the specified points in a profitable
//---              direction relative to breakeven. If so, closes all positions.
//+-----------------------------------------------------------------------------+
void CheckBreakevenClose(double ask, double bid){
   //--- Ensure we have more than one position (grid positions)
   if(PositionsTotal() <= 1)
      return;
      
   double weightedBreakeven = CalculateWeightedBreakevenPrice();
   int posType = -1;
   //--- Determine the trade type from one of the positions
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   if(posType == -1)
      return;
      
   //--- For BUY positions, profit when Bid >= breakeven + threshold
   if(posType == POSITION_TYPE_BUY){
      if(bid >= weightedBreakeven + breakevenPoints){
         Print("Closing BUY positions: Bid (", bid, ") >= Breakeven (", weightedBreakeven, ") + ", breakevenPoints);
         CloseAllPositions();
      }
   }
   //--- For SELL positions, profit when Ask <= breakeven - threshold
   else if(posType == POSITION_TYPE_SELL){
      if(ask <= weightedBreakeven - breakevenPoints){
         Print("Closing SELL positions: Ask (", ask, ") <= Breakeven (", weightedBreakeven, ") - ", breakevenPoints);
         CloseAllPositions();
      }
   }
}

Aqui, calculamos o preço de breakeven para todas as posições abertas e determinamos se o preço de mercado se moveu uma distância especificada além dele para fechar posições com lucro. Em "CalculateWeightedBreakevenPrice", calculamos o preço de breakeven ponderado somando o custo total de todas as posições abertas utilizando POSITION_PRICE_OPEN e ponderando-o por "POSITION_VOLUME". Primeiro determinamos o tipo da posição (BUY ou SELL) a partir da primeira posição aberta utilizando POSITION_TYPE. Em seguida, percorremos todas as posições, somando o custo total e o volume das posições que correspondem ao tipo identificado. Se o volume total for maior que zero, retornamos o preço de breakeven ponderado dividindo o custo total pelo volume total. Caso contrário, retornamos zero.

Em "CheckBreakevenClose", primeiro confirmamos que há múltiplas posições abertas utilizando a função PositionsTotal. Em seguida, recuperamos o preço de breakeven ponderado chamando "CalculateWeightedBreakevenPrice". Determinamos o tipo da posição selecionando uma posição e recuperando POSITION_TYPE. Se o tipo for inválido, saímos da função. Para posições BUY, verificamos se o preço "bid" atingiu ou excedeu "weightedBreakeven" mais "breakevenPoints". Se sim, imprimimos uma mensagem e chamamos "CloseAllPositions". Para posições SELL, verificamos se o preço "ask" caiu abaixo de "weightedBreakeven" menos "breakevenPoints". Se essa condição for atendida, também imprimimos uma mensagem e chamamos a função "CloseAllPositions" para garantir os lucros. Após compilar e executar o programa, temos o seguinte resultado.

GRID GIF

Pela visualização, podemos ver que as posições são abertas e gerenciadas por meio do sistema de grid e fechadas quando os níveis de encerramento definidos são atingidos, alcançando assim nosso objetivo de criar um sistema de grid com dimensionamento dinâmico de lote. O que resta é realizar o backtesting do programa, e isso é tratado na próxima seção.


Backtesting

Após testes retrospectivos minuciosos, obtivemos os seguintes resultados.

Gráfico de backtest:

GRÁFICO

Relatório de backtest:

RELATÓRIO

Apresentamos também um vídeo que demonstra todo o backtest da estratégia ao longo de um período de 1 ano, até 2024.



Conclusão

Em conclusão, demonstramos o processo de desenvolvimento de um Expert Advisor (EA) em MQL5 utilizando uma estratégia dinâmica de grid trading. Ao combinar elementos-chave como posicionamento de ordens em grid, escalonamento dinâmico de lote e gerenciamento direcionado de lucro e breakeven, criamos um sistema que se adapta às flutuações do mercado, visando otimizar a relação risco-retorno e recuperar-se de movimentos adversos de preço.

Aviso legal: Este artigo é apenas para fins educacionais. Negociar envolve risco financeiro significativo, e o comportamento do mercado pode ser altamente imprevisível. Embora as estratégias apresentadas ofereçam uma abordagem estruturada para grid trading, elas não garantem lucratividade futura. Backtesting rigoroso e gerenciamento de risco são essenciais antes de operar ao vivo.

Ao implementar essas técnicas, você pode aprimorar seus sistemas de grid trading, melhorar sua análise de mercado e elevar suas estratégias de negociação algorítmica. Boa sorte em sua jornada no trading!

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17190

Últimos Comentários | Ir para discussão (12)
testtestmio71
testtestmio71 | 4 abr. 2025 em 15:19

está ok.....best EA .

As 4 linhas são confusas para um novato

Allan Munene Mutiiria
Allan Munene Mutiiria | 4 abr. 2025 em 16:35
testtestmio71 #:

está ok.....best EA .

As 4 linhas são confusas para um novato

Certo

Roman Shiredchenko
Roman Shiredchenko | 20 out. 2025 em 02:54

ótimo artigo - muito obrigado... Estou estudando a abordagem de negociação que usarei para editar minhas próprias negociações com símbolos de hashedge personalizados!

verificando....

Allan Munene Mutiiria
Allan Munene Mutiiria | 20 out. 2025 em 14:12
Roman Shiredchenko símbolos de hashedge personalizados!

verificando....

Muito bom. Seja bem-vindo.
Allan Munene Mutiiria
Allan Munene Mutiiria | 30 out. 2025 em 11:32
Roman Shiredchenko símbolos de hash personalizados em minhas próprias negociações!

examine....

Obrigado e seja bem-vindo.
Redes neurais em trading: Pipeline inteligente de previsões (Conclusão) Redes neurais em trading: Pipeline inteligente de previsões (Conclusão)
Este artigo mostrará de forma envolvente como o embedding SwiGLU revela padrões ocultos do mercado, e como a mistura esparsa de especialistas dentro do Decoder-Only Transformer torna as previsões mais precisas com custos computacionais razoáveis. Analisamos detalhadamente a integração do Time-MoE em MQL5 e OpenCL, descrevendo passo a passo a configuração e o treinamento do modelo.
Desenvolvimento do Toolkit de Análise de Price Action (Parte 13): Ferramenta RSI Sentinel Desenvolvimento do Toolkit de Análise de Price Action (Parte 13): Ferramenta RSI Sentinel
A análise de price action pode ser realizada de forma eficaz por meio da identificação de divergências, utilizando indicadores técnicos como o RSI para fornecer sinais cruciais de confirmação. Neste conteúdo, é explicado como a análise automatizada de divergência do RSI pode identificar continuações de tendência e reversões, oferecendo percepções valiosas sobre o sentimento do mercado.
Identificação e classificação de padrões fractais por meio de aprendizado de máquina Identificação e classificação de padrões fractais por meio de aprendizado de máquina
Neste artigo abordaremos o tema intrigante da análise fractal e da previsão de mercados por meio de aprendizado de máquina. Estes são apenas os primeiros passos no caminho para o estudo das diversas estruturas fractais que se formam nos gráficos de cotações financeiras. Utilizaremos a correlação para a busca de padrões e o algoritmo CatBoost para a classificação desses padrões.
Criando um Painel Administrador de Trading em MQL5 (Parte IX): Organização de Código (II): Modularização Criando um Painel Administrador de Trading em MQL5 (Parte IX): Organização de Código (II): Modularização
Nesta discussão, damos um passo adiante ao dividir nosso programa MQL5 em módulos menores e mais gerenciáveis. Esses componentes modulares serão então integrados ao programa principal, melhorando sua organização e capacidade de manutenção. Essa abordagem simplifica a estrutura do programa principal e torna os componentes individuais reutilizáveis em outros Expert Advisors (EAs) e no desenvolvimento de indicadores. Ao adotar esse design modular, criamos uma base sólida para melhorias futuras, beneficiando tanto nosso projeto quanto a comunidade mais ampla de desenvolvedores.