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
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:
- Comprensión del framework del patrón armónico de Gartley
- Implementación en MQL5
- Backtesting
- 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 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.

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.

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.

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 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 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:

Informe de backtesting:

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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
- 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.
Arbitraje estadístico mediante acciones cointegradas (Parte 4): actualización del modelo en tiempo real
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
Particularidades del trabajo con números del tipo double 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
- 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