English Русский 中文 Español Deutsch 日本語
preview
Automatizando Estratégias de Trading em MQL5 (Parte 12): Implementação da Estratégia Mitigation Order Blocks (MOB)

Automatizando Estratégias de Trading em MQL5 (Parte 12): Implementação da Estratégia Mitigation Order Blocks (MOB)

MetaTrader 5Negociação |
185 7
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

Em nosso artigo anterior (Parte 11), construímos um sistema de trading em grade multinível em MetaQuotes Language 5 (MQL5) para aproveitar as flutuações do mercado. Agora, na Parte 12, focamos na implementação da estratégia Mitigation Order Blocks (MOB), um conceito de Smart Money que identifica zonas-chave de preço onde ordens institucionais são mitigadas antes de movimentos significativos do mercado. Abordaremos os seguintes tópicos:

  1. Blueprint da Estratégia
  2. Implementação em MQL5
  3. Backtesting
  4. Conclusão

Ao final deste artigo, você terá um sistema de trading totalmente automatizado de mitigation order blocks pronto para operar. Vamos começar!


Blueprint da Estratégia

Para implementar a estratégia Mitigation Order Blocks Strategy, desenvolveremos um sistema automatizado que detecta, valida e executa operações com base em eventos de mitigação de blocos de ordens. A estratégia se concentrará na identificação de zonas de preço institucionais onde a liquidez é absorvida antes da continuação da tendência. Nosso sistema incorporará condições precisas para entrada, posicionamento de stop-loss e gerenciamento das operações para garantir eficiência e precisão. Estruturaremos o desenvolvimento da seguinte forma:

  • Identificação de Order Blocks – O sistema analisará a ação de preço histórica para detectar blocos de ordens (order blocks) de alta e de baixa, filtrando zonas fracas com base em volatilidade, capturas de liquidez e desequilíbrio de preço.
  • Validação da Mitigação – Programaremos condições que confirmam um evento de mitigação válido, garantindo que o preço revisite o bloco de ordens e reaja com sinais de rejeição, como pavios ou mudanças de momentum.
  • Confirmação da Estrutura de Mercado – O EA analisará tendências em timeframes maiores e varreduras de liquidez para garantir que a mitigação identificada esteja alinhada com o fluxo geral do mercado.
  • Regras de Execução de Operações – Uma vez confirmada a mitigação, o sistema definirá pontos de entrada precisos, calculará dinamicamente os níveis de stop-loss com base na estrutura do bloco de ordens e definirá alvos de take-profit com base em parâmetros de risco-retorno.
  • Gestão de Risco e Capital – A estratégia integrará dimensionamento de posição, proteção contra drawdown e estratégias de saída para gerenciar o risco das operações de forma eficaz.

Em resumo, aqui está uma visualização geral da estratégia.

Mitigation Order Block


Implementação em MQL5

Para criar o programa em MQL5, abra o MetaEditor, vá até o Navigador, localize a pasta Indicadores, clique na aba "Novo" e siga as instruções para criar o arquivo.quando o preço permanece dentro desse intervalo, o intervalo é estendido Após sua criação, 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 Mitigation Order Blocks Strategy"
#property strict

//--- Include the trade library for managing positions
#include <Trade/Trade.mqh>
CTrade obj_Trade;

Iniciamos a implementação incluindo a biblioteca de trade com "#include <Trade/Trade.mqh>", que fornece funções integradas para gerenciar operações de trade. Em seguida, inicializamos o objeto de trade "obj_Trade" utilizando a classe "CTrade", permitindo que o Expert Advisor execute ordens de compra e venda programaticamente. Essa configuração garantirá que a execução das operações seja tratada de forma eficiente sem exigir intervenção manual. Em seguida, podemos fornecer alguns inputs para que o usuário possa alterar e controlar o comportamento a partir da interface do usuário (UI).

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input double tradeLotSize = 0.01;           // Trade size for each position
input bool enableTrading = true;            // Toggle to allow or disable trading
input bool enableTrailingStop = true;       // Toggle to enable or disable trailing stop
input double trailingStopPoints = 30;       // Distance in points for trailing stop
input double minProfitToTrail = 50;         // Minimum profit in points before trailing starts (not used yet)
input int uniqueMagicNumber = 12345;        // Unique identifier for EA trades
input int consolidationBars = 7;            // Number of bars to check for consolidation
input double maxConsolidationSpread = 50;   // Maximum allowed spread in points for consolidation
input int barsToWaitAfterBreakout = 3;      // Bars to wait after breakout before checking impulse
input double impulseMultiplier = 1.0;       // Multiplier for detecting impulsive moves
input double stopLossDistance = 1500;       // Stop loss distance in points
input double takeProfitDistance = 1500;     // Take profit distance in points
input color bullishOrderBlockColor = clrGreen;    // Color for bullish order blocks
input color bearishOrderBlockColor = clrRed;     // Color for bearish order blocks
input color mitigatedOrderBlockColor = clrGray;  // Color for mitigated order blocks
input color labelTextColor = clrBlack;           // Color for text labels

Aqui, definimos parâmetros de input para configurar o comportamento do programa. "tradeLotSize" define o tamanho da posição, enquanto "enableTrading" e "enableTrailingStop" controlam a execução e o trailing stop, com "trailingStopPoints" e "minProfitToTrail" refinando a lógica do stop. "uniqueMagicNumber" identifica as operações, e a consolidação é detectada usando "consolidationBars" e "maxConsolidationSpread". Breakouts são confirmados com "barsToWaitAfterBreakout" e "impulseMultiplier". "stopLossDistance" e "takeProfitDistance" gerenciam o risco, enquanto "bullishOrderBlockColor", "bearishOrderBlockColor", "mitigatedOrderBlockColor" e "labelTextColor" tratam dos elementos visuais do gráfico.

Por fim, precisamos definir algumas variáveis globais que utilizaremos para o controle geral do sistema.

//--- Struct to store price and index for highs and lows
struct PriceAndIndex {
   double price;  // Price value
   int    index;  // Bar index where this price occurs
};

//--- Global variables for tracking market state
PriceAndIndex rangeHighestHigh = {0, 0};    // Highest high in the consolidation range
PriceAndIndex rangeLowestLow = {0, 0};      // Lowest low in the consolidation range
bool isBreakoutDetected = false;            // Flag for when a breakout occurs
double lastImpulseLow = 0.0;                // Low price after breakout for impulse check
double lastImpulseHigh = 0.0;               // High price after breakout for impulse check
int breakoutBarNumber = -1;                 // Bar index where breakout happened
datetime breakoutTimestamp = 0;             // Time of the breakout
string orderBlockNames[];                   // Array of order block object names
datetime orderBlockEndTimes[];              // Array of order block end times
bool orderBlockMitigatedStatus[];           // Array tracking if order blocks are mitigated
bool isBullishImpulse = false;              // Flag for bullish impulsive move
bool isBearishImpulse = false;              // Flag for bearish impulsive move

#define OB_Prefix "OB REC "     // Prefix for order block object names

Primeiro, definimos a struct "PriceAndIndex", que armazena um valor de "price" e o "index" da barra onde esse preço ocorre. Essa estrutura será útil para rastrear pontos específicos de preço dentro de um intervalo definido. As variáveis globais gerenciam aspectos-chave da estrutura de mercado e da detecção de breakouts. "rangeHighestHigh" e "rangeLowestLow" armazenarão, respectivamente, os maiores e menores preços dentro do intervalo de consolidação, ajudando a definir os limites de potenciais blocos de ordens. "isBreakoutDetected" atuará como um sinalizador para indicar quando um breakout ocorreu, enquanto "lastImpulseLow" e "lastImpulseHigh" armazenarão o primeiro mínimo e máximo após um breakout, utilizados para confirmar movimentos impulsivos.

"breakoutBarNumber" registrará o índice da barra onde o breakout ocorreu, e "breakoutTimestamp" armazenará o momento exato do evento de breakout. Os arrays "orderBlockNames", "orderBlockEndTimes" e "orderBlockMitigatedStatus" lidarão com a identificação, tempo de vida e acompanhamento da mitigação dos blocos de ordens. As flags booleanas "isBullishImpulse" e "isBearishImpulse" determinam se o movimento de breakout se qualifica como um impulso de alta ou de baixa. Por fim, "OB_Prefix" é um prefixo de string predefinido pela macro #define, utilizado ao nomear objetos de blocos de ordens, garantindo consistência na representação gráfica. Com as variáveis definidas, estamos prontos para iniciar a lógica do programa.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   //--- Set the magic number for the trade object to identify EA trades
   obj_Trade.SetExpertMagicNumber(uniqueMagicNumber);
   return(INIT_SUCCEEDED);
}

Aqui, inicializamos o Expert Advisor no manipulador de evento OnInit. Definimos o número mágico do expert usando o método "SetExpertMagicNumber", garantindo que todas as operações executadas pelo nosso EA sejam identificadas de forma única, evitando conflitos com outras operações. Essa etapa é crucial para rastrear e gerenciar apenas as operações abertas pela nossa estratégia. Após a conclusão da inicialização, retornamos INIT_SUCCEEDED, confirmando que nosso programa está pronto para operar. Em seguida, podemos avançar para o manipulador de evento principal OnTick, onde está nossa lógica principal de controle.

//+------------------------------------------------------------------+
//| Expert OnTick function                                           |
//+------------------------------------------------------------------+
void OnTick() {

   //--- Check for a new bar to process logic only once per bar
   static bool isNewBar = false;
   int currentBarCount = iBars(_Symbol, _Period);
   static int previousBarCount = currentBarCount;
   if (previousBarCount == currentBarCount) {
      isNewBar = false;
   } else if (previousBarCount != currentBarCount) {
      isNewBar = true;
      previousBarCount = currentBarCount;
   }

   //--- Exit if not a new bar to avoid redundant processing
   if (!isNewBar)
      return;
   //---
}

Para garantir que processemos dados a cada barra e não a cada tick, na função OnTick, que é executada a cada novo tick recebido, utilizamos a função iBars para obter o número total de barras no gráfico e armazená-lo em "currentBarCount". Em seguida, comparamos com "previousBarCount", e se forem iguais, "isNewBar" permanece false, evitando processamento redundante. Se uma nova barra for detectada, atualizamos "previousBarCount" e definimos "isNewBar" como true, permitindo que a lógica da estratégia seja executada. Por fim, se "isNewBar" for false, retornamos antecipadamente, otimizando o desempenho ao evitar cálculos desnecessários. Se for uma nova barra, continuamos a procurar consolidações.

//--- Define the starting bar index for consolidation checks
int startBarIndex = 1;

//--- Check for consolidation or extend the existing range
if (!isBreakoutDetected) {
   if (rangeHighestHigh.price == 0 && rangeLowestLow.price == 0) {
      //--- Check if bars are in a tight consolidation range
      bool isConsolidated = true;
      for (int i = startBarIndex; i < startBarIndex + consolidationBars - 1; i++) {
         if (MathAbs(high(i) - high(i + 1)) > maxConsolidationSpread * Point()) {
            isConsolidated = false;
            break;
         }
         if (MathAbs(low(i) - low(i + 1)) > maxConsolidationSpread * Point()) {
            isConsolidated = false;
            break;
         }
      }
      if (isConsolidated) {
         //--- Find the highest high in the consolidation range
         rangeHighestHigh.price = high(startBarIndex);
         rangeHighestHigh.index = startBarIndex;
         for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) {
            if (high(i) > rangeHighestHigh.price) {
               rangeHighestHigh.price = high(i);
               rangeHighestHigh.index = i;
            }
         }
         //--- Find the lowest low in the consolidation range
         rangeLowestLow.price = low(startBarIndex);
         rangeLowestLow.index = startBarIndex;
         for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) {
            if (low(i) < rangeLowestLow.price) {
               rangeLowestLow.price = low(i);
               rangeLowestLow.index = i;
            }
         }
         //--- Log the established consolidation range
         Print("Consolidation range established: Highest High = ", rangeHighestHigh.price,
               " at index ", rangeHighestHigh.index,
               " and Lowest Low = ", rangeLowestLow.price,
               " at index ", rangeLowestLow.index);
      }
   } else {
      //--- Check if the current bar extends the existing range
      double currentHigh = high(1);
      double currentLow = low(1);
      if (currentHigh <= rangeHighestHigh.price && currentLow >= rangeLowestLow.price) {
         Print("Range extended: High = ", currentHigh, ", Low = ", currentLow);
      } else {
         Print("No extension: Bar outside range.");
      }
   }
}

Aqui, definimos e estabelecemos o intervalo de consolidação analisando os movimentos recentes de preço. Começamos definindo "startBarIndex" como 1, determinando o ponto inicial para nossas verificações de consolidação. Se ainda não detectamos um breakout, conforme indicado por "isBreakoutDetected", prosseguimos para avaliar se o mercado está em uma fase de consolidação estreita. Iteramos pelas últimas barras na quantidade definida por "consolidationBars", utilizando a função MathAbs para medir as diferenças absolutas entre máximas e mínimas consecutivas. Se todas as diferenças permanecerem dentro de "maxConsolidationSpread", confirmamos a consolidação.

Uma vez que a consolidação é detectada, determinamos a máxima mais alta e a mínima mais baixa dentro do intervalo. Inicializamos "rangeHighestHigh" e "rangeLowestLow" com a máxima e a mínima de "startBarIndex", então iteramos pelo intervalo para atualizar esses valores sempre que encontrarmos uma nova máxima mais alta ou mínima mais baixa. Esses valores definem os limites da consolidação.

Se o intervalo de consolidação já estiver estabelecido, verificamos se a barra atual estende o intervalo existente. Recuperamos "currentHigh" e "currentLow" usando as funções "high" e "low" e os comparamos com "rangeHighestHigh.price" e "rangeLowestLow.price". Se o preço permanecer dentro do intervalo, exibimos uma mensagem indicando a extensão do intervalo usando a função Print. Caso contrário, exibimos que não houve extensão, sinalizando um possível cenário de breakout. As funções de preço personalizadas são as seguintes.

//+------------------------------------------------------------------+
//| Price data accessors                                                 |
//+------------------------------------------------------------------+
double high(int index) { return iHigh(_Symbol, _Period, index); }   //--- Get high price of a bar
double low(int index) { return iLow(_Symbol, _Period, index); }     //--- Get low price of a bar
double open(int index) { return iOpen(_Symbol, _Period, index); }   //--- Get open price of a bar
double close(int index) { return iClose(_Symbol, _Period, index); } //--- Get close price of a bar
datetime time(int index) { return iTime(_Symbol, _Period, index); } //--- Get time of a bar

Essas funções personalizadas nos ajudam a recuperar dados de preço. A função "high" utiliza iHigh para retornar o preço máximo de uma barra em um "index" especificado, enquanto a função "low" chama iLow para obter o preço mínimo correspondente. A função "open" obtém o preço de abertura usando iOpen, e a função "close" recupera o preço de fechamento por meio de iClose. Além disso, a função "time" utiliza iTime para retornar o timestamp da barra fornecida. Ao executar o programa, temos o seguinte resultado.

Confirmação de Consolidação

Pela imagem, podemos ver que, uma vez que o intervalo de preço é estabelecido e quando o preço permanece dentro desse intervalo, o intervalo é estendido até que ocorra uma ruptura do intervalo. Agora, precisamos detectar um breakout no intervalo de preço confirmado. Alcançamos isso usando a seguinte lógica.

//--- Detect a breakout from the consolidation range
if (rangeHighestHigh.price > 0 && rangeLowestLow.price > 0) {
   double currentClosePrice = close(1);
   if (currentClosePrice > rangeHighestHigh.price) {
      Print("Upward breakout at ", currentClosePrice, " > ", rangeHighestHigh.price);
      isBreakoutDetected = true;
   } else if (currentClosePrice < rangeLowestLow.price) {
      Print("Downward breakout at ", currentClosePrice, " < ", rangeLowestLow.price);
      isBreakoutDetected = true;
   }
}

//--- Reset state after a breakout is detected
if (isBreakoutDetected) {
   Print("Breakout detected. Resetting for the next range.");
   breakoutBarNumber = 1;
   breakoutTimestamp = TimeCurrent();
   lastImpulseHigh = rangeHighestHigh.price;
   lastImpulseLow = rangeLowestLow.price;

   isBreakoutDetected = false;
   rangeHighestHigh.price = 0;
   rangeHighestHigh.index = 0;
   rangeLowestLow.price = 0;
   rangeLowestLow.index = 0;
}

Para detectar e lidar com breakouts a partir de um intervalo de consolidação previamente identificado, primeiro verificamos se os valores "rangeHighestHigh.price" e "rangeLowestLow.price" são válidos, garantindo que um intervalo de consolidação tenha sido estabelecido. Em seguida, comparamos o "currentClosePrice", obtido usando a função "close", com os limites do intervalo. Se o preço de fechamento exceder "rangeHighestHigh.price", reconhecemos um breakout de alta, registramos o evento e definimos "isBreakoutDetected" como true. Da mesma forma, se o preço de fechamento cair abaixo de "rangeLowestLow.price", identificamos um breakout de baixa e o sinalizamos adequadamente.

Uma vez que um breakout é confirmado, redefinimos as variáveis de estado necessárias para preparar o rastreamento de uma nova fase de consolidação. Registramos a ocorrência do breakout e armazenamos "breakoutBarNumber" como 1, marcando a primeira barra da sequência de breakout. O "breakoutTimestamp" é registrado usando TimeCurrent para marcar o momento exato do evento de breakout. Além disso, armazenamos "lastImpulseHigh" e "lastImpulseLow" para acompanhar o comportamento do preço após o breakout. Por fim, redefinimos "isBreakoutDetected" para false e limpamos o intervalo de consolidação anterior definindo "rangeHighestHigh.price" e "rangeLowestLow.price" como 0, garantindo que o sistema esteja pronto para detectar a próxima oportunidade de trading.

Se houver breakouts confirmados, aguardamos e os verificamos por meio de movimentos impulsivos, e então os plotamos no gráfico.

//--- Check for impulsive movement after breakout and create order blocks
if (breakoutBarNumber >= 0 && TimeCurrent() > breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds()) {
   double impulseRange = lastImpulseHigh - lastImpulseLow;
   double impulseThresholdPrice = impulseRange * impulseMultiplier;
   isBullishImpulse = false;
   isBearishImpulse = false;
   for (int i = 1; i <= barsToWaitAfterBreakout; i++) {
      double closePrice = close(i);
      if (closePrice >= lastImpulseHigh + impulseThresholdPrice) {
         isBullishImpulse = true;
         Print("Impulsive upward move: ", closePrice, " >= ", lastImpulseHigh + impulseThresholdPrice);
         break;
      } else if (closePrice <= lastImpulseLow - impulseThresholdPrice) {
         isBearishImpulse = true;
         Print("Impulsive downward move: ", closePrice, " <= ", lastImpulseLow - impulseThresholdPrice);
         break;
      }
   }
   if (!isBullishImpulse && !isBearishImpulse) {
      Print("No impulsive movement detected.");
   }
   //---
}

Aqui, analisamos a ação de preço após um breakout para determinar se ocorreu um movimento impulsivo, o que é crítico para identificar blocos de ordens válidos. Primeiro, verificamos se "breakoutBarNumber" é válido e se o tempo atual, obtido via TimeCurrent, ultrapassou "breakoutTimestamp" mais "barsToWaitAfterBreakout" multiplicado por PeriodSeconds, garantindo que um período de espera suficiente tenha decorrido. Em seguida, calculamos "impulseRange" como a diferença entre "lastImpulseHigh" e "lastImpulseLow", que representa a variação de preço após o breakout. Com isso, calculamos "impulseThresholdPrice" multiplicando "impulseRange" por "impulseMultiplier" para definir a extensão mínima de preço necessária para um movimento impulsivo.

Em seguida, inicializamos "isBullishImpulse" e "isBearishImpulse" como false, preparando para avaliar a ação de preço nas últimas barras definidas por "barsToWaitAfterBreakout". Iteramos por essas barras usando um for loop, recuperando o preço de fechamento com a função "close". Se "closePrice" for maior ou igual a "lastImpulseHigh + impulseThresholdPrice", detectamos um movimento impulsivo de alta, definimos "isBullishImpulse" como true e registramos o evento. Se "closePrice" for menor ou igual a "lastImpulseLow - impulseThresholdPrice", identificamos um movimento impulsivo de baixa, definimos "isBearishImpulse" como true e o registramos. Se nenhuma dessas condições for atendida, exibimos uma mensagem informando que nenhum movimento impulsivo foi detectado. Essa lógica garante que apenas continuações fortes de breakout sejam qualificadas como bloco de ordens válidos para processamento adicional. Para visualizá-los, utilizamos a seguinte lógica.

bool isOrderBlockValid = isBearishImpulse || isBullishImpulse;

if (isOrderBlockValid) {
   datetime blockStartTime = iTime(_Symbol, _Period, consolidationBars + barsToWaitAfterBreakout + 1);
   double blockTopPrice = lastImpulseHigh;
   int visibleBarsOnChart = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   datetime blockEndTime = blockStartTime + (visibleBarsOnChart / 1) * PeriodSeconds();
   double blockBottomPrice = lastImpulseLow;
   string orderBlockName = OB_Prefix + "(" + TimeToString(blockStartTime) + ")";
   color orderBlockColor = isBullishImpulse ? bullishOrderBlockColor : bearishOrderBlockColor;
   string orderBlockLabel = isBullishImpulse ? "Bullish OB" : "Bearish OB";

   if (ObjectFind(0, orderBlockName) < 0) {
      //--- Create a rectangle for the order block
      ObjectCreate(0, orderBlockName, OBJ_RECTANGLE, 0, blockStartTime, blockTopPrice, blockEndTime, blockBottomPrice);
      ObjectSetInteger(0, orderBlockName, OBJPROP_TIME, 0, blockStartTime);
      ObjectSetDouble(0, orderBlockName, OBJPROP_PRICE, 0, blockTopPrice);
      ObjectSetInteger(0, orderBlockName, OBJPROP_TIME, 1, blockEndTime);
      ObjectSetDouble(0, orderBlockName, OBJPROP_PRICE, 1, blockBottomPrice);
      ObjectSetInteger(0, orderBlockName, OBJPROP_FILL, true);
      ObjectSetInteger(0, orderBlockName, OBJPROP_COLOR, orderBlockColor);
      ObjectSetInteger(0, orderBlockName, OBJPROP_BACK, false);

      //--- Add a text label in the middle of the order block with dynamic font size
      datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2;
      double labelPrice = (blockTopPrice + blockBottomPrice) / 2;
      string labelObjectName = orderBlockName + orderBlockLabel;
      if (ObjectFind(0, labelObjectName) < 0) {
         ObjectCreate(0, labelObjectName, OBJ_TEXT, 0, labelTime, labelPrice);
         ObjectSetString(0, labelObjectName, OBJPROP_TEXT, orderBlockLabel);
         ObjectSetInteger(0, labelObjectName, OBJPROP_COLOR, labelTextColor);
         ObjectSetInteger(0, labelObjectName, OBJPROP_FONTSIZE, dynamicFontSize);
         ObjectSetInteger(0, labelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER);
      }
      ChartRedraw(0);

      //--- Store the order block details in arrays
      ArrayResize(orderBlockNames, ArraySize(orderBlockNames) + 1);
      orderBlockNames[ArraySize(orderBlockNames) - 1] = orderBlockName;
      ArrayResize(orderBlockEndTimes, ArraySize(orderBlockEndTimes) + 1);
      orderBlockEndTimes[ArraySize(orderBlockEndTimes) - 1] = blockEndTime;
      ArrayResize(orderBlockMitigatedStatus, ArraySize(orderBlockMitigatedStatus) + 1);
      orderBlockMitigatedStatus[ArraySize(orderBlockMitigatedStatus) - 1] = false;

      Print("Order Block created: ", orderBlockName);
   }
}

Aqui, determinamos se um blocos de ordens deve ser criado com base na detecção de um movimento impulsivo. Primeiro, avaliamos "isOrderBlockValid" verificando se "isBearishImpulse" ou "isBullishImpulse" é true. Se for válido, definimos parâmetros-chave para o blocos de ordens: "blockStartTime" é obtido usando a função iTime para referenciar a barra em "consolidationBars + barsToWaitAfterBreakout + 1", garantindo alinhamento com a estrutura identificada. "blockTopPrice" é definido como "lastImpulseHigh", e "blockBottomPrice" como "lastImpulseLow", marcando o intervalo de preço do bloco de ordens. Utilizamos a função ChartGetInteger para determinar "visibleBarsOnChart" e calcular "blockEndTime" dinamicamente com base em PeriodSeconds, garantindo que o retângulo permaneça visível dentro do escopo atual do gráfico.

O nome do bloco de ordens (order blocks) é construído usando "OB_Prefix" e a função TimeToString para incluir o timestamp, garantindo unicidade. A cor e o rótulo são determinados com base em se o impulso é de alta ou de baixa, selecionando "bullishOrderBlockColor" ou "bearishOrderBlockColor" e atribuindo o respectivo rótulo.

Em seguida, verificamos a existência do bloco de ordens usando ObjectFind. Se não existir, utilizamos a função ObjectCreate para desenhar um retângulo (OBJ_RECTANGLE) representando o bloco de ordens, definindo seus limites de tempo e preço com ObjectSetInteger e ObjectSetDouble. O retângulo é preenchido (OBJPROP_FILL), a cor é aplicada (OBJPROP_COLOR) e ele é desenhado em primeiro plano (OBJPROP_BACK = false).

Em seguida, criamos um rótulo dentro do bloco de ordens para melhor visualização. O tempo do rótulo ("labelTime") é definido no ponto médio entre "blockStartTime" e "blockEndTime", enquanto "labelPrice" é calculado como o ponto médio entre "blockTopPrice" e "blockBottomPrice". Geramos um nome único para o rótulo anexando "orderBlockLabel" a "orderBlockName". Se o rótulo não existir, criamos um objeto de texto (OBJ_TEXT) com "ObjectCreate", definindo o conteúdo do texto (OBJPROP_TEXT), cor (OBJPROP_COLOR), tamanho da fonte (OBJPROP_FONTSIZE) e centralizando-o com (OBJPROP_ANCHOR = ANCHOR_CENTER). A função ChartRedraw garante que os elementos recém-criados apareçam imediatamente. Como o tamanho da fonte varia significativamente de acordo com a escala do gráfico, nós o calculamos dinamicamente conforme abaixo.

//--- Calculate dynamic font size based on chart scale (0 = zoomed out, 5 = zoomed in)
int chartScale = (int)ChartGetInteger(0, CHART_SCALE); // Scale ranges from 0 to 5
int dynamicFontSize = 8 + (chartScale * 2);           // Font size: 8 (min) to 18 (max)

Por fim, armazenamos os detalhes do bloco de ordens em arrays: "orderBlockNames" (armazena nomes dos objetos), "orderBlockEndTimes" (armazena tempos de expiração) e "orderBlockMitigatedStatus" (rastreia se o bloco de ordens foi mitigado). Redimensionamos cada array dinamicamente usando a função ArrayResize para acomodar novas entradas, garantindo que o gerenciamento dos blocos de ordens permaneça flexível. Uma mensagem de confirmação é exibida para indicar a criação bem-sucedida de um bloco de ordens. Por fim, precisamos apenas redefinir as variáveis de rastreamento de breakout.

//--- Reset breakout tracking variables
breakoutBarNumber = -1;
breakoutTimestamp = 0;
lastImpulseHigh = 0;
lastImpulseLow = 0;
isBullishImpulse = false;
isBearishImpulse = false;

Após compilar e executar o programa, temos o seguinte resultado.

OBs Confirmados

Pela imagem, podemos ver que confirmamos e rotulamos bloco de ordens resultantes de movimentos impulsivos de breakout. Agora, precisamos prosseguir para validar os blocos de ordens mitigados por meio do gerenciamento contínuo dos setups dentro dos limites do gráfico.

//--- Process existing order blocks for mitigation and trading
for (int j = ArraySize(orderBlockNames) - 1; j >= 0; j--) {
   string currentOrderBlockName = orderBlockNames[j];
   bool doesOrderBlockExist = false;

   //--- Retrieve order block properties
   double orderBlockHigh = ObjectGetDouble(0, currentOrderBlockName, OBJPROP_PRICE, 0);
   double orderBlockLow = ObjectGetDouble(0, currentOrderBlockName, OBJPROP_PRICE, 1);
   datetime orderBlockStartTime = (datetime)ObjectGetInteger(0, currentOrderBlockName, OBJPROP_TIME, 0);
   datetime orderBlockEndTime = (datetime)ObjectGetInteger(0, currentOrderBlockName, OBJPROP_TIME, 1);
   color orderBlockCurrentColor = (color)ObjectGetInteger(0, currentOrderBlockName, OBJPROP_COLOR);

   //--- Check if the order block is still valid (not expired)
   if (time(1) < orderBlockEndTime) {
      doesOrderBlockExist = true;
   }
   //---
}

Iteramos por "orderBlockNames" em ordem reversa, processando cada bloco de ordens para mitigação e trading. "currentOrderBlockName" armazena o nome do bloco que está sendo verificado. Usamos ObjectGetDouble e ObjectGetInteger para recuperar "orderBlockHigh", "orderBlockLow", "orderBlockStartTime", "orderBlockEndTime" e "orderBlockCurrentColor", garantindo o tratamento preciso das propriedades de cada bloco de ordens.

Para verificar se o bloco de ordens ainda é válido, comparamos "time(1)" (obtido usando a função "time") com "orderBlockEndTime". Se o tempo atual estiver dentro do tempo de vida do bloco de ordens, "doesOrderBlockExist" é definido como true, confirmando que o bloco de ordens permanece ativo para processamento adicional. Se estiver, prosseguimos para processá-lo e operá-lo.

//--- Get current market prices
double currentAskPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
double currentBidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);

//--- Check for mitigation and execute trades if trading is enabled
if (enableTrading && orderBlockCurrentColor == bullishOrderBlockColor && close(1) < orderBlockLow && !orderBlockMitigatedStatus[j]) {
   //--- Sell trade when price breaks below a bullish order block
   double entryPrice = currentBidPrice;
   double stopLossPrice = entryPrice + stopLossDistance * _Point;
   double takeProfitPrice = entryPrice - takeProfitDistance * _Point;
   obj_Trade.Sell(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice);
   orderBlockMitigatedStatus[j] = true;
   ObjectSetInteger(0, currentOrderBlockName, OBJPROP_COLOR, mitigatedOrderBlockColor);
   string blockDescription = "Bullish Order Block";
   string textObjectName = currentOrderBlockName + blockDescription;
   if (ObjectFind(0, textObjectName) >= 0) {
      ObjectSetString(0, textObjectName, OBJPROP_TEXT, "Mitigated " + blockDescription);
   }
   Print("Sell trade entered upon mitigation of bullish OB: ", currentOrderBlockName);
} else if (enableTrading && orderBlockCurrentColor == bearishOrderBlockColor && close(1) > orderBlockHigh && !orderBlockMitigatedStatus[j]) {
   //--- Buy trade when price breaks above a bearish order block
   double entryPrice = currentAskPrice;
   double stopLossPrice = entryPrice - stopLossDistance * _Point;
   double takeProfitPrice = entryPrice + takeProfitDistance * _Point;
   obj_Trade.Buy(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice);
   orderBlockMitigatedStatus[j] = true;
   ObjectSetInteger(0, currentOrderBlockName, OBJPROP_COLOR, mitigatedOrderBlockColor);
   string blockDescription = "Bearish Order Block";
   string textObjectName = currentOrderBlockName + blockDescription;
   if (ObjectFind(0, textObjectName) >= 0) {
      ObjectSetString(0, textObjectName, OBJPROP_TEXT, "Mitigated " + blockDescription);
   }
   Print("Buy trade entered upon mitigation of bearish OB: ", currentOrderBlockName);
}

Começamos recuperando os preços atuais do mercado usando a função SymbolInfoDouble, garantindo que tanto "currentAskPrice" quanto "currentBidPrice" sejam normalizados para o número apropriado de casas decimais usando _Digits. Isso garante precisão ao executar operações. Em seguida, verificamos se "enableTrading" está ativo e se uma condição de mitigação de blocos de ordens foi atendida. A mitigação ocorre quando o preço rompe um bloco de ordens, indicando uma falha em sua estrutura de sustentação.

Para blocos de ordens de alta, verificamos se o preço de "close" da barra anterior (obtido usando a função "close") caiu abaixo de "orderBlockLow" e garantimos que esse bloco de ordens ainda não tenha sido mitigado ("orderBlockMitigatedStatus[j] == false"). Se essas condições forem atendidas, executamos uma operação de venda usando a função "Sell" do objeto "obj_Trade". A operação é executada em "currentBidPrice", com um stop-loss ("stopLossPrice") posicionado acima do preço de entrada por "stopLossDistance * _Point" e um take-profit ("takeProfitPrice") definido abaixo do preço de entrada por "takeProfitDistance * _Point".

Após a execução da operação, o bloco de ordens é marcado como mitigado atualizando "orderBlockMitigatedStatus[j]" para true, e sua cor é alterada usando ObjectSetInteger para indicar seu estado de mitigado. Se existir um rótulo de texto para esse bloco de ordens (verificado usando ObjectFind), nós o atualizamos usando ObjectSetString para exibir "Mitigated Bullish Order Block". Uma instrução Print registra a execução da operação para fins de monitoramento e depuração.

Para blocos de ordens de baixa, o processo é semelhante. Verificamos se o preço de "close" subiu acima de "orderBlockHigh", indicando uma ruptura do bloco de ordens de baixa. Se as condições forem atendidas, uma operação de compra é executada por meio da função "Buy", usando "currentAskPrice" como preço de entrada. O "stopLossPrice" é posicionado abaixo do preço de entrada, e o "takeProfitPrice" é definido acima dele, garantindo um gerenciamento de risco adequado. Após executar a operação de compra, atualizamos "orderBlockMitigatedStatus[j]", alteramos a cor do bloco de ordens usando ObjectSetInteger e modificamos o rótulo de texto (se encontrado) para exibir "Mitigated Bearish Order Block". Por fim, uma instrução "Print" registra a execução da operação de compra para fins de monitoramento. Aqui está o que obtemos.

OB Mitigados & Operados

Por fim, uma vez que os blocos saem dos limites, nós os removemos dos arrays de armazenamento.

//--- Remove expired order blocks from arrays
if (!doesOrderBlockExist) {
   bool removedName = ArrayRemove(orderBlockNames, j, 1);
   bool removedTime = ArrayRemove(orderBlockEndTimes, j, 1);
   bool removedStatus = ArrayRemove(orderBlockMitigatedStatus, j, 1);
   if (removedName && removedTime && removedStatus) {
      Print("Success removing OB DATA from arrays at index ", j);
   }
}

Se o blocos de ordens não existir mais, removemos seu nome, tempo final e status de mitigação de seus respectivos arrays usando a função ArrayRemove. Se todas as remoções forem bem-sucedidas, registramos a ação com uma instrução Print para confirmar a limpeza. Abaixo está um exemplo da confirmação da limpeza.

Limpeza de Blocos

Pela imagem, podemos ver que realizamos com sucesso a limpeza dos blocos. Agora, precisamos apenas adicionar a lógica de trailing stop, e para isso utilizamos uma função para encapsular tudo.

//+------------------------------------------------------------------+
//| Trailing stop function                                           |
//+------------------------------------------------------------------+
void applyTrailingStop(double trailingPoints, CTrade &trade_object, int magicNo = 0) {
   //--- Calculate trailing stop levels based on current market prices
   double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingPoints * _Point, _Digits);
   double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingPoints * _Point, _Digits);
   
   //--- Loop through all open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if (ticket > 0) {
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && 
             (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)) {
            //--- Adjust stop loss for buy positions
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && 
                buyStopLoss > PositionGetDouble(POSITION_PRICE_OPEN) && 
                (buyStopLoss > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) {
               trade_object.PositionModify(ticket, buyStopLoss, PositionGetDouble(POSITION_TP));
            } 
            //--- Adjust stop loss for sell positions
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && 
                       sellStopLoss < PositionGetDouble(POSITION_PRICE_OPEN) && 
                       (sellStopLoss < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) {
               trade_object.PositionModify(ticket, sellStopLoss, PositionGetDouble(POSITION_TP));
            }
         }
      }
   }
}

Aqui, definimos a função "applyTrailingStop" para ajustar dinamicamente os níveis de stop-loss para posições ativas. Começamos calculando "buyStopLoss" e "sellStopLoss" usando os preços atuais de bid/ask e os "trailingPoints" especificados. Em seguida, percorremos todas as posições abertas, filtrando-as por símbolo e número mágico (se fornecido). Se uma posição de compra tiver um nível de stop-loss válido acima do preço de entrada, e se ele exceder o stop-loss atual ou não estiver definido, nós o atualizamos. Da mesma forma, para posições de venda, garantimos que o novo stop-loss esteja abaixo do preço de entrada antes de modificá-lo.

Em seguida, chamamos a função dentro do manipulador de evento OnTick, para processar a cada tick e não a cada barra desta vez, para verificações de preço em tempo real, conforme a seguir.

//--- Apply trailing stop to open positions if enabled
if (enableTrailingStop) {
   applyTrailingStop(trailingStopPoints, obj_Trade, uniqueMagicNumber);
}

Após compilar e executar o programa, temos o seguinte resultado.

MOB GIF

Pela visualização, podemos ver que o programa identifica e verifica todas as condições de entrada e, se validadas, abre a respectiva posição com os parâmetros de entrada correspondentes, alcançando assim nosso objetivo. O que resta agora é realizar o backtesting do programa, e isso é tratado na próxima seção.


Backtesting

Após um backtesting completo, temos os seguintes resultados.

Gráfico de backtest:

GRAPH

Relatório de backtesting:

REPORT


Conclusão

Em conclusão, implementamos com sucesso a estratégia Mitigation Order Blocks (MOB) em MQL5, permitindo detecção precisa, visualização e trading automatizado com base em conceitos de dinheiro inteligente (Smart Money). Ao integrar validação de breakout, reconhecimento de movimentos impulsivos e execução de operações baseada em mitigação, nosso sistema identifica e processa efetivamente blocos de ordens enquanto se adapta à dinâmica do mercado. Além disso, incorporamos trailing stops e mecanismos de gerenciamento de risco para otimizar o desempenho das operações e aumentar a robustez.

Aviso: Este artigo é apenas para fins educacionais. O trading envolve risco financeiro significativo, e as condições de mercado podem ser imprevisíveis. Backtesting adequado e gerenciamento de risco são essenciais antes da utilização em ambiente real.

Ao aproveitar essas técnicas, você pode refinar suas estratégias de trading algorítmico e melhorar a eficiência do trading baseado em blocos de ordens. Continue testando, otimizando e adaptando sua abordagem para o sucesso a longo prazo. Boa sorte!

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

Arquivos anexados |
Últimos Comentários | Ir para discussão (7)
davesarge1
davesarge1 | 12 abr. 2025 em 13:08
Não está realizando negociações no testador de estratégias. Configurações padrão em todos os pares. Nenhuma mensagem de erro no diário. Mensagens do diário presentes: "Sem extensão: Barra fora do intervalo" e "Nenhum movimento impulsivo detectado".


Allan Munene Mutiiria
Allan Munene Mutiiria | 14 abr. 2025 em 13:47
davesarge1 testador de estratégias. Configurações padrão em todos os pares. Nenhuma mensagem de erro no diário. Mensagens do diário presentes: "Sem extensão: Barra fora do intervalo" e "Nenhum movimento impulsivo detectado".


Você ao menos leu o artigo? Porque temos certeza de que o artigo fornece praticamente todas as suas respostas.

Bao Thuan Thai
Bao Thuan Thai | 1 ago. 2025 em 23:35
Muito obrigado. Fico muito grato! Vou tentar
Chen Yurui
Chen Yurui | 8 jan. 2026 em 03:10
É possível criar um EA que possa negociar blocos de ordens de atenuação e blocos de ordens de reteste (ou seja, ele compra quando o preço cai para um bloco de alta)?
Wesley De Almeida Junior
Wesley De Almeida Junior | 2 jun. 2026 em 20:27

Boa tarde e parabéns pela ótima qualidade do trabalho.

Já foi testada esta aplicação para mini-índice em 1 minuto?

Desde já obrigado!

Wesley

Superando as limitações do aprendizado de máquina (Parte 2): falta de reprodutibilidade Superando as limitações do aprendizado de máquina (Parte 2): falta de reprodutibilidade
O artigo examina por que os resultados de trading podem variar significativamente entre corretoras, mesmo usando a mesma estratégia e o mesmo símbolo financeiro, devido à precificação descentralizada e às divergências nos dados. Este artigo ajuda os desenvolvedores MQL5 a entender por que seus produtos podem receber avaliações mistas no MQL5 Marketplace e incentiva os desenvolvedores a adaptar suas abordagens a corretoras específicas para garantir resultados transparentes e reproduzíveis. Se amplamente adotada, essa pode se tornar uma prática recomendada importante e bastante especializada, capaz de beneficiar nossa comunidade.
Do iniciante ao especialista: Criação de um EA animado para notícias em MQL5 (VIII): botões de negociação rápida para trading de notícias Do iniciante ao especialista: Criação de um EA animado para notícias em MQL5 (VIII): botões de negociação rápida para trading de notícias
Enquanto os sistemas algorítmicos de trading gerenciam operações automatizadas, muitos traders de notícias e scalpers preferem manter controle ativo durante eventos importantes de notícias e condições de mercado que mudam rapidamente, exigindo execução e gestão rápidas das ordens. Isso evidencia a necessidade de ferramentas de interface intuitivas que integrem feeds de notícias em tempo real, dados do calendário econômico, leituras dos indicadores, análises baseadas em IA e gestão adaptativa do trading.
Construindo Expert Advisors Autootimizáveis em MQL5 (Parte 6): Regras de Trading Autoajustáveis (II) Construindo Expert Advisors Autootimizáveis em MQL5 (Parte 6): Regras de Trading Autoajustáveis (II)
Este artigo explora a otimização dos níveis e períodos do RSI para obter melhores sinais de trading. Introduzimos métodos para estimar valores ótimos do RSI e automatizar a seleção de períodos usando busca em grade e modelos estatísticos. Por fim, implementamos a solução em MQL5 enquanto utilizamos Python para análise. Nossa abordagem busca ser pragmática e direta para ajudá-lo a resolver problemas potencialmente complicados, com simplicidade.
Ciência de Dados e ML (Parte 35): NumPy em MQL5 – A Arte de Desenvolver Algoritmos Complexos com Menos Código Ciência de Dados e ML (Parte 35): NumPy em MQL5 – A Arte de Desenvolver Algoritmos Complexos com Menos Código
A biblioteca NumPy está impulsionando praticamente todos os algoritmos de aprendizado de máquina (machine learning) em sua essência na linguagem de programação Python, neste artigo vamos implementar um módulo semelhante que possui uma coleção de todo o código complexo para nos auxiliar na construção de modelos e algoritmos sofisticados de qualquer tipo.