Automatizando Estratégias de Trading em MQL5 (Parte 12): Implementação da Estratégia Mitigation Order Blocks (MOB)
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:
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.

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.

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.

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.

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.

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.

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:

Relatório de backtesting:

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
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Superando as limitações do aprendizado de máquina (Parte 2): falta de reprodutibilidade
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
Construindo Expert Advisors Autootimizáveis em MQL5 (Parte 6): Regras de Trading Autoajustáveis (II)
Ciência de Dados e ML (Parte 35): NumPy em MQL5 – A Arte de Desenvolver Algoritmos Complexos com Menos Código
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Você ao menos leu o artigo? Porque temos certeza de que o artigo fornece praticamente todas as suas respostas.
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