Automatización de estrategias de trading en MQL5 (Parte 35): Creación de un sistema de trading con bloques de ruptura
Introducción
En nuestro artículo anterior (Parte 34), desarrollamos un sistema de ruptura de líneas de tendencia en MetaQuotes Language 5 (MQL5) que identificaba líneas de tendencia de soporte y resistencia a partir de puntos de oscilación y las validaba mediante el coeficiente de determinación R², para ejecutar operaciones de ruptura con visualizaciones dinámicas en el gráfico. En la Parte 35, creamos un sistema de trading basado en bloques de ruptura que detecta rangos de consolidación, valida los bloques de ruptura mediante puntos de oscilación y opera en los retesteos con parámetros de riesgo personalizables e información visual. Hoy hablaremos de los siguientes temas:
- Comprender el marco de la estrategia de bloques de ruptura
- Implementación en MQL5
- Backtesting
- Conclusión
Al final, tendrás una estrategia funcional en MQL5 para operar en los retesteos de bloques de ruptura, lista para personalizarla. ¡Manos a la obra!
Comprender el marco de la estrategia de bloques de ruptura
La estrategia de bloques de ruptura es una estrategia de trading que identifica rangos de consolidación en los que el precio se mueve dentro de un rango estrecho, tras los cuales se producen una ruptura y un movimiento impulsivo que forman bloques de órdenes; cuando estos se invalidan, se convierten en bloques de ruptura que ofrecen posibles oportunidades para operar en los retesteos. Aprovecha el retorno del precio a estos bloques después de haberse alejado considerablemente de ellos, abriendo posiciones en la dirección de la ruptura con niveles de stop loss y de take profit definidos y con el apoyo de elementos visuales en el gráfico. Echa un vistazo a los distintos bloques de ruptura que podemos encontrar a continuación.
Bloque de ruptura bajista:

Bloque de ruptura alcista:

Nuestro plan consiste en detectar rangos de consolidación mediante el análisis de un número determinado de barras, identificar las rupturas cuando el precio sale del rango y confirmar los movimientos impulsivos utilizando un umbral basado en un multiplicador. Implementaremos una lógica para validar los bloques de ruptura mediante puntos de oscilación, ejecutar operaciones en los retesteos con parámetros personalizables y visualizar los bloques con etiquetas y flechas dinámicas, creando así un sistema para identificar y aprovechar oportunidades de trading con bloques de ruptura. En resumen, aquí tienes una representación gráfica de nuestros objetivos.

Implementación en MQL5
Para crear el programa en MQL5, abre MetaEditor, ve al Navegador, busca la carpeta «Experts», haz clic en la pestaña «Nuevo» y sigue las instrucciones para crear el archivo. Una vez creado, en el entorno de programación, tendremos que declarar algunos parámetros de entrada y variables globales que utilizaremos a lo largo de todo el programa.
//+------------------------------------------------------------------+ //| Breaker Block Strategy EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| 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 strict #include <Trade/Trade.mqh> //--- Include Trade library for position management CTrade obj_Trade; //--- Instantiate trade object for order operations
Comenzamos la implementación incluyendo la biblioteca de operaciones con «#include <Trade/Trade.mqh>», que proporciona funciones integradas para gestionar las operaciones de trading. A continuación, inicializamos el objeto de operaciones «obj_Trade» utilizando la clase CTrade, lo que permite al asesor experto ejecutar órdenes de compra y venta mediante código. Esta configuración garantizará que la ejecución de las operaciones se lleve a cabo de forma eficiente sin necesidad de intervención manual. A continuación, podemos definir algunos parámetros de entrada para que el usuario pueda modificar y controlar el comportamiento desde la interfaz de usuario (UI).
//+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input double tradeLotSize = 0.01; // Trade size for each position in lots input bool enableTrading = true; // Toggle to enable or disable automated trading input bool enableTrailingStop = true; // Toggle to enable or disable trailing stop input double trailingStopPoints = 30; // Distance in points for trailing stop adjustment input double minProfitToTrail = 50; // Minimum profit in points before trailing starts input int uniqueMagicNumber = 12345; // Unique identifier for EA trades input int consolidationBars = 7; // Number of bars to check for consolidation range input double maxConsolidationSpread = 50; // Maximum allowed spread in points for consolidation input int barsToWaitAfterBreakout = 3; // Bars to wait after breakout before impulse check input double impulseMultiplier = 1.0; // Multiplier for detecting impulsive price moves input double stopLossDistance = 1500; // Stop loss distance in points from entry input double takeProfitDistance = 1500; // Take profit distance in points from entry input double moveAwayDistance = 50; // Distance in points for price to move away post-invalidation input color bullishColor = clrGreen; // Base color for bullish order/breaker blocks input color bearishColor = clrRed; // Base color for bearish order/breaker blocks input color labelTextColor = clrBlack; // Color for text labels on blocks input bool enableSwingValidation = true; // Enable validation of swing points for block invalidation input bool showSwingPoints = true; // Show swing point labels if validation enabled input color swingLabelColor = clrWhite; // Color for swing point labels input int swingFontSize = 10; // Font size for swing point 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 stops dinámicos, y «trailingStopPoints» y «minProfitToTrail» ajustan la lógica de los stops. «uniqueMagicNumber» identifica las operaciones, y la consolidación se detecta mediante «consolidationBars» y «maxConsolidationSpread». El resto de los parámetros de entrada se explican por sí mismos. Hemos añadido comentarios para facilitar su comprensión. Por último, debemos definir algunas variables globales que utilizaremos para el control general del sistema.
//+------------------------------------------------------------------+ //| Structure for price and index | //+------------------------------------------------------------------+ struct PriceAndIndex { //--- Define structure for price and index data double price; //--- Store price value (high or low) int index; //--- Store bar index of price }; //+------------------------------------------------------------------+ //| Global variables for market state tracking | //+------------------------------------------------------------------+ PriceAndIndex rangeHighestHigh = {0, 0}; //--- Track highest high in consolidation range PriceAndIndex rangeLowestLow = {0, 0}; //--- Track lowest low in consolidation range bool isBreakoutDetected = false; //--- Flag for breakout detection double lastImpulseLow = 0.0; //--- Store low price after breakout for impulse double lastImpulseHigh = 0.0; //--- Store high price after breakout for impulse int breakoutBarNumber = -1; //--- Store bar index of breakout datetime breakoutTimestamp = 0; //--- Store timestamp of breakout string blockNames[]; //--- Store names of block objects datetime blockEndTimes[]; //--- Store end times of blocks bool invalidatedStatus[]; //--- Track invalidation status of blocks string blockTypes[]; //--- Track block types (OB/BB, bullish/bearish) bool movedAwayStatus[]; //--- Track if price moved away after invalidation bool retestedStatus[]; //--- Track if block was retested string blockLabels[]; //--- Store label object names for blocks datetime creationTimes[]; //--- Store creation times of blocks datetime invalidationTimes[]; //--- Store invalidation times of blocks double invalidationSwings[]; //--- Store swing high/low at invalidation bool isBullishImpulse = false; //--- Flag for bullish impulsive move bool isBearishImpulse = false; //--- Flag for bearish impulsive move #define OB_Prefix "OB REC " //--- Define prefix for order block names
Aquí definimos las estructuras de datos y los mecanismos de seguimiento del estado del sistema. En primer lugar, definimos la estructura «PriceAndIndex» para almacenar un valor de precio (máximo o mínimo) y su índice de barra correspondiente, lo que permite un seguimiento preciso de los límites del rango de consolidación. A continuación, inicializamos las variables globales: «rangeHighestHigh» y «rangeLowestLow» como instancias de «PriceAndIndex» para almacenar el máximo más alto y el mínimo más bajo del rango de consolidación; «isBreakoutDetected» con el valor «false» para señalar los eventos de ruptura; «lastImpulseLow» y «lastImpulseHigh» con el valor 0.0 para registrar los precios posteriores a la ruptura y comprobar los impulsos; «breakoutBarNumber» con el valor -1 y «breakoutTimestamp» con el valor 0 para realizar un seguimiento del momento de la ruptura; y las matrices «blockNames», «blockEndTimes», «invalidatedStatus», «blockTypes», «movedAwayStatus», «retestedStatus», «blockLabels», «creationTimes», «invalidationTimes» e «invalidationSwings» para gestionar los objetos de bloque, su caducidad, invalidación, tipos (bloque de órdenes o bloque de ruptura, alcista o bajista), estado de retesteo y puntos de oscilación asociados.
Por último, definimos «OB_Prefix» como «OB REC » para garantizar la coherencia en la denominación de los objetos de bloque de órdenes. Definamos ahora algunas funciones auxiliares que necesitaremos para oscurecer el color de los bloques de órdenes invalidados y gestionar los manejadores de eventos.
//+------------------------------------------------------------------+ //| Darken color by factor | //+------------------------------------------------------------------+ color DarkenColor(color colorValue, double factor = 0.8) { int red = int((colorValue & 0xFF) * factor); //--- Extract and darken red component int green = int(((colorValue >> 8) & 0xFF) * factor); //--- Extract and darken green component int blue = int(((colorValue >> 16) & 0xFF) * factor); //--- Extract and darken blue component return (color)(red | (green << 8) | (blue << 16)); //--- Combine components into color } //+------------------------------------------------------------------+ //| Price data accessors | //+------------------------------------------------------------------+ double high(int index) { return iHigh(_Symbol, _Period, index); //--- Return high price for specified index } double low(int index) { return iLow(_Symbol, _Period, index); //--- Return low price for specified index } double close(int index) { return iClose(_Symbol, _Period, index); //--- Return close price for specified index } datetime time(int index) { return iTime(_Symbol, _Period, index); //--- Return time for specified index } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(uniqueMagicNumber); //--- Set magic number for trade identification return(INIT_SUCCEEDED); //--- Return initialization success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, OB_Prefix); //--- Remove all objects with OB prefix ChartRedraw(0); //--- Redraw chart to clear objects }
Seguimos implementando funciones de utilidad y de gestión del ciclo de vida de nuestro sistema. En primer lugar, desarrollamos la función «DarkenColor», que toma un valor de color y un factor opcional (por defecto, 0.8), extrae los componentes de rojo, verde y azul mediante operaciones bit a bit («colorValue & 0xFF», «(colorValue >> 8) & 0xFF», «(colorValue >> 16) & 0xFF»), los oscurece multiplicándolos por el factor y los combina mediante desplazamientos bit a bit («red | (green << 8) | (blue << 16)») para devolver un color más oscuro que permita distinguir visualmente los bloques invalidados.
A continuación, creamos las funciones de acceso «high», «low», «close» y «time», que devuelven el precio máximo (iHigh), el precio mínimo («iLow»), el precio de cierre («iClose») y la marca de tiempo (iTime) para un índice de barra determinado del símbolo y el periodo actuales, lo que simplifica la obtención de los datos de precios. A continuación, en la función OnInit, llamamos a «SetExpertMagicNumber» en «obj_Trade» con «uniqueMagicNumber» para etiquetar las operaciones a efectos de identificación y devolvemos INIT_SUCCEEDED si la inicialización se ha realizado correctamente. Por último, en la función OnDeinit, utilizamos ObjectsDeleteAll para eliminar todos los objetos del gráfico cuyo nombre comience por el prefijo indicado en «OB_Prefix» y llamamos a ChartRedraw para actualizar el gráfico, lo que garantiza una limpieza adecuada de los recursos. Ahora podemos pasar al manejador de eventos principal OnTick para implementar nuestra lógica de control principal.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static bool isNewBar = false; //--- Track new bar status int currentBarCount = iBars(_Symbol, _Period); //--- Get current bar count static int previousBarCount = currentBarCount; //--- Store previous bar count if (previousBarCount == currentBarCount) { //--- Check if no new bar isNewBar = false; //--- Set no new bar } else { //--- New bar detected isNewBar = true; //--- Set new bar flag previousBarCount = currentBarCount; //--- Update previous bar count } if (!isNewBar) return; //--- Exit if not new bar int startBarIndex = 1; //--- Set start index for analysis int chartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get chart zoom scale int dynamicFontSize = 8 + (chartScale * 2); //--- Calculate dynamic font size if (!isBreakoutDetected) { //--- Check if no breakout detected if (rangeHighestHigh.price == 0 && rangeLowestLow.price == 0) { //--- Check if range not set bool isConsolidated = true; //--- Assume consolidation for (int i = startBarIndex; i < startBarIndex + consolidationBars - 1; i++) { //--- Iterate consolidation bars if (MathAbs(high(i) - high(i + 1)) > maxConsolidationSpread * Point() || MathAbs(low(i) - low(i + 1)) > maxConsolidationSpread * Point()) { //--- Check spread isConsolidated = false; //--- Mark as not consolidated break; //--- Exit loop } } if (isConsolidated) { //--- Confirm consolidation rangeHighestHigh.price = high(startBarIndex); //--- Set initial high rangeHighestHigh.index = startBarIndex; //--- Set high index for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { //--- Find highest high if (high(i) > rangeHighestHigh.price) { //--- Check higher high rangeHighestHigh.price = high(i); //--- Update highest high rangeHighestHigh.index = i; //--- Update high index } } rangeLowestLow.price = low(startBarIndex); //--- Set initial low rangeLowestLow.index = startBarIndex; //--- Set low index for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { //--- Find lowest low if (low(i) < rangeLowestLow.price) { //--- Check lower low rangeLowestLow.price = low(i); //--- Update lowest low rangeLowestLow.index = i; //--- Update low index } } Print("Consolidation range established: Highest High = ", rangeHighestHigh.price, " at index ", rangeHighestHigh.index, " and Lowest Low = ", rangeLowestLow.price, " at index ", rangeLowestLow.index); //--- Log range } } else { //--- Range already set double currentHigh = high(1); //--- Get current high double currentLow = low(1); //--- Get current low if (currentHigh <= rangeHighestHigh.price && currentLow >= rangeLowestLow.price) { //--- Check within range Print("Range extended: High = ", currentHigh, ", Low = ", currentLow); //--- Log range extension } else { //--- Outside range Print("No extension: Bar outside range."); //--- Log no extension } } } }
Comenzamos definiendo una lógica para detectar, en primer lugar, los rangos de consolidación. En la función OnTick, detectamos nuevas barras comparando el número actual de barras obtenido con iBars con la variable estática «previousBarCount». Si se detecta una nueva barra, establecemos «isNewBar» en «true» y actualizamos «previousBarCount»; de lo contrario, establecemos «isNewBar» en «false» y salimos de la función. A continuación, recuperamos la escala de zoom del gráfico con ChartGetInteger utilizando CHART_SCALE y calculamos un «dynamicFontSize» igual a 8 más el doble de la escala para ajustar el tamaño de las etiquetas de forma adaptativa. Si no se detecta ninguna ruptura («isBreakoutDetected» es falso) y no hay ningún rango establecido («rangeHighestHigh.price» y «rangeLowestLow.price» son 0), comprobamos si hay consolidación recorriendo «consolidationBars» barras a partir de «startBarIndex», cuyo valor es 1. Utilizamos Point() en el límite «maxConsolidationSpread * Point()» y MathAbs para comprobar que las diferencias entre los máximos de barras adyacentes y entre sus mínimos sean inferiores a dicho límite; si se supera, establecemos «isConsolidated» en falso.
Si se confirma la consolidación, asignamos a «rangeHighestHigh.price» y «rangeLowestLow.price» el máximo y el mínimo de la barra con índice «startBarIndex», respectivamente, utilizando «high» y «low». A continuación, recorremos «consolidationBars» barras para actualizar esos valores con el máximo más alto y el mínimo más bajo, junto con sus índices, y registramos el rango con la función Print. Si existe un rango, comprobamos si el máximo y el mínimo de la última barra cerrada («high(1)», «low(1)») se encuentran entre los límites «rangeLowestLow.price» y «rangeHighestHigh.price»; si es así, registramos la prolongación del rango y, si quedan fuera, registramos que no se prolonga. Ahora podemos utilizar los precios para detectar, visualizar y gestionar los bloques de órdenes antes de utilizarlos para un análisis más detallado, ya que primero debemos invalidarlos antes de que se conviertan en bloques de ruptura.
if (rangeHighestHigh.price > 0 && rangeLowestLow.price > 0) { //--- Check if range defined double currentClosePrice = close(1); //--- Get current close price if (currentClosePrice > rangeHighestHigh.price) { //--- Check upward breakout Print("Upward breakout at ", currentClosePrice, " > ", rangeHighestHigh.price); //--- Log breakout isBreakoutDetected = true; //--- Set breakout flag } else if (currentClosePrice < rangeLowestLow.price) { //--- Check downward breakout Print("Downward breakout at ", currentClosePrice, " < ", rangeLowestLow.price); //--- Log breakout isBreakoutDetected = true; //--- Set breakout flag } } if (isBreakoutDetected) { //--- Process breakout Print("Breakout detected. Resetting for the next range."); //--- Log reset breakoutBarNumber = 1; //--- Set breakout bar index breakoutTimestamp = TimeCurrent(); //--- Set breakout timestamp lastImpulseHigh = rangeHighestHigh.price; //--- Store high for impulse check lastImpulseLow = rangeLowestLow.price; //--- Store low for impulse check isBreakoutDetected = false; //--- Reset breakout flag rangeHighestHigh.price = 0; //--- Clear highest high rangeHighestHigh.index = 0; //--- Clear high index rangeLowestLow.price = 0; //--- Clear lowest low rangeLowestLow.index = 0; //--- Clear low index } if (breakoutBarNumber >= 0 && TimeCurrent() > breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds()) { //--- Check impulse window double impulseRange = lastImpulseHigh - lastImpulseLow; //--- Calculate impulse range double impulseThresholdPrice = impulseRange * impulseMultiplier; //--- Calculate impulse threshold isBullishImpulse = false; //--- Reset bullish impulse flag isBearishImpulse = false; //--- Reset bearish impulse flag for (int i = 1; i <= barsToWaitAfterBreakout; i++) { //--- Check bars for impulse double closePrice = close(i); //--- Get close price if (closePrice >= lastImpulseHigh + impulseThresholdPrice) { //--- Check bullish impulse isBullishImpulse = true; //--- Set bullish impulse flag Print("Impulsive upward move: ", closePrice, " >= ", lastImpulseHigh + impulseThresholdPrice); //--- Log bullish impulse break; //--- Exit loop } else if (closePrice <= lastImpulseLow - impulseThresholdPrice) { //--- Check bearish impulse isBearishImpulse = true; //--- Set bearish impulse flag Print("Impulsive downward move: ", closePrice, " <= ", lastImpulseLow - impulseThresholdPrice); //--- Log bearish impulse break; //--- Exit loop } } if (!isBullishImpulse && !isBearishImpulse) { //--- Check no impulse Print("No impulsive movement detected."); //--- Log no impulse } bool isOrderBlockValid = isBearishImpulse || isBullishImpulse; //--- Validate order block if (isOrderBlockValid) { //--- Process valid order block datetime blockStartTime = iTime(_Symbol, _Period, consolidationBars + barsToWaitAfterBreakout + 1); //--- Set block start time double blockTopPrice = lastImpulseHigh; //--- Set block top price int visibleBarsOnChart = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get visible bars datetime blockEndTime = blockStartTime + (visibleBarsOnChart / 1) * PeriodSeconds(); //--- Set block end time double blockBottomPrice = lastImpulseLow; //--- Set block bottom price string blockName = OB_Prefix + "(" + TimeToString(blockStartTime) + ")"; //--- Generate block name color blockColor = isBullishImpulse ? bullishColor : bearishColor; //--- Set block color string blockLabel = isBullishImpulse ? "Bullish Order Block" : "Bearish Order Block"; //--- Set block label string blockType = isBullishImpulse ? "OB-bullish" : "OB-bearish"; //--- Set block type if (ObjectFind(0, blockName) < 0) { //--- Check if block exists ObjectCreate(0, blockName, OBJ_RECTANGLE, 0, blockStartTime, blockTopPrice, blockEndTime, blockBottomPrice); //--- Create block rectangle ObjectSetInteger(0, blockName, OBJPROP_TIME, 0, blockStartTime); //--- Set start time ObjectSetDouble(0, blockName, OBJPROP_PRICE, 0, blockTopPrice); //--- Set top price ObjectSetInteger(0, blockName, OBJPROP_TIME, 1, blockEndTime); //--- Set end time ObjectSetDouble(0, blockName, OBJPROP_PRICE, 1, blockBottomPrice); //--- Set bottom price ObjectSetInteger(0, blockName, OBJPROP_FILL, true); //--- Enable fill ObjectSetInteger(0, blockName, OBJPROP_COLOR, blockColor); //--- Set block color ObjectSetInteger(0, blockName, OBJPROP_BACK, false); //--- Set to foreground datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate label time double labelPrice = (blockTopPrice + blockBottomPrice) / 2; //--- Calculate label price string labelObjectName = blockName + " Label"; //--- Generate label name ObjectCreate(0, labelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create label ObjectSetString(0, labelObjectName, OBJPROP_TEXT, blockLabel); //--- Set label text ObjectSetInteger(0, labelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color ObjectSetInteger(0, labelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size ObjectSetInteger(0, labelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor ChartRedraw(0); //--- Redraw chart ArrayResize(blockNames, ArraySize(blockNames) + 1); //--- Resize block names array blockNames[ArraySize(blockNames) - 1] = blockName; //--- Add block name ArrayResize(blockEndTimes, ArraySize(blockEndTimes) + 1); //--- Resize end times array blockEndTimes[ArraySize(blockEndTimes) - 1] = blockEndTime; //--- Add end time ArrayResize(invalidatedStatus, ArraySize(invalidatedStatus) + 1); //--- Resize invalidated status invalidatedStatus[ArraySize(invalidatedStatus) - 1] = false; //--- Set invalidated status ArrayResize(blockTypes, ArraySize(blockTypes) + 1); //--- Resize block types array blockTypes[ArraySize(blockTypes) - 1] = blockType; //--- Add block type ArrayResize(movedAwayStatus, ArraySize(movedAwayStatus) + 1); //--- Resize moved away status movedAwayStatus[ArraySize(movedAwayStatus) - 1] = false; //--- Set moved away status ArrayResize(retestedStatus, ArraySize(retestedStatus) + 1); //--- Resize retested status retestedStatus[ArraySize(retestedStatus) - 1] = false; //--- Set retested status ArrayResize(blockLabels, ArraySize(blockLabels) + 1); //--- Resize block labels array blockLabels[ArraySize(blockLabels) - 1] = labelObjectName; //--- Add label name ArrayResize(creationTimes, ArraySize(creationTimes) + 1); //--- Resize creation times creationTimes[ArraySize(creationTimes) - 1] = time(1); //--- Set creation time ArrayResize(invalidationTimes, ArraySize(invalidationTimes) + 1); //--- Resize invalidation times invalidationTimes[ArraySize(invalidationTimes) - 1] = 0; //--- Set invalidation time ArrayResize(invalidationSwings, ArraySize(invalidationSwings) + 1); //--- Resize invalidation swings invalidationSwings[ArraySize(invalidationSwings) - 1] = 0.0; //--- Set invalidation swing Print("Order Block created: ", blockName); //--- Log block creation } } breakoutBarNumber = -1; //--- Reset breakout bar breakoutTimestamp = 0; //--- Reset breakout timestamp lastImpulseHigh = 0; //--- Reset impulse high lastImpulseLow = 0; //--- Reset impulse low isBullishImpulse = false; //--- Reset bullish impulse isBearishImpulse = false; //--- Reset bearish impulse }
Aquí implementamos la detección de rupturas y la lógica de creación de bloques de órdenes. En primer lugar, comprobamos si hay definido un rango de consolidación («rangeHighestHigh.price > 0» y «rangeLowestLow.price > 0») y obtenemos el precio de cierre de la última barra cerrada con «close(1)». Si supera «rangeHighestHigh.price», registramos una ruptura al alza y establecemos «isBreakoutDetected» en «true»; si está por debajo de «rangeLowestLow.price», registramos una ruptura a la baja y también establecemos el indicador en «true». A continuación, si «isBreakoutDetected» es verdadero, registramos la ruptura, establecemos «breakoutBarNumber» en 1 y «breakoutTimestamp» en TimeCurrent, almacenamos «lastImpulseHigh» y «lastImpulseLow», y restablecemos las variables de rango y el indicador de ruptura.
A continuación, si «breakoutBarNumber» es un número no negativo y la hora actual supera «breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds», calculamos «impulseRange» («lastImpulseHigh - lastImpulseLow») y el umbral («impulseRange * impulseMultiplier»). Después, comprobamos si alguna de las «barsToWaitAfterBreakout» barras tiene un precio de cierre superior a «lastImpulseHigh + impulseThresholdPrice» (en cuyo caso activamos «isBullishImpulse») o inferior a «lastImpulseLow - impulseThresholdPrice» (en cuyo caso activamos «isBearishImpulse»).
Si no se detecta ningún impulso, lo registramos; si el impulso es válido («isBearishImpulse» o «isBullishImpulse»), creamos un bloque de órdenes utilizando ObjectCreate (OBJ_RECTANGLE), con «blockStartTime» obtenido de iTime, los límites superior e inferior tomados de «lastImpulseHigh» y «lastImpulseLow», respectivamente, y la hora de finalización basada en «ChartGetInteger(CHART_VISIBLE_BARS)». Configuramos propiedades como «OBJPROP_FILL» y «OBJPROP_COLOR» («bullishColor» o «bearishColor»), añadimos una etiqueta centrada con «blockLabel» mediante «OBJ_TEXT» y actualizamos las matrices «blockNames», «blockEndTimes», «invalidatedStatus», «blockTypes», «movedAwayStatus», «retestedStatus», «blockLabels», «creationTimes» e «invalidationSwings». Por último, restablecemos las variables de ruptura. Esto permite crear un sistema para detectar rupturas y visualizar bloques de órdenes. Tras la compilación, obtenemos el siguiente resultado.

Ahora que podemos detectar los bloques de órdenes iniciales, definamos una lógica para que, cuando se produzca la correspondiente ruptura del precio, los marquemos como invalidados y confirmemos dicha invalidación mediante los puntos de oscilación de la acción del precio. Oscureceremos su color para diferenciarlos.
for (int j = ArraySize(blockNames) - 1; j >= 0; j--) { //--- Iterate through blocks string currentBlockName = blockNames[j]; //--- Get current block name bool doesBlockExist = false; //--- Initialize block existence flag double blockHigh = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 0); //--- Get block high double blockLow = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 1); //--- Get block low datetime blockStartTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 0); //--- Get block start datetime blockEndTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 1); //--- Get block end color blockCurrentColor = (color)ObjectGetInteger(0, currentBlockName, OBJPROP_COLOR); //--- Get block color if (time(1) < blockEndTime) { //--- Check if block still valid doesBlockExist = true; //--- Set block exists } if (StringFind(blockTypes[j], "OB-") == 0 && !invalidatedStatus[j]) { //--- Check valid order block bool invalidated = false; //--- Initialize invalidation flag string newBlockType = ""; //--- Initialize new block type color invalidatedColor = clrNONE; //--- Initialize invalidated color string newLabel = ""; //--- Initialize new label bool isForBullishBB = false; //--- Initialize bullish breaker block flag double breakPrice = 0.0; //--- Initialize break price int arrowCode = 0; //--- Initialize arrow code int anchor = 0; //--- Initialize anchor if (blockTypes[j] == "OB-bearish" && close(1) > blockHigh) { //--- Check bearish block invalidation isForBullishBB = true; //--- Set bullish breaker block breakPrice = blockHigh; //--- Set break price arrowCode = 233; //--- Set upward arrow anchor = ANCHOR_BOTTOM; //--- Set bottom anchor newBlockType = "Invalidated-bearish"; //--- Set invalidated type invalidatedColor = DarkenColor(bearishColor); //--- Darken bearish color newLabel = "Invalidated Bearish Order Block"; //--- Set invalidated label } else if (blockTypes[j] == "OB-bullish" && close(1) < blockLow) { //--- Check bullish block invalidation isForBullishBB = false; //--- Set bearish breaker block breakPrice = blockLow; //--- Set break price arrowCode = 234; //--- Set downward arrow anchor = ANCHOR_TOP; //--- Set top anchor newBlockType = "Invalidated-bullish"; //--- Set invalidated type invalidatedColor = DarkenColor(bullishColor); //--- Darken bullish color newLabel = "Invalidated Bullish Order Block"; //--- Set invalidated label } else { //--- No invalidation continue; //--- Skip to next block } bool validSwingForInvalidation = true; //--- Assume valid swing int swingShift = -1; //--- Initialize swing shift double swingPrice = 0.0; //--- Initialize swing price if (enableSwingValidation) { //--- Check swing validation int creationShift = iBarShift(_Symbol, _Period, creationTimes[j], false); //--- Get creation bar shift if (creationShift > 1) { //--- Ensure enough bars double extreme = isForBullishBB ? blockLow : blockHigh; //--- Set extreme price bool isBearishOB = isForBullishBB; //--- Set bearish OB flag if (isBearishOB) { //--- Handle bearish OB double minLow = extreme; //--- Initialize minimum low for (int k = creationShift - 1; k > 1; k--) { //--- Find lower low if (low(k) < minLow) { //--- Check lower low minLow = low(k); //--- Update minimum low swingShift = k; //--- Update swing shift } } validSwingForInvalidation = minLow < extreme; //--- Validate swing swingPrice = minLow; //--- Set swing price } else { //--- Handle bullish OB double maxHigh = extreme; //--- Initialize maximum high for (int k = creationShift - 1; k > 1; k--) { //--- Find higher high if (high(k) > maxHigh) { //--- Check higher high maxHigh = high(k); //--- Update maximum high swingShift = k; //--- Update swing shift } } validSwingForInvalidation = maxHigh > extreme; //--- Validate swing swingPrice = maxHigh; //--- Set swing price } } else { //--- Insufficient bars validSwingForInvalidation = false; //--- Invalidate swing } } if (validSwingForInvalidation) { //--- Confirm swing validation invalidated = true; //--- Set invalidated flag } if (invalidated) { //--- Process invalidation ObjectSetInteger(0, currentBlockName, OBJPROP_COLOR, invalidatedColor); //--- Update block color ObjectDelete(0, blockLabels[j]); //--- Delete old label datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate new label time double labelPrice = (blockHigh + blockLow) / 2; //--- Calculate new label price string newLabelObjectName = currentBlockName + " Label"; //--- Generate new label name ObjectCreate(0, newLabelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create new label ObjectSetString(0, newLabelObjectName, OBJPROP_TEXT, newLabel); //--- Set label text ObjectSetInteger(0, newLabelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color ObjectSetInteger(0, newLabelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size ObjectSetInteger(0, newLabelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor string arrowName = currentBlockName + "_break_arrow"; //--- Generate arrow name if (ObjectFind(0, arrowName) < 0) { //--- Check if arrow exists ObjectCreate(0, arrowName, OBJ_ARROW, 0, time(1), breakPrice); //--- Create break arrow ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode); //--- Set arrow code ObjectSetInteger(0, arrowName, OBJPROP_ANCHOR, anchor); //--- Set arrow anchor ObjectSetInteger(0, arrowName, OBJPROP_COLOR, invalidatedColor); //--- Set arrow color } if (enableSwingValidation && showSwingPoints && swingShift > 0) { //--- Check swing point display string swingLabelName = currentBlockName + "_invalid_swing"; //--- Generate swing label name if (ObjectFind(0, swingLabelName) < 0) { //--- Check if swing label exists datetime swingTime = time(swingShift); //--- Get swing time ObjectCreate(0, swingLabelName, OBJ_TEXT, 0, swingTime, swingPrice); //--- Create swing label string swingText = isForBullishBB ? "LL" : "HH"; //--- Set swing text ObjectSetString(0, swingLabelName, OBJPROP_TEXT, swingText); //--- Set swing label text ObjectSetInteger(0, swingLabelName, OBJPROP_COLOR, swingLabelColor); //--- Set swing label color ObjectSetInteger(0, swingLabelName, OBJPROP_FONTSIZE, swingFontSize); //--- Set swing label font size ObjectSetInteger(0, swingLabelName, OBJPROP_ANCHOR, isForBullishBB ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); //--- Set swing label anchor } } ChartRedraw(0); //--- Redraw chart invalidatedStatus[j] = true; //--- Set invalidated status blockTypes[j] = newBlockType; //--- Update block type movedAwayStatus[j] = false; //--- Reset moved away status retestedStatus[j] = false; //--- Reset retested status blockLabels[j] = newLabelObjectName; //--- Update label name invalidationTimes[j] = time(1); //--- Set invalidation time invalidationSwings[j] = isForBullishBB ? high(1) : low(1); //--- Set invalidation swing Print("Order Block invalidated: ", currentBlockName); //--- Log invalidation } } if (!doesBlockExist) { //--- Check if block expired ArrayRemove(blockNames, j, 1); //--- Remove block name ArrayRemove(blockEndTimes, j, 1); //--- Remove end time ArrayRemove(invalidatedStatus, j, 1); //--- Remove invalidated status ArrayRemove(blockTypes, j, 1); //--- Remove block type ArrayRemove(movedAwayStatus, j, 1); //--- Remove moved away status ArrayRemove(retestedStatus, j, 1); //--- Remove retested status ArrayRemove(blockLabels, j, 1); //--- Remove label name ArrayRemove(creationTimes, j, 1); //--- Remove creation time ArrayRemove(invalidationTimes, j, 1); //--- Remove invalidation time ArrayRemove(invalidationSwings, j, 1); //--- Remove invalidation swing Print("Removed expired block at index ", j); //--- Log block removal } }
Para implementar la lógica de gestión e invalidación de bloques de órdenes, recorremos «blockNames» hacia atrás para procesar cada bloque, recuperando sus precios máximo y mínimo con ObjectGetDouble y las horas de inicio y fin con ObjectGetInteger, y marcándolo como existente si la hora de apertura de la última barra cerrada («time(1)») es anterior a su hora de fin. Para los bloques de órdenes válidos (cuyo tipo comienza por «OB-» y que no están invalidados), comprobamos si se cumplen las condiciones de invalidación: si el tipo es «OB-bearish» y el precio de cierre («close(1)») supera el máximo del bloque, creamos un bloque de ruptura alcista con una flecha hacia arriba (código 233) utilizando «ObjectCreate» (OBJ_ARROW) y un color oscurecido mediante «DarkenColor»; si el tipo es «OB-bullish» y el precio de cierre está por debajo del mínimo del bloque, creamos un bloque de ruptura bajista con una flecha hacia abajo (código 234). MQL5 ofrece una amplia lista de códigos Wingdings, tal y como se muestra a continuación, para que puedas utilizarlos como prefieras.

Si «enableSwingValidation» es «true», validamos el bloque comprobando si se ha registrado un mínimo más bajo (para un bloque bajista) o un máximo más alto (para un bloque alcista) desde su creación mediante iBarShift y «low»/«high»; si es válido, actualizamos su color y su etiqueta con ObjectSetInteger y ObjectCreate (OBJ_TEXT). Si la opción «showSwingPoints» está activada, añadimos una etiqueta («LL» o «HH») con «ObjectCreate» en la hora y el precio del punto de oscilación. En caso de invalidación, actualizamos los estados de los bloques con «invalidatedStatus», «blockTypes» e «invalidationTimes», registramos la invalidación y restablecemos el estado del retesteo. Si un bloque no existe, utilizamos ArrayRemove para eliminar sus estados, como «invalidatedStatus», de las matrices de almacenamiento y registramos la eliminación; a continuación, volvemos a dibujar el gráfico con la función ChartRedraw. Al compilar, deberías obtener algo parecido a lo siguiente.

Ahora que hemos completado el segundo paso, el de la invalidación, podemos pasar al siguiente, en el que hacemos un seguimiento y esperamos a que el precio realice un retesteo de nuestros bloques de órdenes invalidados para marcarlos como bloques de ruptura.
for (int j = ArraySize(blockNames) - 1; j >= 0; j--) { //--- Iterate invalidated blocks if (StringFind(blockTypes[j], "Invalidated-") != 0) continue; //--- Skip non-invalidated string currentBlockName = blockNames[j]; //--- Get current block name double blockHigh = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 0); //--- Get block high double blockLow = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 1); //--- Get block low datetime blockStartTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 0); //--- Get block start datetime blockEndTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 1); //--- Get block end bool isForBullishBB = (blockTypes[j] == "Invalidated-bearish"); //--- Check for bullish breaker block datetime currentBarTime = time(1); //--- Get current bar time if (currentBarTime <= invalidationTimes[j]) continue; //--- Skip if same or earlier bar if (!movedAwayStatus[j]) { //--- Check if not moved away if (isForBullishBB && close(1) > blockHigh + moveAwayDistance * _Point) { //--- Check bullish move away movedAwayStatus[j] = true; //--- Set moved away Print("Moved away for bullish BB setup: ", currentBlockName); //--- Log move away } else if (!isForBullishBB && close(1) < blockLow - moveAwayDistance * _Point) { //--- Check bearish move away movedAwayStatus[j] = true; //--- Set moved away Print("Moved away for bearish BB setup: ", currentBlockName); //--- Log move away } } if (movedAwayStatus[j] && !retestedStatus[j]) { //--- Check for retest bool retestCondition = false; //--- Initialize retest condition if (isForBullishBB && low(1) <= blockHigh && close(1) > blockHigh) { //--- Check bullish retest retestCondition = true; //--- Set retest condition } else if (!isForBullishBB && high(1) >= blockLow && close(1) < blockLow) { //--- Check bearish retest retestCondition = true; //--- Set retest condition } bool validSwingForRetest = true; //--- Assume valid swing int swingShift = -1; //--- Initialize swing shift double swingPrice = 0.0; //--- Initialize swing price if (enableSwingValidation && retestCondition) { //--- Check swing validation int invalidShift = iBarShift(_Symbol, _Period, invalidationTimes[j], false); //--- Get invalidation shift if (invalidShift > 1) { //--- Ensure enough bars double extreme = invalidationSwings[j]; //--- Get invalidation swing if (isForBullishBB) { //--- Handle bullish breaker block double maxHigh = extreme; //--- Initialize maximum high for (int k = invalidShift - 1; k > 1; k--) { //--- Find higher high if (high(k) > maxHigh) { //--- Check higher high maxHigh = high(k); //--- Update maximum high swingShift = k; //--- Update swing shift } } validSwingForRetest = maxHigh > extreme; //--- Validate swing swingPrice = maxHigh; //--- Set swing price } else { //--- Handle bearish breaker block double minLow = extreme; //--- Initialize minimum low for (int k = invalidShift - 1; k > 1; k--) { //--- Find lower low if (low(k) < minLow) { //--- Check lower low minLow = low(k); //--- Update minimum low swingShift = k; //--- Update swing shift } } validSwingForRetest = minLow < extreme; //--- Validate swing swingPrice = minLow; //--- Set swing price } } else { //--- Insufficient bars validSwingForRetest = false; //--- Invalidate swing } } } }
Para implementar la lógica de detección de retesteos de los bloques de órdenes invalidados, recorremos hacia atrás la lista «blockNames» en busca de bloques que contengan «Invalidated-» en «blockTypes», recuperando los precios máximo y mínimo con ObjectGetDouble y las horas de inicio y fin con ObjectGetInteger, y comprobando si el bloque es un bloque de ruptura alcista («Invalidated-bearish»). Si la hora de apertura de la última barra cerrada («time(1)») no es posterior a «invalidationTimes», pasamos al siguiente bloque. Para los bloques de los que el precio aún no se ha alejado («movedAwayStatus» en «false»), comprobamos si el precio de cierre («close(1)») supera «blockHigh + moveAwayDistance * _Point» en el caso alcista o cae por debajo de «blockLow - moveAwayDistance * _Point» en el caso bajista; si se cumple la condición, establecemos «movedAwayStatus» en «true».
Para los bloques de los que el precio ya se ha alejado pero que aún no ha retesteado («retestedStatus» en «false»), identificamos un retesteo alcista si «low(1)» alcanza «blockHigh» y «close(1)» está por encima de ese nivel, o un retesteo bajista si «high(1)» alcanza «blockLow» y «close(1)» está por debajo de ese nivel. Si «enableSwingValidation» está activado y se cumple una condición de retesteo, validamos los puntos de oscilación utilizando iBarShift para obtener la barra de invalidación. Comprobamos si, desde la invalidación, se ha producido un máximo más alto («high») en el caso alcista o un mínimo más bajo («low») en el caso bajista, y establecemos «validSwingForRetest» y «swingPrice» en consecuencia. Así creamos un sistema para identificar oportunidades válidas de retesteo tras un movimiento del precio. Podemos realizar un seguimiento de los retesteos, marcar los bloques como bloques de ruptura, cambiar su color para distinguirlos y abrir posiciones. Esta es la lógica que usamos para lograrlo.
if (retestCondition && validSwingForRetest) { //--- Confirm retest and swing if (enableTrading) { //--- Check trading enabled double entryPrice = 0.0; //--- Initialize entry price double stopLossPrice = 0.0; //--- Initialize stop loss double takeProfitPrice = 0.0; //--- Initialize take profit if (isForBullishBB) { //--- Handle bullish trade entryPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Set entry at ask stopLossPrice = entryPrice - stopLossDistance * _Point; //--- Set stop loss takeProfitPrice = entryPrice + takeProfitDistance * _Point; //--- Set take profit obj_Trade.Buy(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); //--- Execute buy trade Print("Buy trade on bullish BB retest: ", currentBlockName); //--- Log buy trade } else { //--- Handle bearish trade entryPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Set entry at bid stopLossPrice = entryPrice + stopLossDistance * _Point; //--- Set stop loss takeProfitPrice = entryPrice - takeProfitDistance * _Point; //--- Set take profit obj_Trade.Sell(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); //--- Execute sell trade Print("Sell trade on bearish BB retest: ", currentBlockName); //--- Log sell trade } } color bbColor = isForBullishBB ? clrBlueViolet : clrOrange; //--- Set breaker block color ObjectSetInteger(0, currentBlockName, OBJPROP_COLOR, bbColor); //--- Update block color ObjectDelete(0, blockLabels[j]); //--- Delete old label string newLabel = isForBullishBB ? "Bullish Breaker Block" : "Bearish Breaker Block"; //--- Set new label datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate label time double labelPrice = (blockHigh + blockLow) / 2; //--- Calculate label price string newLabelObjectName = currentBlockName + " Label"; //--- Generate new label name ObjectCreate(0, newLabelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create new label ObjectSetString(0, newLabelObjectName, OBJPROP_TEXT, newLabel); //--- Set label text ObjectSetInteger(0, newLabelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color ObjectSetInteger(0, newLabelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size ObjectSetInteger(0, newLabelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor if (enableSwingValidation && showSwingPoints && swingShift > 0) { //--- Check swing point display string swingLabelName = currentBlockName + "_retest_swing"; //--- Generate swing label name if (ObjectFind(0, swingLabelName) < 0) { //--- Check if swing label exists datetime swingTime = time(swingShift); //--- Get swing time ObjectCreate(0, swingLabelName, OBJ_TEXT, 0, swingTime, swingPrice); //--- Create swing label string swingText = isForBullishBB ? "HH" : "LL"; //--- Set swing text ObjectSetString(0, swingLabelName, OBJPROP_TEXT, swingText); //--- Set swing label text ObjectSetInteger(0, swingLabelName, OBJPROP_COLOR, swingLabelColor); //--- Set swing label color ObjectSetInteger(0, swingLabelName, OBJPROP_FONTSIZE, swingFontSize); //--- Set swing label font size ObjectSetInteger(0, swingLabelName, OBJPROP_ANCHOR, isForBullishBB ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); //--- Set swing label anchor } } ChartRedraw(0); //--- Redraw chart blockTypes[j] = isForBullishBB ? "BB-bullish" : "BB-bearish"; //--- Update block type retestedStatus[j] = true; //--- Set retested status blockLabels[j] = newLabelObjectName; //--- Update label name Print("Converted to ", newLabel, ": ", currentBlockName); //--- Log conversion }
Por último, implementamos la lógica de negociación y visualización para los bloques de ruptura sometidos a retesteo. Si se confirma un retesteo («retestCondition» y «validSwingForRetest» son verdaderos) y la negociación está habilitada («enableTrading»), ejecutamos operaciones: para un bloque de ruptura alcista («isForBullishBB»), establecemos la entrada al precio ask utilizando SymbolInfoDouble, calculamos el nivel de stop loss («stopLossDistance * _Point» por debajo de la entrada) y el nivel de take profit («takeProfitDistance * _Point» por encima de la entrada), y ejecutamos una compra con «obj_Trade.Buy»; para un bloque bajista, utilizamos el precio bid, fijamos el nivel de stop loss por encima de la entrada y el nivel de take profit por debajo, y ejecutamos una venta con «obj_Trade.Sell», registrando lo que corresponda.
A continuación, actualizamos el aspecto del bloque estableciendo su color en clrBlueViolet si es alcista o en «clrOrange» si es bajista mediante ObjectSetInteger, eliminamos la etiqueta anterior con ObjectDelete y creamos una nueva etiqueta OBJ_TEXT («Bullish Breaker Block» o «Bearish Breaker Block») en el punto medio del bloque utilizando «ObjectCreate» con «labelTextColor» y «dynamicFontSize». Si «enableSwingValidation» y «showSwingPoints» son verdaderos y se dispone de un «swingShift» válido, añadimos una etiqueta al punto de oscilación («HH» en el caso alcista y «LL» en el bajista), en el instante y al precio de dicho punto, utilizando ObjectCreate con «swingLabelColor» y «swingFontSize». Por último, actualizamos «blockTypes» a «BB-bullish» o «BB-bearish», establecemos «retestedStatus» en «true», actualizamos «blockLabels», registramos la conversión y volvemos a dibujar el gráfico. Tras la compilación, obtenemos el siguiente resultado.

En la imagen se puede observar que podemos detectar, visualizar y operar con los bloques de ruptura en consecuencia. Lo que tenemos que hacer ahora es añadir una lógica de stop dinámico para maximizar los beneficios, y con eso ya estará todo listo. Para ello, definimos una función que permite modularizar el código.
//+------------------------------------------------------------------+ //| Apply trailing stop to open positions | //+------------------------------------------------------------------+ void applyTrailingStop(double trailingPoints, CTrade &trade_object, int magicNo = 0) { double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingPoints * _Point, _Digits); //--- Calculate buy stop loss double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingPoints * _Point, _Digits); //--- Calculate sell stop loss for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through open positions ulong ticket = PositionGetTicket(i); //--- Get position ticket if (ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)) { //--- Verify position if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && buyStopLoss > PositionGetDouble(POSITION_PRICE_OPEN) && (buyStopLoss > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { //--- Check buy trailing trade_object.PositionModify(ticket, buyStopLoss, PositionGetDouble(POSITION_TP)); //--- Update buy stop loss } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && sellStopLoss < PositionGetDouble(POSITION_PRICE_OPEN) && (sellStopLoss < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { //--- Check sell trailing trade_object.PositionModify(ticket, sellStopLoss, PositionGetDouble(POSITION_TP)); //--- Update sell stop loss } } } } //--- Call the function on every tick //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (enableTrailingStop) { //--- Check if trailing stop enabled applyTrailingStop(trailingStopPoints, obj_Trade, uniqueMagicNumber); //--- Apply trailing stop } //--- }
Aquí implementamos la funcionalidad de stop dinámico y la integramos en la lógica basada en eventos. En primer lugar, desarrollamos la función «applyTrailingStop», que calcula el nivel de stop loss de una posición de compra como el precio bid actual (SymbolInfoDouble con SYMBOL_BID) menos «trailingPoints * _Point», y el nivel de stop loss de una posición de venta como el precio ask («SYMBOL_ASK») más «trailingPoints * _Point», ambos normalizados con NormalizeDouble al número de dígitos del símbolo. Recorremos en orden inverso las posiciones abiertas utilizando PositionsTotal, obtenemos el ticket de cada posición con PositionGetTicket y comprobamos que su símbolo y su número mágico coincidan con el símbolo utilizado y con «magicNo» (si es distinto de cero), respectivamente, mediante las funciones PositionGetString y «PositionGetInteger».
Para las posiciones de compra (POSITION_TYPE_BUY), comprobamos que «buyStopLoss» esté por encima del precio de apertura («PositionGetDouble(POSITION_PRICE_OPEN)») y que supere el nivel de stop loss actual o que este aún no esté definido; si se cumplen estas condiciones, actualizamos el nivel con «trade_object.PositionModify». Para las posiciones de venta, comprobamos que «sellStopLoss» esté por debajo del precio de apertura y que sea inferior al nivel de stop loss actual o que este aún no esté definido, y lo actualizamos de forma similar. A continuación, en la función OnTick, comprobamos si «enableTrailingStop» es verdadero y llamamos a «applyTrailingStop» con «trailingStopPoints», «obj_Trade» y «uniqueMagicNumber» para gestionar las posiciones abiertas en cada tick. Tras la compilación, obtenemos el siguiente resultado.

En la imagen se puede observar que el stop dinámico se activa por completo cuando el precio evoluciona a nuestro favor. A continuación se muestra una prueba conjunta de los bloques de ruptura bajistas y alcistas.

En la visualización podemos observar 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 adecuados, logrando así nuestro objetivo. Solo queda realizar las pruebas con datos históricos del programa, que se abordan en la siguiente sección.
Backtesting
Tras realizar pruebas exhaustivas con datos históricos, hemos obtenido los siguientes resultados.
Gráfico de las pruebas con datos históricos:

Informe de las pruebas con datos históricos:

Conclusión
En conclusión, hemos creado un sistema de trading basado en bloques de ruptura en MQL5 para identificar rangos de consolidación, validar los bloques de ruptura mediante puntos de oscilación y ejecutar operaciones de retesteo con parámetros de riesgo personalizables y stops dinámicos. El sistema muestra los bloques de órdenes y los bloques de ruptura con etiquetas y flechas dinámicas, lo que facilita la toma de decisiones de trading.
Aviso legal: el presente artículo tiene fines únicamente educativos. Las operaciones de trading conllevan riesgos financieros importantes, y la volatilidad del mercado puede dar lugar a pérdidas. Resulta fundamental realizar un backtesting exhaustivo y aplicar una gestión del riesgo rigurosa antes de implementar este programa en mercados reales.
Con esta estrategia de bloques de ruptura, estarás preparado para aprovechar las oportunidades de retesteo del precio y seguir perfeccionando la estrategia en tu trayectoria como operador. ¡Te deseo un buen trading!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19638
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.
. He intentado añadir el archivo compartido, pero no se muestra en el gráfico; es decir, no ocurre nada en él ni se realizan operaciones. ¿Podrías ayudarme con el proceso de ejecución?
. Gracias de antemano.
— Si me preguntáis de dónde salen estos cuentos y leyendas, os diré, os responderé: ¡me los he sacado de la manga!
Un 1,6 % de beneficio totalmente aleatorio en 8 meses, ¡eso sí que es un resultado impresionante!