Automatización de estrategias de trading en MQL5 (Parte 30): creación de un patrón armónico AB-CD de acción del precio con información visual
Introducción
En nuestro artículo anterior (Parte 29), desarrollamos un sistema basado en el patrón Gartley en MetaQuotes Language 5 (MQL5) que detectaba patrones armónicos de Gartley alcistas y bajistas usando ratios de Fibonacci precisos, automatizaba operaciones con niveles calculados de entrada, stop loss y take profit, y visualizaba los patrones con objetos gráficos como triángulos y líneas de tendencia. En la Parte 30, presentamos un sistema basado en el patrón AB=CD. Mientras que el sistema de Gartley depende de la detección de estructuras armónicas específicas de varios tramos, definidas por múltiples niveles de Fibonacci, el sistema AB=CD identifica patrones que se forman cuando aparecen dos segmentos de precio equivalentes (AB y CD) mediante puntos de pivote y ratios de retroceso y extensión bien definidos, lo que da lugar a una identificación de patrones más simple, aunque dinámica. El sistema AB=CD ejecuta operaciones utilizando entradas dinámicas y objetivos de take profit de varios niveles, y mejora la visualización mediante triángulos, líneas de tendencia y etiquetas para presentar los patrones con claridad. Hoy trataremos los siguientes temas:
Al finalizar, tendrá una estrategia sólida en MQL5 para operar con el patrón armónico AB=CD, lista para personalizarla. ¡Vamos allá!
Comprensión del marco del patrón armónico AB=CD
El patrón AB=CD es una formación armónica de trading que identifica posibles zonas de reversión a través de cuatro puntos de oscilación clave —A, B, C y D—, existe en formas alcista y bajista, y aprovecha los ratios de Fibonacci para localizar oportunidades de trading con alta probabilidad de éxito. En un patrón AB=CD alcista, la estructura forma una secuencia de máximo-mínimo-máximo-mínimo en la que A es un máximo de oscilación, B un mínimo de oscilación, C un máximo de oscilación y D un mínimo de oscilación (por debajo de B), siendo los tramos AB y CD de igual longitud o relacionados mediante ratios de retroceso y extensión de Fibonacci; un patrón AB=CD bajista sigue una secuencia de mínimo-máximo-mínimo-máximo, con D por encima de B. A continuación se muestra una visualización de los patrones:
Patrón armónico bajista AB=CD:

Patrón armónico alcista AB=CD:

Nuestro enfoque consiste en detectar estos pivotes dentro de un rango de barras específico, validar el patrón asegurándonos de que el tramo BC retroceda entre el 0,382 y el 0,886 del tramo AB y que el tramo CD se extienda entre el 1,13 y el 2,618 del tramo BC, visualizar el patrón con objetos gráficos como triángulos y líneas de tendencia para mayor claridad, y ejecutar operaciones en el punto D con un stop loss calculado y múltiples niveles de take profit basados en los retrocesos de Fibonacci para sacar partido de los giros previstos. ¡Pasemos ahora a la implementación!
Implementación en MQL5
Para crear el programa en MQL5, abra MetaEditor, vaya al Navegador, localice 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.
//+------------------------------------------------------------------+ //| ABCD 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 AB=CD 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 AB move) input double LotSize = 0.01; // Lot size for new orders input bool AllowTrading = true; // Enable or disable trading //--------------------------------------------------------------------------- //--- 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_lockedPatternA = 0; //--- The key A pivot time for the locked pattern
Para sentar las bases del patrón AB=CD, en primer lugar incluimos la biblioteca «<Trade\Trade.mqh>» y creamos una instancia de «obj_Trade» como objeto CTrade para gestionar las operaciones de trading, como la ejecución de órdenes de compra y venta. A continuación, procedemos a definir los parámetros de entrada para que el usuario pueda personalizarlos: «PivotLeft» y «PivotRight» en 5 barras cada uno para establecer la ventana de análisis retrospectivo para la detección de pivotes, «Tolerance» en 0,10 para permitir una desviación del 10 % en los ratios de Fibonacci, «LotSize» en 0,01 para el volumen de operación, y «AllowTrading» en «true» para habilitar el trading automatizado.
Luego 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 contener estos puntos e inicializamos las variables globales «g_patternFormationBar» en -1 para realizar un seguimiento de la barra en la que se forma un patrón y «g_lockedPatternA» en 0 para fijar la hora del pivote A con vistas a la confirmación del patrón, destacando el uso de «A» en lugar de «X» para alinearnos con el enfoque del patrón AB=CD en los tramos AB y CD. Esta configuración establece el marco básico para detectar patrones AB=CD y operar con ellos. Para la visualización, podemos usar 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); } }
A continuación, implementamos funciones de visualización para crear representaciones gráficas claras del patrón armónico AB=CD y sus niveles de operación. 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.
Después procedemos a crear la función «DrawTrendLine», que traza una línea de tendencia (OBJ_TREND) entre dos puntos y, por último, desarrollamos la función «DrawTextEx», que crea una etiqueta de texto (OBJ_TEXT) en las coordenadas («t», «p») con «ObjectCreate», estableciendo «OBJPROP_TEXT» en el texto especificado, «OBJPROP_COLOR», «OBJPROP_FONTSIZE» y «OBJPROP_FONT» en «Arial Bold» mediante ObjectSetString y «ObjectSetInteger», anclándola 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 centrándola con «OBJPROP_ALIGN». Ahora podemos pasar al controlador de eventos OnTick e intentar identificar puntos de pivote que podamos utilizar más adelante para el reconocimiento de 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. 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 en el 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. Después de ello, procedemos a vaciar el array «pivots» con ArrayResize para garantizar un análisis desde cero. A continuación, recuperamos 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, asumimos 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»; a continuación, la añadimos al array «pivots» utilizando ArrayResize y la almacenamos. Al imprimir la estructura de pivote, 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 four pivots are found, the pattern cannot be formed if(pivotCount < 4) { //--- Reset pattern lock variables g_patternFormationBar = -1; g_lockedPatternA = 0; //--- Exit the OnTick function return; } //--- Extract the last four pivots as A, B, C, and D 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 AB=CD pattern is found bool patternFound = false; //--- Initialize pattern type string patternType = ""; double used_retr = 0.0; double used_ext = 0.0; //--- Check for the high-low-high-low (Bullish reversal) structure if(A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) { //--- Calculate the difference between pivot A and B double diff = A.price - B.price; //--- Ensure the difference is positive if(diff > 0) { //--- Calculate the BC leg length double BC = C.price - B.price; double retrace = BC / diff; //--- Calculate the CD leg length double CD = C.price - D.price; double extension = CD / BC; //--- Define fib ratios double fib_retr[] = {0.382, 0.5, 0.618, 0.786, 0.886}; double fib_ext[] = {2.618, 2.0, 1.618, 1.272, 1.13}; bool valid = false; for(int k = 0; k < ArraySize(fib_retr); k++) { if(MathAbs(retrace - fib_retr[k]) <= Tolerance && MathAbs(extension - fib_ext[k]) <= Tolerance) { valid = true; used_retr = fib_retr[k]; used_ext = fib_ext[k]; break; } } if(valid && (D.price < B.price)) { patternFound = true; patternType = "Bullish"; } } } //--- Check for the low-high-low-high (Bearish reversal) structure if((!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) { //--- Calculate the difference between pivot B and A double diff = B.price - A.price; //--- Ensure the difference is positive if(diff > 0) { //--- Calculate the BC leg length double BC = B.price - C.price; double retrace = BC / diff; //--- Calculate the CD leg length double CD = D.price - C.price; double extension = CD / BC; //--- Define fib ratios double fib_retr[] = {0.382, 0.5, 0.618, 0.786, 0.886}; double fib_ext[] = {2.618, 2.0, 1.618, 1.272, 1.13}; bool valid = false; for(int k = 0; k < ArraySize(fib_retr); k++) { if(MathAbs(retrace - fib_retr[k]) <= Tolerance && MathAbs(extension - fib_ext[k]) <= Tolerance) { valid = true; used_retr = fib_retr[k]; used_ext = fib_ext[k]; break; } } if(valid && (D.price > B.price)) { patternFound = true; patternType = "Bearish"; } } }
En primer lugar, determinamos el número total de pivotes con «ArraySize(pivots)», almacenado en «pivotCount», y salimos si se encuentran menos de 4 pivotes, restableciendo «g_patternFormationBar» y «g_lockedPatternA» a -1 y 0, ya que el patrón AB=CD requiere los puntos A, B, C y D. Luego procedemos a extraer los cuatro últimos pivotes del array «pivots», asignándoles «A» (el más temprano), «B», «C» y «D» (el más reciente) para formar la estructura del patrón.
A continuación, comprobamos si existe un patrón alcista AB=CD (A máximo, B mínimo, C máximo, D mínimo) calculando la diferencia del tramo AB («A.price - B.price»), asegurándonos de que sea positiva, calculando la longitud del tramo BC («C.price - B.price») y su ratio de retroceso respecto a AB, calculando la longitud del tramo CD («C.price - D.price») y su ratio de extensión respecto a BC, definiendo los ratios de retroceso de Fibonacci (0,382, 0,5, 0,618, 0,786, 0,886) y los ratios de extensión (2,618, 2,0, 1,618, 1,272, 1,13), y validando si ambos ratios se encuentran dentro de «Tolerance», al tiempo que se garantiza que «D.price < B.price», se establece «patternFound» en «true» y «patternType» en «Bullish», y se almacenan los valores coincidentes «used_retr» y «used_ext». Por último, comprobamos si existe un patrón bajista AB=CD (A mínimo, B máximo, C mínimo, D máximo) usando cálculos similares para AB («B.price - A.price»), BC («B.price - C.price») y CD («D.price - C.price»), validando los resultados con los mismos ratios de Fibonacci y asegurándonos de que «D.price > B.price», estableciendo «patternFound» en «true» y «patternType» en «Bearish» si es válido. Si se detecta el patrón, podemos pasar a visualizarlo en el gráfico.
//--- If a valid AB=CD pattern is detected if(patternFound) { //--- Print a message indicating the pattern type and detection time Print(patternType, " AB=CD 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 = "AB_" + IntegerToString(A.time); //--- Choose triangle color based on the pattern type color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed; //--- Draw the first triangle connecting pivots A, B, and C DrawTriangle(signalPrefix+"_Triangle1", A.time, A.price, B.time, B.price, C.time, C.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); }
Para visualizar el patrón, en primer lugar, si se detecta un patrón válido («patternFound» es verdadero), registramos la detección con Print, mostrando «patternType» («Bullish» o «Bearish») y la hora 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 «AB_» con «A.time», convertido en una cadena mediante IntegerToString, para garantizar nombres distintos para los objetos gráficos.
Después establecemos «triangleColor» en azul para los patrones alcistas o en rojo para los bajistas, con el fin de diferenciarlos visualmente. Por último, llamamos a «DrawTriangle» dos veces para visualizar el patrón: primero, para dibujar el triángulo ABC que conecta los pivotes A, B y C, y después, para dibujar el triángulo BCD que conecta los pivotes B, C y D, usando «signalPrefix» con los sufijos «_Triangle1» y «_Triangle2», los tiempos y precios de los pivotes correspondientes, «triangleColor», un ancho de 2, y activando el relleno y la visualización en segundo plano con valores booleanos “true”. Obtenemos el siguiente resultado.

En la imagen se puede observar que podemos trazar y visualizar correctamente el patrón detectado. Ahora tenemos que seguir trazando las líneas de tendencia para delimitarlo visualmente por completo 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_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); //--- 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_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_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 A and C datetime centralTime = (A.time + C.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 AB=CD" : "Bearish AB=CD"); 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); }
Mejoramos aún más la visualización de los patrones detectados añadiendo objetos gráficos detallados que representan con claridad la estructura de dichos patrones. En primer lugar, trazamos tres líneas de tendencia continuas utilizando «DrawTrendLine» con el «signalPrefix» único para conectar los puntos de pivote clave: AB, BC y CD, usando los tiempos y precios de los pivotes (por ejemplo, «A.time», «A.price»), estableciendo OBJPROP_COLOR en «clrBlack», «OBJPROP_WIDTH» en 2 y «OBJPROP_STYLE» en «STYLE_SOLID» con ObjectSetInteger para delinear los tramos del patrón. A continuación, obtenemos el tamaño del punto del símbolo mediante SymbolInfoDouble(_Symbol, SYMBOL_POINT) y calculamos un desplazamiento de 15 puntos para el posicionamiento de las etiquetas, determinando las coordenadas Y («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» verdadero) o un mínimo, 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 A, B, C y D con «signalPrefix» y sufijos como «_Text_A», mostrando la letra correspondiente, situadas en el tiempo del pivote y con la coordenada Y ajustada, usando «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 «A.time» y «C.time», y «centralPrice» como «D.price», creando un objeto de texto con ObjectCreate denominado «signalPrefix + '_Text_Center'», estableciendo OBJPROP_TEXT en «Bullish AB=CD» o «Bearish AB=CD» según «patternType», y configurando «OBJPROP_COLOR» en «clrBlack», «OBJPROP_FONTSIZE» en 11, OBJPROP_FONT en «Arial Bold» y «OBJPROP_ALIGN» en «ALIGN_CENTER» mediante ObjectSetString y «ObjectSetInteger», garantizando así una representación visual completa de la estructura y el tipo del patrón en el gráfico. Al ejecutar el programa, esta es una visualización del resultado que obtenemos.

En la imagen podemos ver que hemos añadido los tramos 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 operación 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; //--- Calculate pattern range (CD length) double patternRange = (patternType=="Bullish") ? (C.price - D.price) : (D.price - C.price); //--- 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; //--- Set TP1 at 0.382 fib retrace from D to C TP1Level = D.price + 0.382 * patternRange; //--- Set TP2 at 0.618 fib retrace from D to C TP2Level = D.price + 0.618 * patternRange; } 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; //--- Set TP1 at 0.382 fib retrace from D to C TP1Level = D.price - 0.382 * patternRange; //--- Set TP2 at 0.618 fib retrace from D to C TP2Level = D.price - 0.618 * patternRange; } //--- 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 operación, establecemos «lineStart» en la hora del pivote D («D.time») y «lineEnd» dos periodos más adelante utilizando «PeriodSeconds(_Period) * 2», y declaramos las variables «entryPriceLevel», «TP1Level», «TP2Level» y «TP3Level» para los cálculos de las operaciones. A continuación, calculamos «patternRange» como la longitud del tramo CD («C.price - D.price» para un patrón alcista, «D.price - C.price» para uno bajista); para un patrón alcista, establecemos «entryPriceLevel» en el precio Ask con SymbolInfoDouble, «TP3Level» en el precio de C, «TP1Level» en «D.price + 0.382 * patternRange» y «TP2Level» en «D.price + 0.618 * patternRange»; para un patrón bajista, utilizamos el precio Bid, establecemos «TP3Level» en el precio de C, «TP1Level» en «D.price - 0.382 * patternRange» y «TP2Level» en «D.price - 0.618 * patternRange».
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 los textos de las etiquetas con los precios formateados mediante DoubleToString (por ejemplo, «BUY (price)» o «SELL (price)» para la entrada, «TP1 (price)», 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. 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 operación. Lo que tenemos que hacer ahora es abrir las posiciones de trading reales, y con ello quedaría completada esta parte.
//--- 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_lockedPatternA = A.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 A pivot if(g_lockedPatternA == A.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; //--- Determine next extension for SL double next_ext = 0.0; if(MathAbs(used_ext - 1.13) < 0.05) next_ext = 1.272; else if(MathAbs(used_ext - 1.272) < 0.05) next_ext = 1.618; else if(MathAbs(used_ext - 1.618) < 0.05) next_ext = 2.0; else if(MathAbs(used_ext - 2.0) < 0.05) next_ext = 2.618; else if(MathAbs(used_ext - 2.618) < 0.05) next_ext = 3.618; else next_ext = used_ext * 1.618; // fallback //--- For a Bullish pattern, execute a BUY trade if(patternType=="Bullish") { //--- BUY signal entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double BC_leg = C.price - B.price; stopLoss = C.price - next_ext * BC_leg; if(stopLoss > D.price) stopLoss = D.price - 10 * point; takeProfit = TP2Level; tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "AB=CD 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 BC_leg = B.price - C.price; stopLoss = C.price + next_ext * BC_leg; if(stopLoss < D.price) stopLoss = D.price + 10 * point; takeProfit = TP2Level; tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "AB=CD 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 A pivot g_patternFormationBar = currentBarIndex; g_lockedPatternA = A.time; Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation."); return; } } } else { //--- If no valid AB=CD pattern is detected, reset the pattern lock variables g_patternFormationBar = -1; g_lockedPatternA = 0; }
Aquí finalizamos la implementación del tick para el patrón, gestionando la ejecución de operaciones y la confirmación del patrón armónico AB=CD detectado. En primer lugar, obtenemos el índice de la barra actual con «Bars(_Symbol, _Period) - 1» y lo almacenamos en «currentBarIndex». Luego, si no hay ningún patrón bloqueado («g_patternFormationBar == -1»), establecemos «g_patternFormationBar» en «currentBarIndex», fijamos el tiempo del pivote A en «g_lockedPatternA» con «A.time», registramos la detección indicando que hay que esperar a la confirmación y salimos. A continuación, si seguimos en la barra de formación («currentBarIndex == g_patternFormationBar»), salimos para evitar operaciones prematuras.
Por último, si se ha formado una nueva barra («currentBarIndex > g_patternFormationBar») y el pivote A coincide con «g_lockedPatternA», confirmamos el patrón, lo registramos, actualizamos «g_patternFormationBar» y comprobamos si se permite operar con «AllowTrading» y si no existen posiciones abiertas mediante PositionSelect; determinamos la siguiente extensión de Fibonacci («next_ext») para el stop loss basándonos en «used_ext» (por ejemplo, de 1,13 a 1,272, hasta 3,618, o un valor de reserva de «used_ext * 1,618»); para un patrón alcista, establecemos «entryPriceTrade» en el precio Ask, calculamos «BC_leg» como «C.price - B.price», establecemos «stopLoss» en «C.price - next_ext * BC_leg» (ajustado a «D.price - 10 * point» si está por encima de D), establecemos «takeProfit» en «TP2Level» y ejecutamos una compra con «obj_Trade.Buy» utilizando «LotSize» y «AB=CD Signal», registrando si se ha realizado correctamente o si ha fallado; para un patrón bajista, utilizamos el precio Bid, calculamos «BC_leg» como «B.price - C.price», establecemos «stopLoss» en «C.price + next_ext * BC_leg» (ajustado a «D.price + 10 * point» si está por debajo de D) y ejecutamos una venta con «obj_Trade.Sell»; si no se permite operar o ya existe una posición, registramos que no se ha realizado 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 resultado que sigue.
Señal bajista:

Señal alcista:

En la imagen podemos ver que trazamos el patrón armónico y que, una vez confirmado, seguimos pudiendo operar con él en consecuencia, logrando así nuestro objetivo de identificar, trazar y operar el patrón. Solo queda 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 backtest:

Informe de backtest:

Conclusión
En conclusión, hemos desarrollado un sistema de patrones AB=CD en MQL5, que aprovecha la acción del precio para detectar patrones armónicos AB=CD alcistas y bajistas con ratios precisos de retroceso y extensión de Fibonacci, automatiza las operaciones con niveles calculados de entrada, stop loss y take profit en varios niveles, y visualiza los patrones mediante objetos gráficos como triángulos y líneas de tendencia.
Aviso legal: el presente 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. Resulta fundamental realizar un backtesting exhaustivo y aplicar una gestión del riesgo rigurosa antes de implementar este programa en mercados reales.
Aprovechando los conceptos y la implementación presentados, podrá adaptar este sistema basado en el patrón AB=CD a su estilo de trading, mejorando así sus estrategias algorítmicas. ¡Que tenga buenas operaciones de trading!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19442
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.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 13): Introducción a la teoría de control mediante la factorización de matrices
Arbitraje estadístico mediante acciones cointegradas (Parte 4): actualización del modelo en tiempo real
Automatización de estrategias de trading en MQL5 (parte 31): Creación de un sistema de patrón armónico de 3 Drivesbasado 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
- 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
La marca registrada (TM) quedará invalidada mediante cualquier patrón de cuatro puntos creado de forma natural por los movimientos de los precios, ya que las denominaciones en inglés asignadas como descriptores no pueden ajustarse a la norma de la marca registrada (TM) en lo que respecta a la gestión de la propiedad intelectual y a la titularidad de derechos exclusivos sobre dichas descripciones. Te tacho de farsante y has malgastado entre 350,00 y 850,00 $ en la oficina de la marca registrada, y esto será impugnado.
Esta es mi opinión.
Louis B. Wetzel
La marca registrada quedará invalidada mediante cualquier patrón de cuatro puntos creado de forma natural por los movimientos de los precios, ya que las denominaciones en inglés asignadas como descriptores no pueden ajustarse a la norma de la marca registrada en materia de gestión de la propiedad intelectual y no confieren derechos exclusivos sobre dichas descripciones. Te tacho de farsante y has malgastado entre 350,00 y 850,00 $ en la oficina de la TM, por lo que se te impugnará.
Esta es mi opinión.
Louis B. Wetzel