English Русский 中文 Deutsch 日本語
preview
Automatización de estrategias de trading en MQL5 (Parte 29): Creación de un sistema de patrón armónico de Gartley basado en la acción del precio

Automatización de estrategias de trading en MQL5 (Parte 29): Creación de un sistema de patrón armónico de Gartley basado en la acción del precio

MetaTrader 5 — Trading |
20 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En nuestro artículo anterior (Parte 28), desarrollamos un sistema de patrón Bat en MetaQuotes Language 5 (MQL5) que detectaba patrones armónicos Bat alcistas y bajistas usando ratios de Fibonacci precisos. En la parte 29, crearemos un programa de patrón de Gartley que identifica patrones armónicos de Gartley alcistas y bajistas mediante puntos de pivote y retrocesos de Fibonacci específicos, ejecuta operaciones con niveles de entrada dinámicos y niveles de take profit multinivel, y se complementa con triángulos visuales, líneas de tendencia y etiquetas para mostrar con claridad la estructura del patrón. Hoy abordaremos los siguientes temas:

  1. Comprensión del framework del patrón armónico de Gartley
  2. Implementación en MQL5
  3. Backtesting
  4. Conclusión

Al final, usted tendrá una potente estrategia MQL5 para negociar con el patrón armónico de Gartley, lista para personalizarla. ¡Manos a la obra!


Comprensión del framework del patrón armónico de Gartley

El patrón de Gartley es una formación armónica de trading definida por cinco puntos de oscilación clave —X, A, B, C y D— y se presenta en dos variantes: un patrón alcista y un patrón bajista, cada uno de ellos diseñado para identificar zonas de reversión de alta probabilidad mediante ratios de Fibonacci específicos. En un patrón Gartley alcista, la estructura forma una secuencia de mínimo-máximo-mínimo-máximo-mínimo, en la que el punto X es un mínimo de oscilación, el punto A un máximo de oscilación, el punto B un mínimo de oscilación (que retrocede aproximadamente el 0,618 de XA), el punto C un máximo de oscilación (que se extiende entre 0,382 y 0,886 de AB) y el punto D un mínimo de oscilación (que retrocede 0,786 de XA y se sitúa por encima de X). Por el contrario, un Gartley bajista forma una secuencia de máximo-mínimo-máximo-mínimo-máximo, en la que el punto X es un máximo de oscilación, el punto A un mínimo de oscilación, el punto B un máximo de oscilación, el punto C un mínimo de oscilación y el punto D un máximo de oscilación (que retrocede 0,786 de XA y se sitúa por debajo de X). Más abajo se muestran los tipos de patrones visualizados.

Patrón armónico de Gartley alcista:

PATRÓN ARMÓNICO DE GARTLEY ALCISTA

Patrón armónico de Gartley bajista:

PATRÓN ARMÓNICO DE GARTLEY BAJISTA

Para identificar los patrones, a continuación se presenta nuestro enfoque estructurado:

  • Definición del tramo XA: El movimiento inicial desde el punto X hasta el punto A establece la dirección del patrón (al alza para el alcista, a la baja para el bajista) y sirve de referencia para los cálculos posteriores de Fibonacci.
  • Determinación del tramo AB: El punto B debería retroceder aproximadamente el 0,618 del tramo XA, lo que indica una corrección significativa, aunque controlada, del impulso inicial.
  • Análisis del tramo BC: Este tramo debería extenderse entre el 0,382 y el 0,886 del tramo AB, dando lugar a un movimiento contrario que precede a la zona de reversión final.
  • Configuración del tramo CD: El tramo final debe retroceder el 0,786 del tramo XA, lo que marca la zona de posible reversión en el punto D, donde se completa el patrón y se genera una señal de operación.

Al aplicar estos criterios geométricos y basados en Fibonacci, nuestro sistema de trading detectará de forma sistemática patrones de Gartley válidos en los datos de precios. Una vez confirmado un patrón, el sistema visualizará la formación en el gráfico mediante triángulos, líneas de tendencia y etiquetas para los puntos X, A, B, C y D, junto con los niveles de trading para la entrada y los niveles de take profit. Esta configuración permitirá la ejecución automática de operaciones en el punto D con un stop loss calculado y zonas de take profit de varios niveles, aprovechando la capacidad predictiva del patrón para las reversiones del mercado. ¡Pasemos ahora a la implementación!


Implementación en MQL5

Para crear el programa en MQL5, abra el MetaEditor, vaya al Navegador, busque la carpeta «Indicadores», haga clic en la pestaña «Nuevo» y siga las instrucciones para crear el archivo. Una vez creado, en el entorno de programación, tendremos que declarar algunas variables globales que utilizaremos a lo largo de todo el programa.

//+------------------------------------------------------------------+
//|                                       Gartley Pattern EA.mq5     |
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA trades based on Gartley Strategy"
#property strict

//--- Include the trading library for order functions  
#include <Trade\Trade.mqh>  //--- Include Trade library
CTrade obj_Trade;  //--- Instantiate a obj_Trade object

//--- Input parameters for user configuration  
input int    PivotLeft    = 5;      // Number of bars to the left for pivot check  
input int    PivotRight   = 5;      // Number of bars to the right for pivot check  
input double Tolerance    = 0.10;   // Allowed deviation (10% of XA move)  
input double LotSize      = 0.01;   // Lot size for new orders  
input bool   AllowTrading = true;   // Enable or disable trading

//---------------------------------------------------------------------------  
//--- Gartley pattern definition:  
//  
//--- Bullish Gartley:  
//---   Pivots (X-A-B-C-D): X swing low, A swing high, B swing low, C swing high, D swing low.  
//---   Normally XA > 0; Ideal B = A - 0.618*(A-X); Legs within specified ranges; D at 0.786 retracement.  
//  
//--- Bearish Gartley:  
//---   Pivots (X-A-B-C-D): X swing high, A swing low, B swing high, C swing low, D swing high.  
//---   Normally XA > 0; Ideal B = A + 0.618*(X-A); Legs within specified ranges; D at 0.786 retracement.  
//---------------------------------------------------------------------------

//--- Structure for a pivot point  
struct Pivot {  
   datetime time;   //--- Bar time of the pivot  
   double   price;  //--- Pivot price (High for swing high, low for swing low)  
   bool     isHigh; //--- True if swing high; false if swing low  
};  

//--- Global dynamic array for storing pivots in chronological order  
Pivot pivots[];     //--- Declare a dynamic array to hold identified pivot points

//--- Global variables to lock in a pattern (avoid trading on repaint)  
int      g_patternFormationBar = -1;  //--- Bar index where the pattern was formed (-1 means none)  
datetime g_lockedPatternX      = 0;   //--- The key X pivot time for the locked pattern

Para sentar las bases del patrón de Gartley, primero incluimos la biblioteca «<Trade\Trade.mqh>» e instanciamos «obj_Trade» como objeto CTrade para gestionar operaciones de trading, como la ejecución de órdenes de compra y venta. A continuación, definimos los parámetros de entrada para la personalización del usuario: «PivotLeft» y «PivotRight» en 5 barras cada uno para especificar el intervalo de retrospectiva para la detección de puntos de pivote, «Tolerance» en 0,10 para permitir una desviación del 10 % en las relaciones de Fibonacci, «LotSize» en 0,01 para el volumen de la operación, y «AllowTrading» en «true» para habilitar el trading automatizado.

Después definimos la estructura «Pivot» con «time» (datetime), «price» (double) e «isHigh» (bool) para almacenar los puntos de oscilación; declaramos «pivots» como un array dinámico para guardar estos puntos e inicializamos las variables globales «g_patternFormationBar» con el valor -1 para realizar un seguimiento de la barra en la que se forma un patrón y «g_lockedPatternX» con el valor 0 para fijar el tiempo del pivote X para la confirmación del patrón, lo que proporciona el framework básico para detectar patrones de Gartley y operar con ellos. Para la visualización, podemos definir funciones para dibujar líneas, etiquetas y triángulos.

//+------------------------------------------------------------------+  
//| Helper: Draw a filled triangle                                   |  
//+------------------------------------------------------------------+  
void DrawTriangle(string name, datetime t1, double p1, datetime t2, double p2, datetime t3, double p3, color cl, int width, bool fill, bool back) {  
   //--- Attempt to create a triangle object with three coordinate points  
   if(ObjectCreate(0, name, OBJ_TRIANGLE, 0, t1, p1, t2, p2, t3, p3)) {  
      //--- Set the triangle's color  
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);  
      //--- Set the triangle's line style to solid  
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);  
      //--- Set the line width of the triangle  
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);  
      //--- Determine if the triangle should be filled  
      ObjectSetInteger(0, name, OBJPROP_FILL, fill);  
      //--- Set whether the object is drawn in the background  
      ObjectSetInteger(0, name, OBJPROP_BACK, back);  
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw a trend line                                        |  
//+------------------------------------------------------------------+  
void DrawTrendLine(string name, datetime t1, double p1, datetime t2, double p2, color cl, int width, int style) {  
   //--- Create a trend line object connecting two points  
   if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) {  
      //--- Set the trend line's color  
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);  
      //--- Set the trend line's style (solid, dotted, etc.)  
      ObjectSetInteger(0, name, OBJPROP_STYLE, style);  
      //--- Set the width of the trend line  
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);  
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw a dotted trend line                                 |  
//+------------------------------------------------------------------+  
void DrawDottedLine(string name, datetime t1, double p, datetime t2, color lineColor) {  
   //--- Create a horizontal trend line at a fixed price level with dotted style  
   if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p, t2, p)) {  
      //--- Set the dotted line's color  
      ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);  
      //--- Set the line style to dotted  
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);  
      //--- Set the line width to 1  
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);  
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw anchored text label (for pivots)                    |  
//| If isHigh is true, anchor at the bottom (label appears above);   |  
//| if false, anchor at the top (label appears below).               |  
//+------------------------------------------------------------------+  
void DrawTextEx(string name, string text, datetime t, double p, color cl, int fontsize, bool isHigh) {  
   //--- Create a text label object at the specified time and price  
   if(ObjectCreate(0, name, OBJ_TEXT, 0, t, p)) {  
      //--- Set the text of the label  
      ObjectSetString(0, name, OBJPROP_TEXT, text);  
      //--- Set the color of the text  
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);  
      //--- Set the font size for the text  
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontsize);  
      //--- Set the font type and style  
      ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold");  
      //--- Anchor the text depending on whether it's a swing high or low  
      if(isHigh)  
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_BOTTOM);  
      else  
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP);  
      //--- Center-align the text  
      ObjectSetInteger(0, name, OBJPROP_ALIGN, ALIGN_CENTER);  
   }  
}

Luego procedemos a implementar funciones de visualización en el sistema para crear representaciones claras en el gráfico del patrón y sus niveles de trading. En primer lugar, desarrollamos la función «DrawTriangle», que utiliza ObjectCreate para dibujar un triángulo relleno (OBJ_TRIANGLE) definido por tres puntos con tiempos («t1», «t2», «t3») y precios («p1», «p2», «p3»), estableciendo OBJPROP_COLOR en el color especificado, «OBJPROP_STYLE» en STYLE_SOLID, «OBJPROP_WIDTH» en el ancho indicado, «OBJPROP_FILL» para activar o desactivar el relleno y «OBJPROP_BACK» para establecer la posición en segundo plano o en primer plano mediante la función ObjectSetInteger.

A continuación, procedemos a crear la función «DrawTrendLine», que traza una línea de tendencia (OBJ_TREND) entre dos puntos. Entonces implementamos la función «DrawDottedLine», que crea una línea de puntos horizontal («OBJ_TREND») a un precio especificado, desde «t1» hasta «t2». Por último, desarrollamos la función «DrawTextEx», que crea una etiqueta de texto (OBJ_TEXT) en las coordenadas («t», «p»), establece «OBJPROP_TEXT» en el texto especificado, «OBJPROP_COLOR», «OBJPROP_FONTSIZE» y «OBJPROP_FONT» en «Arial Bold» mediante ObjectSetString y «ObjectSetInteger», la ancla por encima para los máximos de oscilación o por debajo para los mínimos en función de «isHigh» con OBJPROP_ANCHOR y la centra con «OBJPROP_ALIGN», lo que garantiza que el patrón de Gartley y sus niveles de trading se vean claramente en el gráfico. Ahora podemos pasar al controlador de eventos OnTick e intentar encontrar puntos de pivote que podamos usar más adelante para identificar patrones. Esta es la lógica que utilizamos para lograrlo.

//+------------------------------------------------------------------+  
//| Expert tick function                                             |  
//+------------------------------------------------------------------+  
void OnTick() {  
   //--- Declare a static variable to store the time of the last processed bar  
   static datetime lastBarTime = 0;  
   //--- Get the time of the current confirmed bar  
   datetime currentBarTime = iTime(_Symbol, _Period, 1);  
   //--- If the current bar time is the same as the last processed, exit  
   if(currentBarTime == lastBarTime)  
      return;  
   //--- Update the last processed bar time  
   lastBarTime = currentBarTime;  
   
   //--- Clear the pivot array for fresh analysis  
   ArrayResize(pivots, 0);  
   //--- Get the total number of bars available on the chart  
   int barsCount = Bars(_Symbol, _Period);  
   //--- Define the starting index for pivot detection (ensuring enough left bars)  
   int start = PivotLeft;  
   //--- Define the ending index for pivot detection (ensuring enough right bars)  
   int end = barsCount - PivotRight;  
   
   //--- Loop through bars from 'end-1' down to 'start' to find pivot points  
   for(int i = end - 1; i >= start; i--) {  
      //--- Assume current bar is both a potential swing high and swing low  
      bool isPivotHigh = true;  
      bool isPivotLow = true;  
      //--- Get the high and low of the current bar  
      double currentHigh = iHigh(_Symbol, _Period, i);  
      double currentLow = iLow(_Symbol, _Period, i);  
      //--- Loop through the window of bars around the current bar  
      for(int j = i - PivotLeft; j <= i + PivotRight; j++) {  
         //--- Skip if the index is out of bounds  
         if(j < 0 || j >= barsCount)  
            continue;  
         //--- Skip comparing the bar with itself  
         if(j == i)  
            continue;  
         //--- If any bar in the window has a higher high, it's not a swing high  
         if(iHigh(_Symbol, _Period, j) > currentHigh)  
            isPivotHigh = false;  
         //--- If any bar in the window has a lower low, it's not a swing low  
         if(iLow(_Symbol, _Period, j) < currentLow)  
            isPivotLow = false;  
      }  
      //--- If the current bar qualifies as either a swing high or swing low  
      if(isPivotHigh || isPivotLow) {  
         //--- Create a new pivot structure  
         Pivot p;  
         //--- Set the pivot's time  
         p.time = iTime(_Symbol, _Period, i);  
         //--- Set the pivot's price depending on whether it is a high or low  
         p.price = isPivotHigh ? currentHigh : currentLow;  
         //--- Set the pivot type (true for swing high, false for swing low)  
         p.isHigh = isPivotHigh;  
         //--- Get the current size of the pivots array  
         int size = ArraySize(pivots);  
         //--- Increase the size of the pivots array by one  
         ArrayResize(pivots, size + 1);  
         //--- Add the new pivot to the array  
         pivots[size] = p;  
      }  
   }
}

Aquí implementamos la lógica inicial de la función OnTick para detectar pivotes de oscilación, lo que constituye la base para identificar patrones armónicos de Gartley. En primer lugar, declaramos una variable estática «lastBarTime», inicializada en 0, para realizar un seguimiento de la última barra procesada y compararla con «currentBarTime», obtenida de iTime con desplazamiento 1 para el símbolo y el periodo actuales; si no ha cambiado, salimos para evitar un procesamiento redundante, y actualizamos «lastBarTime» cuando se detecta una nueva barra. A continuación, procedemos a borrar el array «pivots» con ArrayResize para realizar un nuevo análisis.

Luego obtenemos el número total de barras con Bars, establecemos el rango de detección de pivotes con «start» como «PivotLeft» y «end» como el número total de barras menos «PivotRight», y recorremos las barras desde «end - 1» hasta «start». Para cada barra, suponemos que es un máximo de oscilación («isPivotHigh» verdadero) y un mínimo de oscilación («isPivotLow» verdadero), obtenemos sus precios máximo y mínimo mediante iHigh e iLow, y validamos el pivote comprobando las barras circundantes dentro de «PivotLeft» y «PivotRight» con «iHigh» e «iLow», invalidando el pivote si alguna barra vecina tiene un máximo más alto o un mínimo más bajo. Por último, si la barra cumple los requisitos para ser considerada un pivote, creamos una estructura «Pivot», establecemos su «time» con «iTime», su «price» en el máximo o el mínimo en función de «isPivotHigh» y el indicador «isHigh», y a continuación la añadimos al array «pivots» utilizando ArrayResize y la almacenamos. Al imprimir la estructura de pivotes, obtenemos el siguiente array de datos.

ESTRUCTURA DE DATOS DE PIVOTES

Con los datos, podemos extraer los puntos de pivote y, si disponemos de suficientes pivotes, podemos analizar y detectar los patrones. Esta es la lógica que implementamos para lograrlo.

//--- Determine the total number of pivots found  
int pivotCount = ArraySize(pivots);  
//--- If fewer than five pivots are found, the pattern cannot be formed  
if(pivotCount < 5) {  
   //--- Reset pattern lock variables  
   g_patternFormationBar = -1;  
   g_lockedPatternX = 0;  
   //--- Exit the OnTick function  
   return;  
}  

//--- Extract the last five pivots as X, A, B, C, and D  
Pivot X = pivots[pivotCount - 5];  
Pivot A = pivots[pivotCount - 4];  
Pivot B = pivots[pivotCount - 3];  
Pivot C = pivots[pivotCount - 2];  
Pivot D = pivots[pivotCount - 1];  

//--- Initialize a flag to indicate if a valid Gartley pattern is found  
bool patternFound = false;  
//--- Initialize pattern type  
string patternType = "";  
//--- Check for the high-low-high-low-high (Bearish reversal) structure  
if(X.isHigh && (!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) {  
   //--- Calculate the difference between pivot X and A  
   double diff = X.price - A.price;  
   //--- Ensure the difference is positive  
   if(diff > 0) {  
      //--- Calculate the ideal position for pivot B based on Fibonacci ratio  
      double idealB = A.price + 0.618 * diff;  
      //--- Check if actual B is within tolerance of the ideal position  
      if(MathAbs(B.price - idealB) <= Tolerance * diff) {  
         //--- Calculate the AB leg length  
         double AB = B.price - A.price;  
         //--- Calculate the BC leg length  
         double BC = B.price - C.price;  
         //--- Verify that BC is within the acceptable Fibonacci range  
         if((BC >= 0.382 * AB) && (BC <= 0.886 * AB)) {  
            //--- Calculate the retracement  
            double retrace = D.price - A.price;  
            //--- Verify that the retracement is within tolerance of 0.786 and that D is below X  
            if(MathAbs(retrace - 0.786 * diff) <= Tolerance * diff && (D.price < X.price)) {  
               patternFound = true;  
               patternType = "Bearish";  
            }  
         }  
      }  
   }  
}  
//--- Check for the low-high-low-high-low (Bullish reversal) structure  
if((!X.isHigh) && A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) {  
   //--- Calculate the difference between pivot A and X  
   double diff = A.price - X.price;  
   //--- Ensure the difference is positive  
   if(diff > 0) {  
      //--- Calculate the ideal position for pivot B based on Fibonacci ratio  
      double idealB = A.price - 0.618 * diff;  
      //--- Check if actual B is within tolerance of the ideal position  
      if(MathAbs(B.price - idealB) <= Tolerance * diff) {  
         //--- Calculate the AB leg length  
         double AB = A.price - B.price;  
         //--- Calculate the BC leg length  
         double BC = C.price - B.price;  
         //--- Verify that BC is within the acceptable Fibonacci range  
         if((BC >= 0.382 * AB) && (BC <= 0.886 * AB)) {  
            //--- Calculate the retracement  
            double retrace = A.price - D.price;  
            //--- Verify that the retracement is within tolerance of 0.786 and that D is above X  
            if(MathAbs(retrace - 0.786 * diff) <= Tolerance * diff && (D.price > X.price)) {  
               patternFound = true;  
               patternType = "Bullish";  
            }  
         }  
      }  
   }  
}

En primer lugar, determinamos el número total de pivotes con «ArraySize(pivots)», almacenado en «pivotCount», y salimos si se encuentran menos de 5 pivotes, restableciendo «g_patternFormationBar» y «g_lockedPatternX» a -1 y 0, ya que el patrón de Gartley requiere los puntos X, A, B, C y D. Después extraemos los últimos cinco pivotes del array «pivots», asignándoles «X» (el más antiguo), «A», «B», «C» y «D» (el más reciente) para formar la estructura del patrón.

A continuación, comprobamos si existe un patrón Gartley bajista (X: máximo, A: mínimo, B: máximo, C: mínimo, D: máximo) calculando la diferencia del tramo XA («X.price - A.price»), asegurándonos de que sea positiva, calculando el punto B ideal como «A.price + 0.618 * diff», verificando que B se encuentre dentro de «Tolerance * diff» mediante MathAbs, comprobando el tramo BC (entre 0,382 y 0,886 de AB) y confirmando el retroceso AD (0,786 de XA con D por debajo de X), estableciendo «patternFound» en «true» y «patternType» en «Bearish» si es válido. Por último, comprobamos si existe un patrón Gartley alcista (X: mínimo; A: máximo; B: mínimo; C: máximo; D: mínimo), calculando XA como «A.price - X.price», asegurándonos de que sea positivo, verificando que B se encuentre en el retroceso de 0,618, que BC esté comprendido entre 0,382 y 0,886 de AB, y que AD esté en 0,786 de XA con D por encima de X, estableciendo «patternFound» en «true» y «patternType» en «Bullish» si es válido. Si se detecta el patrón, podemos pasar a visualizarlo en el gráfico.

//--- If a valid Gartley pattern is detected  
if(patternFound) {  
   //--- Print a message indicating the pattern type and detection time  
   Print(patternType, " Gartley pattern detected at ", TimeToString(D.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS));  
   
   //--- Create a unique prefix for all graphical objects related to this pattern  
   string signalPrefix = "GA_" + IntegerToString(X.time);  
   
   //--- Choose triangle color based on the pattern type  
   color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed;  
   
   //--- Draw the first triangle connecting pivots X, A, and B  
   DrawTriangle(signalPrefix+"_Triangle1", X.time, X.price, A.time, A.price, B.time, B.price,  
                triangleColor, 2, true, true);  
   //--- Draw the second triangle connecting pivots B, C, and D  
   DrawTriangle(signalPrefix+"_Triangle2", B.time, B.price, C.time, C.price, D.time, D.price,  
                triangleColor, 2, true, true);  
}

Si se detecta un patrón válido («patternFound» es «true»), registramos la detección con Print, mostrando el «patternType» («Bullish» o «Bearish») y la marca temporal del pivote D formateada con TimeToString, incluyendo la fecha, los minutos y los segundos. A continuación, procedemos a crear un identificador único, «signalPrefix», concatenando «GA_» con «X.time», convertido en una cadena mediante la función IntegerToString. Luego establecemos «triangleColor» en azul para los patrones alcistas o en rojo para los patrones bajistas. Por último, llamamos a «DrawTriangle» dos veces para visualizar el patrón: primero para dibujar el triángulo XAB que une los puntos de pivote X, A y B, y después para dibujar el triángulo BCD que une los puntos de pivote B, C y D, utilizando «signalPrefix» con los sufijos «_Triangle1» y «_Triangle2», los tiempos y precios de los pivotes correspondientes, «triangleColor», un grosor de 2, y activando el relleno y la visualización en segundo plano con indicadores «true». Obtenemos el siguiente resultado.

CONJUNTO DE TRIÁNGULOS

En la imagen, podemos ver que podemos trazar y visualizar correctamente el patrón detectado. Ahora debemos añadir las líneas de tendencia para que el patrón se vea por completo dentro de sus límites y añadirle una etiqueta que permita identificar más fácilmente los niveles.

//--- Draw boundary trend lines connecting the pivots for clarity  
DrawTrendLine(signalPrefix+"_TL_XA", X.time, X.price, A.time, A.price, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(signalPrefix+"_TL_AB", A.time, A.price, B.time, B.price, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(signalPrefix+"_TL_BC", B.time, B.price, C.time, C.price, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(signalPrefix+"_TL_CD", C.time, C.price, D.time, D.price, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(signalPrefix+"_TL_XB", X.time, X.price, B.time, B.price, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(signalPrefix+"_TL_BD", B.time, B.price, D.time, D.price, clrBlack, 2, STYLE_SOLID);  

//--- Retrieve the symbol's point size to calculate offsets for text positioning  
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);  
//--- Calculate an offset (15 points) for positioning text above or below pivots  
double offset = 15 * point;  

//--- Determine the Y coordinate for each pivot label based on its type  
double textY_X = (X.isHigh ? X.price + offset : X.price - offset);  
double textY_A = (A.isHigh ? A.price + offset : A.price - offset);  
double textY_B = (B.isHigh ? B.price + offset : B.price - offset);  
double textY_C = (C.isHigh ? C.price + offset : C.price - offset);  
double textY_D = (D.isHigh ? D.price + offset : D.price - offset);  

//--- Draw text labels for each pivot with appropriate anchoring  
DrawTextEx(signalPrefix+"_Text_X", "X", X.time, textY_X, clrBlack, 11, X.isHigh);  
DrawTextEx(signalPrefix+"_Text_A", "A", A.time, textY_A, clrBlack, 11, A.isHigh);  
DrawTextEx(signalPrefix+"_Text_B", "B", B.time, textY_B, clrBlack, 11, B.isHigh);  
DrawTextEx(signalPrefix+"_Text_C", "C", C.time, textY_C, clrBlack, 11, C.isHigh);  
DrawTextEx(signalPrefix+"_Text_D", "D", D.time, textY_D, clrBlack, 11, D.isHigh);  

//--- Calculate the central label's time as the midpoint between pivots X and B  
datetime centralTime = (X.time + B.time) / 2;  
//--- Set the central label's price at pivot D's price  
double centralPrice = D.price;  
//--- Create the central text label indicating the pattern type  
if(ObjectCreate(0, signalPrefix+"_Text_Center", OBJ_TEXT, 0, centralTime, centralPrice)) {  
   ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_TEXT,  
      (patternType=="Bullish") ? "Bullish Gartley" : "Bearish Gartley");  
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_COLOR, clrBlack);  
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_FONTSIZE, 11);  
   ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_FONT, "Arial Bold");  
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_ALIGN, ALIGN_CENTER);  
}

Mejoraremos aún más la visualización de los patrones detectados añadiendo objetos de gráfico detallados para representar la estructura de dichos patrones. En primer lugar, trazamos seis líneas de tendencia continuas utilizando «DrawTrendLine» con el «signalPrefix» único para conectar los puntos de pivote clave: XA, AB, BC, CD, XB y BD, utilizando los tiempos y precios de pivote (p. ej., «X.time», «X.price»), estableciendo OBJPROP_COLOR en negro, «OBJPROP_WIDTH» en 2 y «OBJPROP_STYLE» en «STYLE_SOLID» con ObjectSetInteger para delinear los tramos del patrón. A continuación, recuperamos el tamaño en puntos del símbolo mediante «SymbolInfoDouble(_Symbol, SYMBOL_POINT)» y calculamos un desplazamiento de 15 puntos para posicionar las etiquetas, determinando las coordenadas Y («textY_X», «textY_A», «textY_B», «textY_C», «textY_D») sumando o restando el desplazamiento en función de si cada pivote es un máximo de oscilación («isHigh» en «true») o un mínimo de oscilación, para colocar las etiquetas por encima de los máximos o por debajo de los mínimos.

Acto seguido, utilizamos «DrawTextEx» para crear etiquetas de texto para los pivotes X, A, B, C y D con «signalPrefix» y sufijos como «_Text_X», mostrando la letra correspondiente, situadas en el tiempo del pivote y con la coordenada Y ajustada, utilizando «clrBlack», tamaño de fuente 11 y el estado «isHigh» del pivote para el anclaje. Por último, calculamos la posición de la etiqueta central, con «centralTime» como el punto medio entre «X.time» y «B.time» y «centralPrice» en «D.price», creando un objeto de texto con ObjectCreate denominado «signalPrefix + '_Text_Center'», estableciendo OBJPROP_TEXT en «Bullish Gartley» o «Bearish Gartley» en función de «patternType», y configurando «OBJPROP_COLOR» en «clrBlack», «OBJPROP_FONTSIZE» en 11, OBJPROP_FONT en «Arial Bold» y «OBJPROP_ALIGN» en «ALIGN_CENTER» mediante las funciones ObjectSetString y «ObjectSetInteger». Esta lógica garantiza una representación visual completa de la estructura y el tipo del patrón en el gráfico. Cuando ejecutamos el programa, esto es lo que vemos.

PATRÓN CON ETIQUETAS Y BORDES

En la imagen podemos ver que hemos añadido los lados y las etiquetas al patrón, lo que lo hace más claro e ilustrativo. Lo siguiente que tenemos que hacer es determinar los niveles de trading del patrón.

//--- Define start and end times for drawing horizontal dotted lines for trade levels  
datetime lineStart = D.time;  
datetime lineEnd = D.time + PeriodSeconds(_Period)*2;  

//--- Declare variables for entry price and take profit levels  
double entryPriceLevel, TP1Level, TP2Level, TP3Level, tradeDiff;  
//--- Calculate trade levels based on whether the pattern is Bullish or Bearish  
if(patternType=="Bullish") { //--- Bullish → BUY signal  
   //--- Use the current ASK price as the entry  
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_ASK);  
   //--- Set TP3 at pivot C's price  
   TP3Level = C.price;  
   //--- Calculate the total distance to be covered by the trade  
   tradeDiff = TP3Level - entryPriceLevel;  
   //--- Set TP1 at one-third of the total move  
   TP1Level = entryPriceLevel + tradeDiff/3;  
   //--- Set TP2 at two-thirds of the total move  
   TP2Level = entryPriceLevel + 2*tradeDiff/3;  
} else { //--- Bearish → SELL signal  
   //--- Use the current BID price as the entry  
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_BID);  
   //--- Set TP3 at pivot C's price  
   TP3Level = C.price;  
   //--- Calculate the total distance to be covered by the trade  
   tradeDiff = entryPriceLevel - TP3Level;  
   //--- Set TP1 at one-third of the total move  
   TP1Level = entryPriceLevel - tradeDiff/3;  
   //--- Set TP2 at two-thirds of the total move  
   TP2Level = entryPriceLevel - 2*tradeDiff/3;  
}  

//--- Draw dotted horizontal lines to represent the entry and TP levels  
DrawDottedLine(signalPrefix+"_EntryLine", lineStart, entryPriceLevel, lineEnd, clrMagenta);  
DrawDottedLine(signalPrefix+"_TP1Line", lineStart, TP1Level, lineEnd, clrForestGreen);  
DrawDottedLine(signalPrefix+"_TP2Line", lineStart, TP2Level, lineEnd, clrGreen);  
DrawDottedLine(signalPrefix+"_TP3Line", lineStart, TP3Level, lineEnd, clrDarkGreen);  

//--- Define a label time coordinate positioned just to the right of the dotted lines  
datetime labelTime = lineEnd + PeriodSeconds(_Period)/2;  

//--- Construct the entry label text with the price  
string entryLabel = (patternType=="Bullish") ? "BUY (" : "SELL (";  
entryLabel += DoubleToString(entryPriceLevel, _Digits) + ")";  
//--- Draw the entry label on the chart  
DrawTextEx(signalPrefix+"_EntryLabel", entryLabel, labelTime, entryPriceLevel, clrMagenta, 11, true);  

//--- Construct and draw the TP1 label  
string tp1Label = "TP1 (" + DoubleToString(TP1Level, _Digits) + ")";  
DrawTextEx(signalPrefix+"_TP1Label", tp1Label, labelTime, TP1Level, clrForestGreen, 11, true);  

//--- Construct and draw the TP2 label  
string tp2Label = "TP2 (" + DoubleToString(TP2Level, _Digits) + ")";  
DrawTextEx(signalPrefix+"_TP2Label", tp2Label, labelTime, TP2Level, clrGreen, 11, true);  

//--- Construct and draw the TP3 label  
string tp3Label = "TP3 (" + DoubleToString(TP3Level, _Digits) + ")";  
DrawTextEx(signalPrefix+"_TP3Label", tp3Label, labelTime, TP3Level, clrDarkGreen, 11, true);

Para definir y visualizar los niveles de trading del patrón detectado, establecemos «lineStart» en la hora del pivote D («D.time») y «lineEnd» dos períodos más adelante utilizando «PeriodSeconds(_Period) * 2», y declaramos las variables «entryPriceLevel», «TP1Level», «TP2Level», «TP3Level» y «tradeDiff» para los cálculos de trading. A continuación, para un patrón alcista («patternType == 'Bullish'»), establecemos «entryPriceLevel» en el precio Ask actual mediante SymbolInfoDouble, «TP3Level» en el precio del pivote C, calculamos «tradeDiff» como «TP3Level - entryPriceLevel» y calculamos «TP1Level» y «TP2Level» como un tercio y dos tercios de «tradeDiff» sumados a «entryPriceLevel»; para un patrón bajista, utilizamos el precio Bid, establecemos «TP3Level» en el precio de C, calculamos «tradeDiff» como «entryPriceLevel - TP3Level» y calculamos «TP1Level» y «TP2Level» restando un tercio y dos tercios de la diferencia de trading.

Después trazamos cuatro líneas horizontales punteadas con la función «DrawDottedLine»: una línea de entrada en «entryPriceLevel» en color magenta, y líneas de take profit en «TP1Level» (verde bosque), «TP2Level» (verde) y «TP3Level» (verde oscuro), que se extienden desde «lineStart» hasta «lineEnd». Por último, establecemos «labelTime» en «lineEnd» más medio periodo, creamos textos de etiqueta con los precios formateados mediante DoubleToString (por ejemplo, «BUY (precio)» o «SELL (precio)» para la entrada, «TP1 (precio)», etc.) y utilizamos «DrawTextEx» para dibujar estas etiquetas en «labelTime» con los colores correspondientes, tamaño de fuente 11 y ancladas por encima de los niveles de precio, lo que garantiza una visualización clara de los niveles de entrada de trading y de take profit del patrón de Gartley. Tras la compilación, obtenemos el siguiente resultado.

Patrón bajista:

PATRÓN BAJISTA

Patrón alcista:

PATRÓN ALCISTA

En las imágenes se puede observar que hemos representado correctamente los niveles de trading. Lo que tenemos que hacer ahora es abrir las posiciones de trading propiamente dichas, y eso es todo.

//--- Retrieve the index of the current bar  
int currentBarIndex = Bars(_Symbol, _Period) - 1;  
//--- If no pattern has been previously locked, lock the current pattern formation  
if(g_patternFormationBar == -1) {  
   g_patternFormationBar = currentBarIndex;  
   g_lockedPatternX = X.time;  
   //--- Print a message that the pattern is detected and waiting for confirmation  
   Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar.");  
   return;  
}  
//--- If still on the same formation bar, the pattern is considered to be repainting  
if(currentBarIndex == g_patternFormationBar) {  
   Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No trade yet.");  
   return;  
}  
//--- If we are on a new bar compared to the locked formation  
if(currentBarIndex > g_patternFormationBar) {  
   //--- Check if the locked pattern still corresponds to the same X pivot  
   if(g_lockedPatternX == X.time) {  
      Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening trade on bar ", currentBarIndex, ".");  
      //--- Update the pattern formation bar to the current bar  
      g_patternFormationBar = currentBarIndex;  
      //--- Only proceed with trading if allowed and if there is no existing position  
      if(AllowTrading && !PositionSelect(_Symbol)) {  
         double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0;  
         point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);  
         bool tradeResult = false;  
         //--- For a Bullish pattern, execute a BUY trade  
         if(patternType=="Bullish") {  //--- BUY signal  
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK);  
            double diffTrade = TP2Level - entryPriceTrade;  
            stopLoss = entryPriceTrade - diffTrade * 3;  
            takeProfit = TP2Level;  
            tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Gartley Signal");  
            if(tradeResult)  
               Print("Buy order opened successfully.");  
            else  
               Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription());  
         }  
         //--- For a Bearish pattern, execute a SELL trade  
         else if(patternType=="Bearish") {  //--- SELL signal  
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_BID);  
            double diffTrade = entryPriceTrade - TP2Level;  
            stopLoss = entryPriceTrade + diffTrade * 3;  
            takeProfit = TP2Level;  
            tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Gartley Signal");  
            if(tradeResult)  
               Print("Sell order opened successfully.");  
            else  
               Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription());  
         }  
      }  
      else {  
         //--- If a position is already open, do not execute a new trade  
         Print("A position is already open for ", _Symbol, ". No new trade executed.");  
      }  
   }  
   else {  
      //--- If the pattern has changed, update the lock with the new formation bar and X pivot  
      g_patternFormationBar = currentBarIndex;  
      g_lockedPatternX = X.time;  
      Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation.");  
      return;  
   }  
}  
}  
else {  
   //--- If no valid Gartley pattern is detected, reset the pattern lock variables  
   g_patternFormationBar = -1;  
   g_lockedPatternX = 0;  
}

Aquí completamos la implementación del tick gestionando la ejecución de operaciones y la confirmación del patrón detectado. En primer lugar, obtenemos el índice de la barra actual con «Bars(_Symbol, _Period) - 1» y lo almacenamos en «currentBarIndex». A continuación, si no hay ningún patrón bloqueado («g_patternFormationBar == -1»), establecemos «g_patternFormationBar» en «currentBarIndex», bloqueamos la hora del pivote X en «g_lockedPatternX» con «X.time», registramos la detección indicando que se debe esperar a la confirmación y salimos. Entonces, si seguimos en la barra de formación («currentBarIndex == g_patternFormationBar»), registramos el repintado y salimos para evitar operaciones de trading prematuras.

Por último, si se ha formado una nueva barra («currentBarIndex > g_patternFormationBar») y el pivote X coincide con «g_lockedPatternX», confirmamos el patrón, lo registramos, actualizamos «g_patternFormationBar» y comprobamos si está permitido operar y si no existe ninguna posición abierta mediante la función PositionSelect; en el caso de un patrón alcista, establecemos «entryPriceTrade» en el precio Ask, calculamos «diffTrade» como «TP2Level - entryPriceTrade», fijamos «stopLoss» tres veces esta distancia por debajo del precio de entrada, establecemos «takeProfit» en «TP2Level» y ejecutamos una compra con «obj_Trade.Buy» utilizando «LotSize» y «Gartley Signal», registrando si la operación ha tenido éxito o ha fallado; para un patrón bajista, usamos el precio Bid, fijamos «stopLoss» tres veces esta distancia por encima del precio de entrada y ejecutamos una venta con «obj_Trade.Sell»; si no se permite operar o ya existe una posición, registramos que no se abre ninguna operación; si el patrón cambia, actualizamos el bloqueo y esperamos; si no se encuentra ningún patrón, restablecemos las variables globales. Tras la compilación, obtenemos el siguiente resultado.

Señal bajista:

SEÑAL BAJISTA

Señal alcista:

SEÑAL ALCISTA

En la imagen podemos ver que trazamos el patrón armónico y, una vez confirmado, también podemos operarlo correctamente, logrando así nuestro objetivo de identificar, trazar y operar con el patrón. Lo que queda por hacer es realizar el backtesting del programa, y eso se trata en la siguiente sección.


Backtesting

Tras realizar un backtesting exhaustivo, hemos obtenido los siguientes resultados.

Gráfico de backtesting:

GRÁFICO

Informe de backtesting:

INFORME


Conclusión

En conclusión, hemos desarrollado un sistema de patrón de Gartley en MQL5 que aprovecha la acción del precio para detectar patrones armónicos de Gartley alcistas y bajistas con proporciones de Fibonacci precisas, automatizar operaciones con puntos calculados de entrada, stop loss y take profit multinivel, y visualizar los patrones mediante objetos gráficos como triángulos y líneas de tendencia.

Aviso legal: este artículo tiene únicamente fines educativos. Las operaciones de trading conllevan riesgos financieros importantes, y la volatilidad del mercado puede dar lugar a pérdidas. Es fundamental realizar un backtesting exhaustivo y aplicar una gestión del riesgo rigurosa antes de poner en marcha este programa en mercados reales.

Aprovechando los conceptos y la implementación presentados, usted puede adaptar este sistema de patrón de Gartley a su estilo de trading, mejorando así sus estrategias algorítmicas. ¡Buen trading!

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19111

Archivos adjuntos |
Miguel Angel Vico Alba
Miguel Angel Vico Alba | 30 ago 2025 en 09:57
Este artículo es un ejemplo didáctico muy bueno para aprender a estructurar un EA y visualizar patrones armónicos en MQL5.

Solo una aclaración para los lectores: aunque en la introducción se dice que «tendrás una potente estrategia lista para personalizar», esto no debe interpretarse como un sistema listo para operar.

Las razones principales son:
  • Definición incompleta del patrón Gartley: el código solo comprueba que el punto D se encuentre en torno a 0,786 de XA. En el trading armónico, la idea clave es la Zona de Reversión Potencial (PRZ), que requiere la confluencia de múltiples ratios (por ejemplo, 0,786 de XA junto con 1,27–1,618 de BC). Sin ello, aparecen muchos patrones falsos.
  • Reglas de Stop Loss / Take Profit: aquí se definen como simples fracciones del movimiento hacia C. En el trading armónico, el stop suele colocarse más allá de X, y los objetivos se basan en los retrocesos de Fibonacci de AD. Si no se sigue esto, la relación riesgo/recompensa se vuelve arbitraria.
Así pues, como ejercicio de programación y visualización, es excelente. Pero para el trading real, un principiante debe comprender que se necesitan más reglas y validaciones antes de confiar en un programa de este tipo.
Arbitraje estadístico mediante acciones cointegradas (Parte 4): actualización del modelo en tiempo real Arbitraje estadístico mediante acciones cointegradas (Parte 4): actualización del modelo en tiempo real
Este artículo describe un pipeline de arbitraje estadístico sencillo pero completo para operar con una cesta de acciones cointegradas. Incluye un script de Python totalmente funcional para la descarga y el almacenamiento de datos; pruebas de correlación, cointegración y estacionariedad, junto con una implementación de ejemplo de un Servicio de MetaTrader 5 para la actualización de la base de datos y el Asesor Experto correspondiente. Aquí se recogen algunas decisiones de diseño a modo de referencia y para facilitar la reproducción del experimento.
Automatización de estrategias de trading en MQL5 (Parte 28): Creación de un patrón armónico «Bat» basado en la acción del precio con retroalimentación visual Automatización de estrategias de trading en MQL5 (Parte 28): Creación de un patrón armónico «Bat» basado en la acción del precio con retroalimentación visual
En este artículo, desarrollamos un sistema de patrones «Bat» en MQL5 que identifica patrones armónicos «Bat» alcistas y bajistas utilizando puntos de pivote y ratios de Fibonacci, activa operaciones con niveles precisos de entrada, stop loss y take profit, y se complementa con una representación visual mediante objetos del gráfico.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 14): cómo considerar las transformaciones de datos como parámetros de ajuste de nuestro controlador de realimentación Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 14): cómo considerar las transformaciones de datos como parámetros de ajuste de nuestro controlador de realimentación
El preprocesamiento es un parámetro de ajuste muy eficaz, aunque a menudo se pase por alto. Vive a la sombra de sus hermanos mayores: los optimizadores y las arquitecturas de modelos llamativas. Unas pequeñas mejoras porcentuales en este ámbito pueden tener efectos acumulativos desproporcionadamente grandes sobre la rentabilidad y el riesgo. Con demasiada frecuencia, esta ciencia en gran medida inexplorada se reduce a una simple rutina, vista solo como un medio para alcanzar un fin, cuando en realidad es ahí donde la señal puede amplificarse directamente o, con la misma facilidad, destruirse.