Automatización de estrategias de trading en MQL5 (Parte 12): Implementación de la estrategia Mitigation Order Blocks (MOB)
Introducción
En nuestro artículo anterior (Parte 11), creamos un sistema de trading con cuadrículas multinivel en MetaQuotes Language 5 (MQL5) para sacar provecho de las fluctuaciones del mercado. Ahora, en la parte 12, nos centramos en la implementación de la estrategia Mitigation Order Blocks (MOB), un concepto de Smart Money que identifica zonas de precios clave en las que se mitigan las órdenes institucionales antes de que se produzcan movimientos significativos en el mercado. Cubriremos los siguientes temas:
Al final de este artículo, tendrás un sistema de negociación totalmente automatizado con mitigación de bloques de órdenes listo para operar. ¡Empecemos!
Plan de la estrategia
Para implementar la estrategia mitigación de bloques de órdenes, desarrollaremos un sistema automatizado que detecte, valide y ejecute operaciones basadas en eventos de mitigación de bloques de órdenes. La estrategia se centrará en identificar zonas de precios institucionales en las que se absorbe la liquidez antes de que continúe la tendencia. Nuestro sistema incorporará condiciones precisas para la entrada, la colocación de órdenes stop-loss y la gestión de operaciones con el fin de garantizar la eficiencia y la precisión. Estructuraremos el desarrollo de la siguiente manera:
- Identificación de bloques de órdenes: El sistema analizará el historial de precios para detectar bloques de órdenes alcistas y bajistas, filtrando las zonas débiles en función de la volatilidad, la captación de liquidez y el desequilibrio de precios.
- Validación de la mitigación: Programaremos condiciones que confirmen un evento de mitigación válido, asegurándonos de que el precio vuelva al bloque de órdenes y reaccione con señales de rechazo, como mechas o cambios de impulso.
- Confirmación de la estructura del mercado: El EA analizará las tendencias en marcos temporales superiores y los barridos de liquidez para garantizar que la mitigación identificada se ajuste al flujo general del mercado.
- Reglas de ejecución de operaciones: Una vez confirmada la mitigación, el sistema definirá puntos de entrada precisos, calculará dinámicamente los niveles de stop-loss basándose en la estructura del bloque de órdenes y establecerá objetivos de take-profit basándose en parámetros de riesgo-recompensa.
- Gestión del riesgo y del dinero: La estrategia integrará el tamaño de las posiciones, la protección contra las pérdidas y las estrategias de salida para gestionar eficazmente el riesgo comercial.
En pocas palabras, aquí tienes una visualización general de la estrategia.

Implementación en MQL5
Para crear el programa en MQL5 abra MetaEditor, vaya al Navegador, localice la carpeta Indicators, haga clic en la pestaña «Nuevo» y siga las instrucciones para crear el archivo. Una vez creado, en el entorno de programación, tendremos que declarar algunas variables globales que utilizaremos a lo largo del 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;
Comenzamos la implementación incluyendo la librería comercial con «#include <Trade/Trade.mqh>», que proporciona funciones integradas para gestionar las operaciones comerciales. A continuación, inicializamos el objeto comercial «obj_Trade» utilizando la clase «CTrade», lo que permite al Asesor Experto ejecutar órdenes de compra y venta de forma programada. Esta configuración garantizará que la ejecución de las operaciones se gestione de forma eficiente sin necesidad de intervención manual. A continuación, podemos proporcionar algunos datos para que el usuario pueda cambiar y controlar el comportamiento desde la interfaz de usuario (User Interface, 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
Aquí definimos los parámetros de entrada para configurar el comportamiento del programa. «tradeLotSize» establece el tamaño de la posición, mientras que «enableTrading» y «enableTrailingStop» controlan la ejecución y los trailing stops, con «trailingStopPoints» y «minProfitToTrail» refinando la lógica de los stops. «uniqueMagicNumber» identifica las operaciones, y la consolidación se detecta utilizando «consolidationBars» y «maxConsolidationSpread». Las rupturas se confirman con «barsToWaitAfterBreakout» e «impulseMultiplier». «stopLossDistance» y «takeProfitDistance» gestionan el riesgo, mientras que «bullishOrderBlockColor», «bearishOrderBlockColor», «mitigatedOrderBlockColor» y «labelTextColor» se encargan de los elementos visuales del gráfico.
Por último, debemos definir algunas variables globales que utilizaremos para el control general del 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
Primero definimos la estructura «PriceAndIndex» struct, que almacena un valor «price» y el «index» de la barra donde se produce este precio. Esta estructura será útil para realizar un seguimiento de precios específicos dentro de un rango definido. Las variables globales gestionan aspectos clave de la estructura del mercado y la detección de rupturas. «rangeHighestHigh» y «rangeLowestLow» almacenarán los precios más altos y más bajos del rango de consolidación, respectivamente, lo que ayudará a definir los límites de los posibles bloques de órdenes. «isBreakoutDetected» actuará como un indicador para señalar cuándo se ha producido una ruptura, mientras que «lastImpulseLow» y «lastImpulseHigh» almacenarán el primer mínimo y máximo tras una ruptura, lo que se utiliza para confirmar movimientos impulsivos.
«breakoutBarNumber» registrará el índice de la barra en la que se produjo la ruptura, y «breakoutTimestamp» almacenará la hora exacta del evento de ruptura. Las matrices «orderBlockNames», «orderBlockEndTimes» y «orderBlockMitigatedStatus» se encargarán de la identificación, la duración y el seguimiento de la mitigación de los bloques de órdenes. Los indicadores booleanos «isBullishImpulse» e «isBearishImpulse» determinan si el movimiento de ruptura se clasifica como un impulso alcista o bajista. Por último, «OB_Prefix» es un prefijo de cadena (string) predefinido por la macro #define que se utiliza al nombrar objetos de bloques de orden, lo que garantiza la coherencia en la representación gráfica. Con las variables ya estamos listos para comenzar la lógica del 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); }
Inicializamos el Asesor Experto en el controlador de eventos OnInit. Establecemos el número mágico utilizando el método «SetExpertMagicNumber», lo que garantiza que todas las operaciones ejecutadas por nuestro EA estén etiquetadas de forma única, evitando conflictos con otras operaciones. Este paso es crucial para realizar el seguimiento y gestionar únicamente las operaciones abiertas por nuestra estrategia. Una vez completada la inicialización, devolvemos INIT_SUCCEEDED, lo que confirma que nuestro programa está listo para funcionar. A continuación, podemos pasar al controlador de eventos principal OnTick para nuestra lógica de control principal.
//+------------------------------------------------------------------+ //| 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 asegurarnos de que procesamos los datos de cada barra y no de cada tick, en la función OnTick, que se ejecuta cada vez que se recibe un nuevo tick, utilizamos la función iBars para obtener el número total de barras del gráfico y lo almacenamos en «currentBarCount». A continuación, lo comparamos con «previousBarCount» y, si son iguales, «isNewBar» permanece en false, lo que evita un procesamiento redundante. Si se detecta una nueva barra, actualizamos «previousBarCount» y establecemos «isNewBar» en true, lo que permite que se ejecute la lógica de la estrategia. Por último, si «isNewBar» es false, volvemos antes de tiempo, optimizando el rendimiento al omitir cálculos innecesarios. Si se trata de una nueva barra, seguimos buscando consolidaciones.
//--- 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."); } } }
Aquí definimos y establecemos el rango de consolidación mediante el análisis de los movimientos recientes de los precios. Comenzamos estableciendo «startBarIndex» en 1, lo que determina el punto de partida para nuestras comprobaciones de consolidación. Si aún no hemos detectado una ruptura, tal y como indica «isBreakoutDetected», procedemos a evaluar si el mercado se encuentra en una fase de consolidación ajustada. Iteramos a través del último recuento de barras «consolidationBars», utilizando la función MathAbs para medir las diferencias absolutas entre los máximos y mínimos consecutivos. Si todas las diferencias se mantienen dentro de «maxConsolidationSpread», confirmamos la consolidación.
Una vez detectada la consolidación, determinamos el máximo más alto y el mínimo más bajo dentro del rango. Inicializamos «rangeHighestHigh» y «rangeLowestLow» con el máximo y el mínimo de «startBarIndex», y luego iteramos a través del rango para actualizar estos valores cada vez que encontramos un nuevo máximo o mínimo. Estos valores definen nuestros límites de consolidación.
Si el rango de consolidación ya está establecido, comprobamos si la barra actual amplía el rango existente. Recuperamos «currentHigh» y «currentLow» utilizando las funciones «high» y «low» y los comparamos con «rangeHighestHigh.price» y «rangeLowestLow.price». Si el precio permanece dentro del rango, imprimimos un mensaje indicando la extensión del rango utilizando la función Print. De lo contrario, imprimimos que no se produjo ninguna extensión, lo que indica un posible escenario de ruptura. Las funciones de precios personalizadas son las siguientes:
//+------------------------------------------------------------------+ //| 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
Estas funciones personalizadas nos ayudan a recuperar datos sobre precios. La función «high» utiliza iHigh para devolver el precio máximo de una barra en un «index» especificado, mientras que la función «low» llama a iLow para obtener el precio mínimo correspondiente. La función «open» obtiene el precio de apertura utilizando iOpen, y la función «close» recupera el precio de cierre a través de iClose. Además, la función «time» utiliza iTime para devolver la marca de tiempo de la barra dada. Al ejecutar el programa, obtenemos el siguiente resultado.

En la imagen podemos ver que, una vez establecido el rango de precios y cuando el precio oscila dentro de ese rango, lo ampliamos hasta que se produce una ruptura del rango. Ahora, necesitamos detectar una ruptura en el rango de retraso de precios confirmado. Lo conseguimos utilizando la siguiente 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 y gestionar las rupturas de un rango de consolidación previamente identificado, primero verificamos que los valores «rangeHighestHigh.price» y «rangeLowestLow.price» sean válidos, asegurándonos de que se haya establecido un rango de consolidación. A continuación, comparamos el «currentClosePrice», obtenido mediante la función «close», con los límites del rango. Si el precio de cierre supera «rangeHighestHigh.price», reconocemos una ruptura al alza, registramos el evento y establecemos «isBreakoutDetected» en verdadero. Del mismo modo, si el precio de cierre cae por debajo de «rangeLowestLow.price», identificamos una ruptura a la baja y lo señalamos como corresponde.
Una vez que se confirma una ruptura, restablecemos las variables de estado necesarias para prepararnos para el seguimiento de una nueva fase de consolidación. Registramos la ocurrencia de la ruptura y almacenamos el «breakoutBarNumber» como 1, marcando la primera barra de la secuencia de ruptura. El «breakoutTimestamp» se registra utilizando TimeCurrent para anotar la hora exacta de la ruptura. Además, almacenamos «lastImpulseHigh» y «lastImpulseLow» para realizar un seguimiento del comportamiento del precio tras la ruptura. Por último, restablecemos «isBreakoutDetected» a false y borramos el rango de consolidación anterior estableciendo «rangeHighestHigh.price» y «rangeLowestLow.price» en 0, lo que garantiza que el sistema esté listo para detectar la próxima oportunidad de negociación.
Si hay rupturas confirmadas, esperamos y las verificamos mediante movimientos impulsivos, y luego las trazamos en el 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."); } //--- }
Aquí analizamos la evolución del precio tras una ruptura para determinar si se ha producido un movimiento impulsivo, lo cual es fundamental para identificar bloques de órdenes válidos. Primero comprobamos si «breakoutBarNumber» es válido y si la hora actual, obtenida a través de TimeCurrent, ha superado «breakoutTimestamp» más «barsToWaitAfterBreakout» multiplicado por PeriodSeconds, asegurándonos de que ha transcurrido un periodo de espera suficiente. A continuación, calculamos «impulseRange» como la diferencia entre «lastImpulseHigh» y «lastImpulseLow», que representa la fluctuación del precio tras la ruptura. Con esto, calculamos el «impulseThresholdPrice» multiplicando el «impulseRange» por el «impulseMultiplier» para definir la extensión mínima del precio necesaria para un movimiento impulsivo.
A continuación, inicializamos «isBullishImpulse» e «isBearishImpulse» como falsos, preparándonos para evaluar la acción del precio durante las últimas barras «barsToWaitAfterBreakout». Recorremos estas barras utilizando un bucle for, recuperando el precio de cierre con la función «close». Si «closePrice» es mayor o igual que «lastImpulseHigh + impulseThresholdPrice», detectamos un movimiento alcista impulsivo, establecemos «isBullishImpulse» en verdadero y registramos el evento. Si «closePrice» es menor o igual que «lastImpulseLow - impulseThresholdPrice», identificamos un movimiento bajista impulsivo, establecemos «isBearishImpulse» en verdadero y lo registramos. Si no se cumple ninguna de las dos condiciones, imprimimos un mensaje indicando que no se ha detectado ningún movimiento impulsivo. Esta lógica garantiza que solo las continuaciones de rupturas fuertes se consideren bloques de órdenes válidos para su posterior procesamiento. Para visualizarlos, utilizamos la siguiente 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); } }
Aquí, determinamos si se debe crear un bloque de órdenes basándonos en la detección de un movimiento impulsivo. Primero evaluamos «isOrderBlockValid» comprobando si «isBearishImpulse» o «isBullishImpulse» es verdadero. Si es válido, definimos los parámetros clave para el bloque de órdenes: «blockStartTime» se obtiene utilizando la función iTime para hacer referencia a la barra en «consolidationBars + barsToWaitAfterBreakout + 1», asegurando que se alinea con la estructura identificada. «blockTopPrice» se establece en «lastImpulseHigh» y «blockBottomPrice» en «lastImpulseLow», lo que marca el rango de precios del bloque de órdenes. Utilizamos la función ChartGetInteger para determinar «visibleBarsOnChart» y calcular «blockEndTime» dinámicamente en función de PeriodSeconds, lo que garantiza que el rectángulo permanezca visible dentro del ámbito actual del gráfico.
El nombre del bloque de pedido se construye utilizando «OB_Prefix» y la función TimeToString para incluir la marca de tiempo y garantizar su singularidad. El color y la etiqueta se determinan en función de si el impulso es alcista o bajista, seleccionando «bullishOrderBlockColor» o «bearishOrderBlockColor» y asignando la etiqueta correspondiente.
A continuación, comprobamos la existencia del bloque de órdenes utilizando ObjectFind. Si no existe, utilizamos la función ObjectCreate para dibujar un rectángulo (OBJ_RECTANGLE) que representa el bloque de órdenes, estableciendo sus límites de tiempo y precio con ObjectSetInteger y ObjectSetDouble. El rectángulo se rellena (OBJPROP_FILL), se le aplica color (OBJPROP_COLOR) y se dibuja en primer plano (OBJPROP_BACK = false).
A continuación, creamos una etiqueta dentro del bloque de pedidos para una mejor visualización. El tiempo de la etiqueta («labelTime») se establece en el punto medio entre «blockStartTime» y «blockEndTime», mientras que «labelPrice» se calcula como el punto medio entre «blockTopPrice» y «blockBottomPrice». Generamos un nombre de etiqueta único añadiendo «orderBlockLabel» a «orderBlockName». Si la etiqueta no existe, creamos un objeto de texto (OBJ_TEXT) con «ObjectCreate», estableciendo el contenido del texto (OBJPROP_TEXT), el color (OBJPROP_COLOR), el tamaño de la fuente (OBJPROP_FONTSIZE) y centrándolo con (OBJPROP_ANCHOR = ANCHOR_CENTER). La función ChartRedraw garantiza que los elementos recién creados aparezcan inmediatamente. Dado que el tamaño de la fuente sería muy importante en función de la escala del gráfico, lo calculamos dinámicamente como se indica a continuación.
//--- 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 último, almacenamos los detalles del bloque de órdenes en matrices: «orderBlockNames» (almacena los nombres de los objetos), «orderBlockEndTimes» (almacena los tiempos de caducidad) y «orderBlockMitigatedStatus» (realiza un seguimiento de si el bloque de órdenes se ha mitigado). Cambiamos el tamaño de cada matriz de forma dinámica utilizando la función ArrayResize para dar cabida a nuevas entradas, lo que garantiza que la gestión de nuestros bloque de órdenes siga siendo flexible. Se imprime un mensaje de confirmación para indicar que se ha creado correctamente un bloque de órdenes. Por último, solo tenemos que restablecer las variables de seguimiento de ruptura.
//--- Reset breakout tracking variables breakoutBarNumber = -1; breakoutTimestamp = 0; lastImpulseHigh = 0; lastImpulseLow = 0; isBullishImpulse = false; isBearishImpulse = false;
Tras compilar y ejecutar el programa, obtenemos el siguiente resultado.

En la imagen, podemos ver que hemos confirmado y etiquetado bloques de órdenes que son el resultado de movimientos impulsivos de ruptura. Ahora solo tenemos que proceder a validar los bloques de órdenes mitigados mediante la gestión continua de las configuraciones dentro de los límites del 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; } //--- }
Recorremos «orderBlockNames» en orden inverso, procesando cada bloque de órdenes para su mitigación y negociación. «currentOrderBlockName» almacena el nombre del bloque que se está comprobando. Utilizamos ObjectGetDouble y ObjectGetInteger para recuperar «orderBlockHigh», «orderBlockLow», «orderBlockStartTime», «orderBlockEndTime» y «orderBlockCurrentColor», lo que garantiza un manejo preciso de las propiedades de cada bloque de órdenes.
Para verificar si el bloque de órdenes sigue siendo válido, comparamos «time(1)» (obtenido mediante la función «time») con «orderBlockEndTime». Si la hora actual se encuentra dentro del periodo de vigencia del bloque de órdenes, «doesOrderBlockExist» se establece en verdadero, lo que confirma que el bloque de órdenes permanece activo para su posterior procesamiento. Si es así, procedemos a procesarlo y negociarlo.
//--- 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); }
Comenzamos recuperando los precios actuales de mercado utilizando la función SymbolInfoDouble, asegurándonos de que tanto «currentAskPrice» como «currentBidPrice» se normalicen al número adecuado de decimales utilizando _Digits. Esto garantiza la precisión al realizar operaciones. A continuación, comprobamos si «enableTrading» está activo y si se ha cumplido una condición de mitigación de bloqueo de órdenes. La mitigación se produce cuando un precio rompe un bloque de órdenes, lo que indica un fallo en su estructura de soporte.
Para los bloques de órdenes alcistas, verificamos si el precio de «cierre» de la barra anterior (obtenido mediante la función «close») ha caído por debajo de «orderBlockLow» y nos aseguramos de que este bloque de órdenes no haya sido ya mitigado («orderBlockMitigatedStatus[j] == false»). Si se cumplen estas condiciones, realizamos una operación de venta utilizando la función «Sell» del objeto «obj_Trade». La operación se ejecuta al «currentBidPrice», con un stop-loss («stopLossPrice») situado por encima del precio de entrada en «stopLossDistance * _Point» y un take-profit («takeProfitPrice») fijado por debajo del precio de entrada en «takeProfitDistance * _Point».
Una vez ejecutada la operación, el bloque de órdenes se marca como mitigado actualizando «orderBlockMitigatedStatus[j]» a verdadero, y su color se cambia utilizando ObjectSetInteger para indicar su estado mitigado. Si existe una etiqueta de texto para este bloque de órdenes (comprobado mediante ObjectFind), la actualizamos utilizando ObjectSetString para mostrar «Mitigated Bullish Order Block». Una instrucción Print registra la ejecución de la operación para su seguimiento y depuración.
Para los bloques de órdenes bajistas, el proceso es similar. Comprobamos si el precio de «cierre» ha subido por encima de «orderBlockHigh», lo que indica una ruptura del bloque de órdenes bajistas. Si se cumplen las condiciones, se realiza una operación de compra a través de la función «Buy», utilizando «currentAskPrice» como precio de entrada. El «stopLossPrice» se sitúa por debajo del precio de entrada, y el «takeProfitPrice» se establece por encima de él, lo que garantiza una gestión adecuada del riesgo. Después de realizar la operación de compra, actualizamos «orderBlockMitigatedStatus[j]», cambiamos el color del bloque de órdenes utilizando ObjectSetInteger y modificamos la etiqueta de texto (si la encontramos) para que muestre «Mitigated Bearish Order Block». Por último, una instrucción «Print» registra la ejecución de la operación de compra con fines de supervisión. Esto es lo que logramos:

Por último, una vez que los bloques están fuera de los límites, los eliminamos de las matrices de almacenamiento.
//--- 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); } }
Si el bloque de órdenes ya no existe, eliminamos su nombre, hora de finalización y estado de mitigación de sus respectivas matrices utilizando la función ArrayRemove. Si todas las eliminaciones se realizan correctamente, registramos la acción con una instrucción Print para confirmar la limpieza. Aquí hay un ejemplo de confirmación de limpieza.

En la imagen podemos ver que hemos realizado correctamente la limpieza de bloques. Ahora solo tenemos que añadir una lógica de stop dinámico y, para ello, empleamos una función que lo engloba todo.
//+------------------------------------------------------------------+ //| 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)); } } } } }
Aquí definimos la función «applyTrailingStop» para ajustar dinámicamente los niveles de stop-loss para las posiciones activas. Comenzamos calculando «buyStopLoss» y «sellStopLoss» utilizando los precios de compra/venta actuales y los «trailingPoints» especificados. A continuación, recorremos todas las posiciones abiertas, filtrándolas por símbolo y número mágico (si se proporciona). Si una posición de compra tiene un nivel de stop-loss válido por encima de su precio de entrada y supera el stop-loss actual o no está configurado, lo actualizamos. Del mismo modo, para las posiciones de venta, nos aseguramos de que el nuevo stop-loss esté por debajo del precio de entrada antes de modificarlo.
A continuación, llamamos a la función dentro del controlador de eventos OnTick, para procesar cada tick y no cada barra, esta vez para comprobaciones de precios en tiempo real, como se muestra a continuación.
//--- Apply trailing stop to open positions if enabled if (enableTrailingStop) { applyTrailingStop(trailingStopPoints, obj_Trade, uniqueMagicNumber); }
Tras compilar y ejecutar el programa, obtenemos el siguiente resultado.

A partir de la visualización, podemos ver que el programa identifica y verifica todas las condiciones de entrada y, si se validan, abre la posición correspondiente con los parámetros de entrada respectivos, logrando así nuestro objetivo. Lo que queda es realizar pruebas retrospectivas del programa, lo cual se trata en la siguiente sección.
Prueba retrospectiva
Tras realizar exhaustivas pruebas retrospectivas, hemos obtenido los siguientes resultados.
Gráfico de prueba retrospectiva:

Informe de prueba retrospectiva:

Conclusión
En conclusión, hemos implementado con éxito la estrategia Mitigation Order Blocks (MOB) en MQL5, lo que permite una detección precisa, visualización y negociación automatizada basada en conceptos de dinero inteligente (Smart Money). Al integrar la validación de rupturas, el reconocimiento de movimientos impulsivos y la ejecución de operaciones basada en la mitigación, nuestro sistema identifica y procesa eficazmente los bloques de órdenes, al tiempo que se adapta a la dinámica del mercado. Además, incorporamos stops dinámicos y mecanismos de gestión de riesgos para optimizar el rendimiento de las operaciones y mejorar la solidez.
Descargo de responsabilidad: Este artículo tiene fines exclusivamente educativos. El trading implica un riesgo financiero significativo y las condiciones del mercado pueden ser impredecibles. Es esencial realizar pruebas retrospectivas adecuadas y gestionar los riesgos antes de la implementación en vivo.
Al aprovechar estas técnicas, puede perfeccionar sus estrategias de negociación algorítmica y mejorar la eficiencia de la negociación basada en bloques de órdenes. Sigue probando, optimizando y adaptando tu enfoque para lograr el éxito a largo plazo. ¡Mucha suerte!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17547
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Desarrollo de estrategias comerciales de tendencia basadas en el aprendizaje automático
Particularidades del trabajo con números del tipo double en MQL4
Algoritmo de optimización de la fuerza central — Central Force Optimization (CFO)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Gracias Allan , muy bien puesto juntos realmente tike los efectos visuales y el cambio de color en mitigado y su manejo de las matrices . Gracias por compartir
Gracias por tus amables comentarios. Gracias por tus amables comentarios.
¿Ha leído el artículo? Porque estamos seguros de que el artículo proporciona prácticamente todas las respuestas.