
Desarrollo de un Asesor Experto (EA) en MQL5 basado en la estrategia de ruptura del rango de consolidación
Introducción
En este artículo, exploraremos el desarrollo de un Asesor Experto (EA) basado en la estrategia de ruptura del rango de consolidación utilizando MetaQuotes Language 5 (MQL5), el lenguaje de programación para MetaTrader 5 (MT5). En el vertiginoso mundo de las operaciones financieras, las estrategias que aprovechan los patrones y comportamientos del mercado son cruciales para el éxito, y una de esas estrategias es la ruptura del rango de consolidación, que se centra en identificar períodos de consolidación del mercado y operar las rupturas posteriores. Esta estrategia es particularmente eficaz para capturar movimientos de precios significativos que siguen a un período de baja volatilidad. Diseñaremos el Asesor Experto con una estrategia de ruptura del rango de consolidación a través de los siguientes temas:
- Descripción general de la estrategia
- Plan estratégico
- Implementación en MetaQuotes Language 5 (MQL5)
- Resultados de las pruebas retrospectivas
- Conclusión
Al final de este artículo, usted tendrá una comprensión completa de cómo desarrollar e implementar un EA robusto basado en la estrategia de ruptura del rango de consolidación en MQL5, equipándolo con el conocimiento para mejorar su conjunto de herramientas de trading. Utilizaremos ampliamente MetaQuotes Language 5 (MQL5) como nuestro entorno de codificación base Integrated Development Environment (IDE), y ejecutaremos los archivos en el terminal de trading MetaTrader 5 (MT5). Por lo tanto, contar con las versiones mencionadas anteriormente será de vital importancia. En marcha.
Descripción general de la estrategia
Para comprender fácilmente la estrategia de ruptura del rango de consolidación, dividámosla en trozos.
- Explicación del rango de consolidación
Un rango de consolidación, también conocido como rango de negociación, es un periodo en el que el precio de un instrumento financiero oscila horizontalmente dentro de un rango definido sin mostrar fuertes movimientos al alza o a la baja. Este periodo se caracteriza por una baja volatilidad, con el precio oscilando entre un nivel de soporte bien definido (el límite inferior) y un nivel de resistencia (el límite superior). Los operadores suelen utilizar esta fase para prever posibles puntos de ruptura, en los que el precio puede moverse enormemente en una dirección una vez finalizado el periodo de consolidación.
- Cómo funciona la estrategia
La estrategia de ruptura del rango de consolidación aprovecha el comportamiento predecible de los precios durante los períodos de consolidación para identificar y negociar rupturas. Así es como funciona:
Identificar el rango de consolidación: El primer paso es detectar un rango de consolidación examinando los movimientos de precios recientes. Esto implica identificar los precios más altos y más bajos a lo largo de un número específico de barras (velas) para definir los límites superior e inferior del rango, que normalmente actúan como niveles de resistencia y soporte del rango. La selección del marco temporal aquí no es estática, ya que se puede utilizar cualquier gráfico para el proceso de identificación, por lo tanto, debe seleccionar un marco temporal del gráfico que se adapte a su estilo comercial.
Vigilancia de rupturas: Una vez establecido el rango de consolidación, la estrategia vigila los movimientos de precios que rompen los límites superior o inferior del rango. Una ruptura se produce cuando el precio cierra por encima del nivel de resistencia o por debajo del nivel de soporte. Otros traders consideran hacer scalping con la misma vela que rompe el rango, es decir, una vez que el precio cae por debajo o sube por encima del rango extremo, ya consideran una ruptura.
Comerciar en la ruptura: Al detectar una ruptura, la estrategia inicia una operación en la dirección de la ruptura. Si el precio supera el nivel de resistencia, se coloca una orden de compra. Por el contrario, si el precio rompe por debajo del nivel de soporte, se coloca una orden de venta. Nuevamente, algunos traders consideran esperar un retroceso, es decir, después de una ruptura, para una mayor confirmación, esperan a que el precio vuelva a visitar el rango y una vez que vuelve a romper en la misma dirección que la inicial, se infiltran en el mercado. Para su uso, no consideraremos la opción de retroceso.
- Implementación de la estrategia
La implementación de la estrategia de ruptura del rango de consolidación implicará varios pasos:
Defina los parámetros del rango: Determine el número de barras a analizar para identificar el rango de consolidación y establezca los criterios de lo que constituye una ruptura. Se determina y fija un rango de barras y un rango objetivo en el precio. Por ejemplo, para que un rango de consolidación sea válido, se requieren al menos 10 barras dentro de un rango de precios de 700 puntos o 70 pips.
Desarrollar la lógica de detección: Escriba código para escanear datos de precios históricos, identificar el máximo más alto y el mínimo más bajo dentro del rango especificado y trazar estos niveles en el gráfico. El código debe ser claro en cuanto a las condiciones que deben cumplirse para que el rango de consolidación se considere válido, y las suposiciones realizadas deben describirse claramente para evitar ambigüedades.
Monitorizar datos de precios en tiempo real: Monitoree continuamente los datos de precios entrantes para detectar rupturas tan pronto como ocurran. Se debe realizar un seguimiento en cada tick y, si no es necesario, en cada nueva generación de velas.
Ejecutar operaciones: Implementar la lógica de ejecución comercial para colocar órdenes de compra o venta cuando se detecta una ruptura, incluido el establecimiento de niveles adecuados de Stop Loss y Take Profit para gestionar el riesgo.
Optimizar y probar: Realice pruebas retrospectivas de la estrategia utilizando datos históricos para optimizar los parámetros y garantizar su eficacia antes de implementarla en operaciones reales. Esto le ayudará a identificar los mejores parámetros y a señalar las características clave que deben mejorarse o filtrarse para potenciar y mejorar el sistema.
Siguiendo estos pasos, podemos crear una herramienta poderosa para explotar las condiciones del mercado utilizando la estrategia de ruptura del rango de consolidación. En pocas palabras, estos son algunos de los parámetros que se necesitan para la creación de la estrategia, así como una visión general de lo que normalmente se requiere.
Plan estratégico
Para comprender fácilmente el concepto que hemos transmitido, visualicémoslo en un plano.
- Ruptura de nivel superior del rango de consolidación:
- Ruptura de nivel inferior del rango de consolidación:
Implementación en MetaQuotes Language 5 (MQL5)
Después de aprender los pasos básicos y el enfoque que debe adoptarse para elaborar la estrategia de ruptura del rango de consolidación, vamos a automatizar la teoría y elaborar un Asesor Experto (EA) en MetaQuotes Language 5 (MQL5) para MetaTrader 5 (MT5).
Para crear un asesor experto (EA), en su terminal MetaTrader 5, haga clic en la pestaña Herramientas y marque MetaQuotes Language Editor, o simplemente pulse F4 en su teclado. También puede hacer clic en el icono IDE (Integrated Development Environment) de la barra de herramientas. Esto abrirá el entorno del Editor de lenguaje de MetaQuotes 5 (MQL5), que permite escribir robots comerciales, indicadores técnicos, scripts y bibliotecas de funciones.
Una vez abierto MetaEditor, en la barra de herramientas, navega hasta la pestaña Archivo y marca Nuevo Archivo, o simplemente pulsa CTRL + N, para crear un nuevo documento. Alternativamente, puede hacer clic en el icono Nuevo en la pestaña de herramientas. Esto generará una ventana emergente del Asistente MQL (MQL Wizard).
En el Asistente que aparece, marque Asesor experto (plantilla) y haga clic en Siguiente.
En las propiedades generales del Asesor Experto, debajo de la sección de nombre, proporcione el nombre de archivo de su experto. Tenga en cuenta que para especificar o crear una carpeta si no existe, se utiliza la barra invertida antes del nombre del EA. Por ejemplo, aquí tenemos «Experts\» por defecto. Esto significa que nuestro EA se creará en la carpeta Experts y podremos encontrarlo allí. Las demás secciones son bastante sencillas, pero puede seguir el enlace que figura al final del Asistente para saber cómo realizar el proceso con precisión.
Después de proporcionar el nombre de archivo del Asesor Experto que desee, haga clic en Siguiente, vuelva a hacer clic en Siguiente y, a continuación, haga clic en Finalizar. Después de hacer todo esto, ya estamos listos para codificar y programar nuestra estrategia.
En primer lugar, incluimos una instancia de comercio utilizando #include al principio del código fuente. Esto nos da acceso a la clase CTrade, que utilizaremos para crear un objeto comercial. Esto es crucial, ya que lo necesitamos para abrir operaciones.
#include <Trade/Trade.mqh> // Include the trade library CTrade obj_Trade; // Create an instance of the CTrade class
El preprocesador sustituirá la línea #include <Trade/Trade.mqh> por el contenido del archivo Trade.mqh. Los corchetes indican que el archivo Trade.mqh se tomará del directorio estándar (normalmente es directorio_de_instalación_del_terminal\MQL5\Include). El directorio actual no se incluye en la búsqueda. La línea puede colocarse en cualquier parte del programa, pero normalmente, todas las inclusiones se colocan al principio del código fuente, para una mejor estructura del código y una referencia más fácil. La declaración del objeto obj_Trade de la clase CTrade nos dará acceso a los métodos contenidos en dicha clase fácilmente, gracias a los desarrolladores de MQL5.
Dado que necesitaremos dibujar un rango gráficamente en el gráfico, necesitaremos su nombre. Solo necesitamos y usaremos el mismo objeto de rango de rectángulo y, por lo tanto, se usará un solo objeto para la visualización y, una vez trazado, simplemente actualizaremos su configuración sin tener que volver a dibujarlo. Para lograr un nombre estático que pueda recuperarse fácilmente y reutilizarse instantáneamente, lo definimos de la siguiente manera.
#define rangeNAME "CONSOLIDATION RANGE" // Define the name of the consolidation range
Usamos la palabra clave #define para definir una macro llamada «rangeNAME» con el valor «CONSOLIDATION RANGE» para almacenar fácilmente nuestro nombre de rango de consolidación, en lugar de tener que volver a escribir repetidamente el nombre en cada instancia que creamos el nivel, lo que nos ahorra significativamente tiempo y reduce las posibilidades de proporcionar erróneamente el nombre. Básicamente, las macros se utilizan para sustituir texto durante la compilación.
De nuevo, necesitaremos almacenar las coordenadas del rectángulo que será trazado. Se trata de ordenadas bidimensionales (2D) del formato (x, y), utilizadas para identificar explícitamente la primera y segunda ubicaciones documentadas como x1,y1, y x2,y2 respectivamente. En un gráfico de precios, el eje x está representado por la escala de fechas y horas, mientras que el eje y está representado por la escala de precios. Para facilitar la comprensión y las referencias, veamos una ilustración.
A partir de la imagen presentada, ahora está claro por qué necesitamos las coordenadas para el trazado del objeto rectángulo. A continuación se muestra la lógica que se utiliza para asegurar que almacenamos las coordenadas del rango sin tener que declararlas cada vez que actualizamos las coordenadas.
datetime TIME1_X1, TIME2_Y2; // Declare datetime variables to hold range start and end times double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices
Aquí, declaramos dos variables del tipo de datos datetime. El método que utilizamos aquí se llama declaración de tipo de datos único que declara múltiples variables del mismo tipo de datos. Se trata de una forma abreviada de declarar varias variables del mismo tipo en una única sentencia. Es útil ya que mantiene la concisión al reducir el número de líneas de código y agrupa variables relacionadas, haciendo más fácil entender que son del mismo tipo y potencialmente usadas para propósitos similares manteniendo la consistencia. También puedes escribirlos de la siguiente manera:
datetime TIME1_X1; datetime TIME2_Y2;
La variable «TIME1_X1» contiene el valor temporal de la primera coordenada a lo largo del eje x, mientras que «TIME2_Y2» contiene el valor temporal de la segunda coordenada también a lo largo del eje x. Del mismo modo, declaramos las coordenadas de precios del siguiente modo:
double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices
Tendremos que escanear constantemente el mercado en cada nueva barra para evaluar el establecimiento de un rango de consolidación causado por la baja volatilidad. Por lo tanto, se necesitarán dos variables para almacenar las banderas cuando el rango exista y cuando el precio esté dentro del rango.
bool isRangeExist = false; // Flag to check if the range exists bool isInRange = false; // Flag to check if we are currently within the range
Aquí, definimos dos variables booleanas llamadas «isRangeExist» e «isInRange» y las inicializamos. La variable «isRangeExist» servirá como bandera para indicar si un rango de consolidación ha sido identificado y trazado en el gráfico. Lo inicializamos a false porque no se ha establecido ningún rango al principio. De nuevo, la variable «isInRange», también inicializada a false, se utiliza para determinar si el precio de mercado actual se encuentra dentro del rango de consolidación identificado. Estos indicadores son cruciales para la lógica del Asesor Experto, ya que ayudan a gestionar el estado del proceso de detección de rangos y monitorización de rupturas, asegurando que las acciones se toman sólo cuando se cumplen las condiciones apropiadas.
En el ámbito global aún, tendremos que definir el rango número mínimo de velas a considerar así como el tamaño del rango en puntos. Como ya hicimos en la parte teórica, afirmamos que estos parámetros son cruciales para mantener la validez del intervalo de consolidación y garantizar que disponemos de intervalos de consolidación significativos.
int rangeBars = 10; // Number of bars to consider for the range int rangeSizePoints = 400; // Maximum range size in points
De nuevo, declaramos e inicializamos dos variables integer, «rangeBars» y «rangeSizePoints». La variable "rangeBars" se establece en 10, para especificar el número de barras (o velas) que analizaremos para determinar el rango de consolidación. Esto significa que nos remontamos a las últimas 10 barras para encontrar el máximo más alto y el mínimo más bajo para definir nuestro rango. La variable «rangeSizePoints» se establece en 400, que define el tamaño máximo permitido del rango de consolidación en puntos. Si el rango entre el precio más alto y el más bajo dentro de estas 10 barras supera los 400 puntos, no se considera un rango de consolidación válido. Estos parámetros son esenciales para establecer los criterios de la horquilla y garantizar que identificamos periodos de consolidación significativos en los datos de precios.
Por último, como vamos a abrir posiciones, definimos los puntos de Stop Loss y Take Profit.
double sl_points = 500.0; // Stop loss points double tp_points = 500.0; // Take profit points
Eso es todo lo que necesitamos en el ámbito global. Quizá se pregunte qué es un ámbito global. Un ámbito global se refiere simplemente a un área de un programa donde las variables, funciones y otros elementos son accesibles a través de todo el código, fuera de cualquier función o bloque. Cuando una variable o función se declara en el ámbito global, cualquier parte del programa puede acceder a ella y modificarla.
Todas nuestras actividades se ejecutarán en el manejador de eventos OnTick. Esto será pura acción del precio y dependeremos en gran medida de este controlador de eventos. Así pues, echemos un vistazo a los parámetros que la función toma a su lado ya que es el corazón de este código.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- }
Como ya se ha visto, se trata de una función sencilla pero crucial que no toma argumentos ni devuelve nada. Es sólo una función nula, lo que significa que no tiene que devolver nada. Esta función se utiliza en los Asesores Expertos y se ejecuta cuando hay un nuevo tick, es decir, un cambio en las cotizaciones de precios de la materia prima en particular.
Ahora que hemos visto que la función OnTick se genera en cada cambio en las cotizaciones, necesitamos definir alguna lógica de control que nos permita que el código se ejecute una vez por barra y no en cada tick, al menos para evitar ejecuciones innecesarias de código, ahorrando así memoria del dispositivo. Eso será necesario cuando se busquen configuraciones de rango de consolidación. No necesitamos buscar las configuraciones en cada tick, aunque siempre obtendremos los mismos resultados, siempre que sigamos en la misma vela. Esta es la lógica:
int currBars = iBars(_Symbol,_Period); // Get the current number of bars static int prevBars = currBars; // Static variable to store the previous number of bars static bool isNewBar = false; // Static flag to check if a new bar has appeared if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars
En primer lugar, declaramos una variable integer «currBars» que almacena el número calculado de barras actuales en el gráfico para el símbolo de negociación y el período especificados o, mejor dicho, el marco temporal como lo habrá oído. Esto se consigue mediante el uso de la función iBars, que sólo toma dos argumentos, es decir, símbolo y punto. En segundo lugar, declaramos una variable estática entera «prevBars» y la inicializamos con el número actual de barras. La palabra clave static garantiza que la variable «prevBars» conserve su valor entre llamadas a la función, recordando efectivamente el número de barras del tick anterior. En tercer lugar, declaramos una variable static booleana «isNewBar» y la inicializamos también a false. Esta variable nos ayudará a saber si ha aparecido una nueva barra. A continuación, utilizamos una sentencia condicional para comprobar si el número actual de barras es igual al número anterior de barras. Si son iguales, significa que no se ha formado ninguna barra nueva, por lo que fijamos el indicador de generación de barra nueva en falso. En caso contrario, si las barras anteriores no son iguales a las actuales, significa que el número de barras ha aumentado, lo que indica que ha aparecido una nueva barra. Por lo tanto, fijamos la bandera para la generación de nuevas barras en true y actualizamos el valor de las barras anteriores a las barras actuales.
Ahora, para cada barra que se genere y no tengamos un rango de consolidación, tenemos que escanear las barras predefinidas en busca de un posible periodo de baja volatilidad.
if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared ... }
Comprobamos si «isRangeExist» es falso, lo que significa que aún no se ha establecido ningún rango, y «isNewBar» es verdadero, lo que significa que ha aparecido una nueva barra. Esto garantiza que sólo procedamos si aún no se ha identificado un rango de consolidación y se ha formado una nueva barra.
Para determinar las coordenadas del primer punto de nuestro objeto rectángulo que se trazará en el gráfico, necesitamos los niveles de resistencia extrema, es decir, el momento de la última barra de nuestro rango de exploración de barras predefinido y el precio de la barra más alta dentro del rango.
TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range
En primer lugar, establecemos la hora de inicio del rango mediante la función iTime, que devuelve la hora de apertura de una barra específica para el símbolo y el periodo dados. La función toma tres parámetros de entrada o argumentos, donde _Symbol es el símbolo de negociación (por ejemplo, «AUDUSD»), _Period es el marco temporal (por ejemplo, PERIOD_M1 para barras de 1 minuto), y «rangeBars» es el índice de la barra que se encuentra el número especificado de períodos atrás. El resultado se almacena en «TIME1_X1», marcando la hora de inicio de nuestro intervalo de consolidación.
A continuación, buscamos la barra con el precio máximo más alto dentro del rango especificado mediante la función iHighest , que devuelve el índice de la barra que tiene el precio máximo más alto dentro de un número especificado de barras. La función toma cinco argumentos. No hace falta que volvamos a explicar qué hacen los dos primeros parámetros, puesto que ya lo hemos hecho. El tercer parámetro, que es «MODE_HIGH», se utiliza para indicar que estamos buscando el precio máximo más alto. El cuarto, "rangeBars" especifica el número de barras a considerar en el análisis de escaneo, y por último, 1 significa que empezamos a mirar desde la barra anterior a la barra actual que se está formando. Técnicamente, la barra actualmente en formación es el índice 0, y la inmediatamente anterior es el índice 1. El índice resultante se almacena en la variable entera «highestHigh_BarIndex».
Por último, recuperamos el precio máximo más alto de esa barra utilizando la función iHigh, que devuelve el precio máximo de una barra específica. La función toma tres parámetros de entrada, de los cuales los dos primeros son especialmente sencillos. El tercer argumento, «highestHigh_BarIndex» es el índice de la barra determinada en el paso anterior. El precio más alto se almacena en «PRICE1_Y1». Estas variables nos permiten definir el punto inicial y el punto máximo del rango de consolidación, que son fundamentales para trazar el rango y detectar posteriormente rupturas.
Para obtener las segundas coordenadas, se utiliza un enfoque similar al empleado para determinar las ordenadas del primer punto.
TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range
La diferencia en el código es que primero, nuestro tiempo está vinculado a la barra actual, que está en el índice 0. En segundo lugar, para obtener el índice de la barra más baja dentro del rango de barras predefinido, utilizamos la función iLowest y usamos «MODE_LOW» para indicar que estamos buscando el precio mínimo más bajo. Por último, se utiliza la función iLow para obtener el precio de la barra más baja. En pocas palabras, aquí hay una visualización de las coordenadas requeridas si tomáramos un gráfico arbitrario.
Ahora que tenemos los puntos del rango de consolidación, debemos verificar su validez, para asegurarnos de que se cumplan las condiciones para un rango válido, como se indicó anteriormente en la parte introductoria, antes de considerarlos y representarlos en el gráfico.
isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points
Calculamos la diferencia entre el precio máximo más alto (PRICE1_Y1) y el precio mínimo más bajo (PRICE2_Y2) dentro del intervalo y, a continuación, convertimos la diferencia en puntos dividiéndola por el tamaño de un punto, _Point. Por ejemplo, podríamos tener 0,66777 como valor de precio más alto y 0,66773 como valor de precio más bajo. Su diferencia matemática es de 0,66777 - 0,66773 = 0,00004. El valor en puntos del símbolo supuesto sería 0,00001. Ahora dividiendo el resultado por el valor en puntos sería 0,00004/0,00001 = 4 puntos. A continuación, este valor se compara con «rangeSizePoints», que es el tamaño de rango máximo permitido definido en puntos.
Por último, comprobamos si se ha identificado un rango válido y, en caso afirmativo, lo trazamos en el gráfico e informamos de que se ha creado correctamente.
if (isInRange){ // If the range size is valid plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range isRangeExist = true; // Set the range exist flag to true Print("RANGE PLOTTED"); // Print a message indicating the range is plotted }
Aquí comprobamos si el rango de consolidación identificado es válido evaluando la variable «isInRange». Si la bandera de la variable es verdadera, indicando que el tamaño del rango está dentro de los límites aceptables, procedemos a trazar el rango de consolidación en el gráfico. Para realizar el trazado, llamamos a la función «plotConsolidationRange» con los parámetros de entrada «rangeNAME», «TIME1_X1», «PRICE1_Y1», «TIME2_Y2» y «PRICE2_Y2», que crea una representación visual del rango. Después de trazar con éxito el rango, establecemos la bandera «isRangeExist» a true para indicar que se ha identificado y trazado un rango válido. Además, imprimimos el mensaje «RANGE PLOTTED» en el terminal a efectos de registro, confirmando que el rango de consolidación se ha visualizado correctamente.
El fragmento de código de la función responsable de trazar o actualizar el intervalo de consolidación es el siguiente:
//+------------------------------------------------------------------+ //| Function to plot the consolidation range | //| rangeName - name of the range object | //| time1_x1 - start time of the range | //| price1_y1 - high price of the range | //| time2_x2 - end time of the range | //| price2_y2 - low price of the range | //+------------------------------------------------------------------+ void plotConsolidationRange(string rangeName,datetime time1_x1,double price1_y1, datetime time2_x2,double price2_y2){ if (ObjectFind(0,rangeName) < 0){ // If the range object does not exist ObjectCreate(0,rangeName,OBJ_RECTANGLE,0,time1_x1,price1_y1,time2_x2,price2_y2); // Create the range object ObjectSetInteger(0,rangeName,OBJPROP_COLOR,clrBlue); // Set the color of the range ObjectSetInteger(0,rangeName,OBJPROP_FILL,true); // Enable fill for the range ObjectSetInteger(0,rangeName,OBJPROP_WIDTH,5); // Set the width of the range } else { // If the range object exists ObjectSetInteger(0,rangeName,OBJPROP_TIME,0,time1_x1); // Update the start time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,0,price1_y1); // Update the high price of the range ObjectSetInteger(0,rangeName,OBJPROP_TIME,1,time2_x2); // Update the end time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,1,price2_y2); // Update the low price of the range } ChartRedraw(0); // Redraw the chart to reflect changes }
En primer lugar, definimos una función void llamada «plotConsolidationRange» y le pasamos 5 parámetros o argumentos, que son el nombre del rango, las 2 coordenadas del primer punto y las 2 coordenadas del segundo punto. A continuación, utilizamos una sentencia condicional para comprobar si el objeto existe mediante el uso de la función ObjectFind, que devuelve un entero negativo en caso de que no se encuentre el objeto. Si es así, procedemos a crear el objeto identificado como OBJ_RECTANGLE, a la hora actual y a los precios especificados, para la primera y segunda coordenadas. A continuación, establecemos su color, área de relleno y anchura. Si se encuentra el objeto, simplemente actualizamos su hora y precios a los valores especificados y volvemos a dibujar el gráfico para que se apliquen los cambios actuales. El valor modificador 0 se utiliza para apuntar a la primera coordenada y el 1 a la segunda coordenada.
Esto es todo lo que necesitamos para trazar el rango de consolidación identificado en el gráfico. El código fuente completo responsable de ello es el siguiente:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- int currBars = iBars(_Symbol,_Period); // Get the current number of bars static int prevBars = currBars; // Static variable to store the previous number of bars static bool isNewBar = false; // Static flag to check if a new bar has appeared if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points if (isInRange){ // If the range size is valid plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range isRangeExist = true; // Set the range exist flag to true Print("RANGE PLOTTED"); // Print a message indicating the range is plotted } } }
Tras la compilación, estos son los resultados que obtenemos:
Puede ver que trazamos el rango dentro de los puntos predefinidos e informamos de la instancia de trazado en el diario. Si no necesita que se rellene el rango, todo lo que tiene que hacer es establecer el indicador de la propiedad de relleno en false. Eso dibujará la propiedad de línea del rectángulo, y la anchura se activará y se aplicará según se haya definido. A continuación se expone la lógica:
ObjectSetInteger(0,rangeName,OBJPROP_FILL,false); // Disable fill for the range
El resultado sería la siguiente gama:
Para el artículo, utilizaremos un rango lleno. Ahora que estamos seguros de que podemos establecer un rango, tenemos que continuar y desarrollar una lógica que supervise la ruptura del rango y las posiciones abiertas respectivamente, o las extensiones del rango y actualice las coordenadas del rango a los nuevos valores.
A continuación, simplemente identificamos las instancias de la ruptura como se describe en la parte teórica, y si existe una instancia, abrimos posiciones de mercado respectivamente. Esto requiere que se haga en cada tick, así que lo hacemos sin la restricción de las nuevas barras. Primero declaramos los precios Ask y Bid que utilizaremos para abrir las posiciones una vez que se cumplan las condiciones respectivas. Tenga en cuenta que esto también debe hacerse en cada tick para que obtengamos las cotizaciones más recientes.
double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); // Get and normalize the current Ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); // Get and normalize the current Bid price
Aquí, declaramos las variables de tipo de datos double para almacenar los precios recientes y normalizarlos a los dígitos de la moneda símbolo redondeando el número de coma flotante para mantener la precisión.
Dado que buscamos las rupturas en cada tick, necesitaremos una lógica para asegurarnos de que sólo estresamos el programa si existe un rango y que estamos en el rango. La mayor parte del tiempo, el mercado estará en volatilidad media a alta y por lo tanto la frecuencia de aparición del rango de consolidación será limitada, y si el rango ha sido identificado y está dentro de las proximidades del gráfico, el precio eventualmente romperá fuera del rango. Cuando cualquiera de estas condiciones se cumpla, no habrá necesidad de comprobar si se producen rupturas o nuevas actualizaciones del rango. Simplemente nos relajamos y esperamos a que se identifique otra gama.
if (isRangeExist && isInRange){ // If the range exists and we are in range ... }
Aquí, comprobamos si el rango de consolidación existe «isRangeExist» y si el precio actual está dentro de este rango «isInRange». Si se cumplen ambas condiciones, procedemos a calcular los posibles precios de ruptura.
double R_HighBreak_Prc = (PRICE2_Y2+rangeSizePoints*_Point); // Calculate the high breakout price double R_LowBreak_Prc = (PRICE1_Y1-rangeSizePoints*_Point); // Calculate the low breakout price
Para encontrar el precio máximo de ruptura, añadimos el tamaño máximo permitido del rango en puntos al precio mínimo más bajo. Este cálculo se realiza multiplicando los puntos de tamaño de rango por el tamaño de punto y sumando el resultado a la variable «PRICE2_Y2», almacenando el valor final en una variable de tipo de datos doble denominada «R_HighBreak_Prc». Por ejemplo, aún suponiendo que nuestro precio mínimo más bajo es 0,66773, tenemos puntos de tamaño de rango como 400 y valor de punto como 0,00001. Multiplicando 400 por 0,00001 obtenemos (400 * 0,00001) 0,00400. Sumando este valor al precio tenemos (0,66773 + 0,00400) 0,67173. Este resultado final calculado es el valor que se almacena en el precio de ruptura alto y utilizaremos el valor para compararlo con el precio de mercado para definir una ruptura si el precio de venta supera este precio. Del mismo modo, para determinar el precio mínimo de ruptura, restamos el tamaño máximo permitido del rango en puntos del precio máximo más alto. Esto se hace multiplicando los puntos de tamaño de rango por el tamaño de punto y restando el resultado del precio alto más alto, almacenando el valor final en la variable «R_LowBreak_Prc».
A continuación, procedemos a comprobar si se ha producido una ruptura y, en caso afirmativo, abrimos las respectivas posiciones. En primer lugar, vamos a tratar la situación en la que el precio del mercado rompe por encima del precio máximo de ruptura definido, señalando una oportunidad de compra.
if (Ask > R_HighBreak_Prc){ // If the Ask price breaks the high breakout price Print("BUY NOW, ASK = ",Ask,", L = ",PRICE2_Y2,", H BREAK = ",R_HighBreak_Prc); // Print a message to buy isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Buy(0.01,_Symbol,Ask,Bid-sl_points*_Point,Bid+tp_points*_Point); return; // Exit the function }
Primero, verificamos si el precio de venta actual es mayor que el precio de ruptura máximo. Si esta condición es verdadera, indica que el precio del mercado ha superado el límite superior del rango de consolidación. En respuesta, imprimimos un mensaje en la terminal, registrando el evento y brindando contexto para la señal de compra al incluir el precio de venta actual, el precio mínimo más bajo del rango y el precio máximo de ruptura. Luego restablecemos los indicadores "isInRange" y "isRangeExist" a falso, lo que indica que el rango de consolidación actual ya no es válido y evita futuras decisiones basadas en este rango. A continuación, comprobamos si hay alguna posición existente utilizando la función PositionsTotal y salimos de la función antes de tiempo si la hay, para evitar abrir varias posiciones simultáneamente. Si no hay posiciones existentes, procedemos a colocar una orden de compra utilizando el método Buy del objeto CTrade «obj_Trade», especificando el volumen de la operación, el símbolo, el precio de apertura como precio Ask, el precio Stop Loss y el precio Take Profit. Por último, salimos de la función para completar el proceso de inicio de la operación, asegurando que no se ejecute más código dentro de este tick.
Para manejar la situación en la que el precio de mercado rompe por debajo del precio de ruptura bajo definido, señalando una oportunidad de venta, se adopta una lógica de control similar como se muestra en el siguiente fragmento de código.
else if (Bid < R_LowBreak_Prc){ // If the Bid price breaks the low breakout price Print("SELL NOW"); // Print a message to sell isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Sell(0.01,_Symbol,Bid,Ask+sl_points*_Point,Ask-tp_points*_Point); return; // Exit the function }
Tras la compilación, esto es lo que obtenemos.
De la imagen se desprende que una vez que rompemos el rango, en este caso, el precio alto, abrimos una posición de compra, con un Stop Loss y Take Profit respectivamente. Las condiciones de entrada y los niveles de negociación son completamente dinámicos y puedes utilizar el que consideres oportuno o el que se ajuste a tu estilo de negociación. Por ejemplo, podría tener niveles dentro del rango de los extremos o de la relación riesgo-recompensa. Para confirmarlo, puede ver que el precio de venta es 0,68313, y el precio mínimo es 0,67911, lo que hace que el precio máximo de ruptura sea (0,67911 + 0,00400) 0,68311. Matemáticamente, el precio de venta actual es 0,68313, que está por encima del precio máximo calculado de 0,68311, que cumple nuestras condiciones de ruptura de alto rango, lo que lleva a iniciar una posición de compra al precio de venta actual.
Actualmente, el alcance es estático y no se mueve. Es decir, el rectángulo es fijo. Puedes ver que aunque establezcamos el rango correctamente, el objeto rango no se actualiza. Por tanto, debemos actualizar el rango si el precio supera el precio del rango de objetos definido. Para dar vida al rectángulo, consideremos una lógica que actualice siempre la extensión del rango por las barras que se han generado. En primer lugar, consideremos el escenario en el que el precio Ask actual supera el precio máximo registrado anteriormente dentro del rango de consolidación. Cuando se cumple esta condición, indica que el límite superior del intervalo de consolidación debe actualizarse para reflejar el nuevo precio más alto. Esto se consigue mediante el siguiente fragmento de código.
if (Ask > PRICE1_Y1){ // If the Ask price is higher than the current high price PRICE1_Y1 = Ask; // Update the high price to the Ask price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE1_Y1 TO ASK, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range }
Si el precio de venta es superior al precio máximo registrado anteriormente, fijamos el «PRICE1_Y1» en el precio de venta actual. Simultáneamente, actualizamos la hora final del rango «TIME2_Y2» a la hora actual, obtenida mediante la función iTime y pasando el índice de la barra objetivo como la barra actual, 0. Para realizar un seguimiento de estos ajustes y garantizar la claridad, imprimimos un mensaje en el terminal indicando que el rango se ha actualizado y requiere un replanteamiento. A continuación, llamamos a la función «plotConsolidationRange» con los parámetros actualizados, incluido el nuevo precio máximo y la hora actual, para reflejar visualmente los cambios en el gráfico.
Para manejar el escenario en el que el precio de oferta actual cae por debajo del precio mínimo registrado previamente dentro del rango de consolidación, una indicación de que el límite inferior del rango de consolidación necesita ser actualizado para reflejar el nuevo precio mínimo, se adopta un enfoque similar.
else if (Bid < PRICE2_Y2){ // If the Bid price is lower than the current low price PRICE2_Y2 = Bid; // Update the low price to the Bid price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE2_Y2 TO BID, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range }
Para rastrear estos cambios, vamos a tener una instancia en la que no tenemos las actualizaciones y en la que tenemos las actualizaciones en un formato de intercambio de gráficos (GIF) para hacer fácilmente una comparación.
Antes de la actualización:
Después de la actualización:
Por último, puede darse el caso de que ni el precio de demanda supere el precio máximo ni el precio de oferta caiga por debajo del precio mínimo. Si es así, todavía tenemos que ampliar el rango de consolidación para incluir la última barra completada. A continuación se muestra un fragmento de código para solucionarlo.
else{ if (isNewBar){ // If a new bar has appeared TIME2_Y2 = iTime(_Symbol,_Period,1); // Update the end time to the previous bar time Print("EXTEND THE RANGE TO PREV BAR TIME"); // Print a message indicating the range is extended plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } }
Comprobamos si ha aparecido una nueva barra mediante el indicador «isNewBar». Si efectivamente ha aparecido una nueva barra, actualizamos el tiempo final del rango de consolidación «TIME2_Y2» al tiempo de la barra anterior, que obtenemos mediante la función iTime, pasando como índice de barra objetivo 1, que es la barra que precede a la barra actual. Para garantizar la claridad y no perder de vista este ajuste, imprimimos un mensaje en el terminal indicando que el tiempo final del intervalo se ha ampliado al tiempo de la barra anterior. A continuación, llamamos a la función «plotConsolidationRange» con los parámetros actualizados, incluida la nueva hora final, para reflejar visualmente los cambios en el gráfico.
He aquí una ilustración del primer intento de actualización.
A continuación se muestra el código fuente completo responsable de la creación del Asesor Experto (EA) basado en la estrategia de ruptura del rango de consolidación en MQL5:
//+------------------------------------------------------------------+ //| CONSOLIDATION RANGE BREAKOUT.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh> // Include the trade library CTrade obj_Trade; // Create an instance of the CTrade class #define rangeNAME "CONSOLIDATION RANGE" // Define the name of the consolidation range datetime TIME1_X1, TIME2_Y2; // Declare datetime variables to hold range start and end times double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices bool isRangeExist = false; // Flag to check if the range exists bool isInRange = false; // Flag to check if we are currently within the range int rangeBars = 10; // Number of bars to consider for the range int rangeSizePoints = 400; // Maximum range size in points double sl_points = 500.0; // Stop loss points double tp_points = 500.0; // Take profit points //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- // Initialization code here (we don't initialize anything) //--- return(INIT_SUCCEEDED); // Return initialization success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- // Deinitialization code here (we don't deinitialize anything) } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- int currBars = iBars(_Symbol,_Period); // Get the current number of bars static int prevBars = currBars; // Static variable to store the previous number of bars static bool isNewBar = false; // Static flag to check if a new bar has appeared if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points if (isInRange){ // If the range size is valid plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range isRangeExist = true; // Set the range exist flag to true Print("RANGE PLOTTED"); // Print a message indicating the range is plotted } } double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); // Get and normalize the current Ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); // Get and normalize the current Bid price if (isRangeExist && isInRange){ // If the range exists and we are in range double R_HighBreak_Prc = (PRICE2_Y2+rangeSizePoints*_Point); // Calculate the high breakout price double R_LowBreak_Prc = (PRICE1_Y1-rangeSizePoints*_Point); // Calculate the low breakout price if (Ask > R_HighBreak_Prc){ // If the Ask price breaks the high breakout price Print("BUY NOW, ASK = ",Ask,", L = ",PRICE2_Y2,", H BREAK = ",R_HighBreak_Prc); // Print a message to buy isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Buy(0.01,_Symbol,Ask,Bid-sl_points*_Point,Bid+tp_points*_Point); return; // Exit the function } else if (Bid < R_LowBreak_Prc){ // If the Bid price breaks the low breakout price Print("SELL NOW"); // Print a message to sell isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Sell(0.01,_Symbol,Bid,Ask+sl_points*_Point,Ask-tp_points*_Point); return; // Exit the function } if (Ask > PRICE1_Y1){ // If the Ask price is higher than the current high price PRICE1_Y1 = Ask; // Update the high price to the Ask price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE1_Y1 TO ASK, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } else if (Bid < PRICE2_Y2){ // If the Bid price is lower than the current low price PRICE2_Y2 = Bid; // Update the low price to the Bid price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE2_Y2 TO BID, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } else{ if (isNewBar){ // If a new bar has appeared TIME2_Y2 = iTime(_Symbol,_Period,1); // Update the end time to the previous bar time Print("EXTEND THE RANGE TO PREV BAR TIME"); // Print a message indicating the range is extended plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Function to plot the consolidation range | //| rangeName - name of the range object | //| time1_x1 - start time of the range | //| price1_y1 - high price of the range | //| time2_x2 - end time of the range | //| price2_y2 - low price of the range | //+------------------------------------------------------------------+ void plotConsolidationRange(string rangeName,datetime time1_x1,double price1_y1, datetime time2_x2,double price2_y2){ if (ObjectFind(0,rangeName) < 0){ // If the range object does not exist ObjectCreate(0,rangeName,OBJ_RECTANGLE,0,time1_x1,price1_y1,time2_x2,price2_y2); // Create the range object ObjectSetInteger(0,rangeName,OBJPROP_COLOR,clrBlue); // Set the color of the range ObjectSetInteger(0,rangeName,OBJPROP_FILL,true); // Enable fill for the range ObjectSetInteger(0,rangeName,OBJPROP_WIDTH,5); // Set the width of the range } else { // If the range object exists ObjectSetInteger(0,rangeName,OBJPROP_TIME,0,time1_x1); // Update the start time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,0,price1_y1); // Update the high price of the range ObjectSetInteger(0,rangeName,OBJPROP_TIME,1,time2_x2); // Update the end time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,1,price2_y2); // Update the low price of the range } ChartRedraw(0); // Redraw the chart to reflect changes }
Resultados de las pruebas retrospectivas
Tras probarlo en el probador de estrategias, estos son los resultados.
- Gráfico Balance/Equidad:
- Resultados de las pruebas retrospectivas:
- Entradas comerciales por periodos:
Conclusión
En conclusión, podemos decir con confianza que la automatización de la estrategia de consolidación y ruptura no es tan compleja como se percibe una vez que se le da el pensamiento necesario. Técnicamente, se puede ver que su creación sólo requirió una comprensión clara de la estrategia y los requisitos reales, o más bien los objetivos que deben cumplirse para crear una configuración de estrategia válida.
En general, el artículo enfatiza la parte teórica que se debe tener en cuenta y comprender claramente para crear una estrategia de trading de forex de ruptura del rango de consolidación. Esto incluye su definición y descripción general, además de su esquema. Además, el aspecto de codificación de la estrategia resalta los pasos que se toman para analizar las velas, identificar períodos de baja volatilidad, identificar los niveles de soporte y resistencia para los períodos, rastrear sus rupturas, visualizar sus resultados y abrir posiciones comerciales basadas en las señales generadas. A largo plazo, esto permite la automatización de la estrategia de ruptura del rango de consolidación, lo que facilita una ejecución más rápida y la escalabilidad de la estrategia.
Esperamos que el artículo le haya resultado útil, divertido y fácil de entender, de manera que pueda utilizar el conocimiento presentado en el desarrollo de sus futuros asesores expertos. Técnicamente, esto facilita su forma de analizar el mercado basándose en el enfoque de acción del precio y, en particular, en la estrategia de ruptura del rango de consolidación. Disfrútalo.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15311





- 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