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
Introducción
En nuestro artículo anterior (Parte 27), desarrollamos un sistema denominado «Crab Pattern» en MetaQuotes Language 5 (MQL5) que utilizaba patrones armónicos para identificar puntos de reversión de alta probabilidad con ratios de Fibonacci precisos, automatizando las operaciones y visualizando los patrones mediante objetos gráficos. En la parte 28, creamos un sistema de patrones Bat que detecta patrones armónicos Bat alcistas y bajistas utilizando puntos de pivote y retrocesos de Fibonacci específicos. El programa ejecuta operaciones con puntos de entrada, stop-loss y take-profit multinivel calculados, complementados con triángulos visuales, líneas de tendencia y etiquetas para representar claramente los patrones. Trataremos los siguientes temas:
Al final, tendrás una sólida estrategia MQL5 para operar con patrones armónicos Bat, lista para personalizar. ¡Empecemos!
Comprensión del marco del patrón armónico Bat
El patrón Bat 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 posibles reversiones del mercado mediante mediciones precisas de Fibonacci. En un patrón Bat 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,5 de XA), el punto C un máximo de oscilación (que se extiende entre el 0,382 y el 0,886 de AB), y el punto D, un mínimo oscilante (que se extiende hasta el 0,886 de XA y se sitúa por encima de X). Por el contrario, un patrón «Bat» 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 se extiende hasta el 0,886 de XA y se sitúa por debajo de X). A continuación se muestran los tipos de patrones visualizados.
Patrón armónico alcista Bat:

Patrón armónico bajista Bat:

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 (hacia arriba en el patrón alcista y hacia abajo en el bajista) y sirve como referencia principal para los cálculos de Fibonacci.
- Determinación del tramo AB: El punto B debería retroceder aproximadamente el 0,5 del tramo XA, lo que indica una corrección moderada del impulso inicial.
- Análisis del tramo BC: Este tramo debería extenderse entre el 0,382 y el 0,886 del tramo AB, creando un movimiento contrario que prepare la zona de reversión final.
- Configuración del tramo CD: El tramo final debe extenderse 0,886 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 la secuencia de Fibonacci, nuestro sistema de negociación detectará de forma sistemática patrones Bat válidos en los datos de precios. Una vez confirmado un patrón, el EA 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 entrada y de toma de beneficios. Esta configuración permite ejecutar operaciones automáticamente en el punto D, con un stop loss calculado y varios niveles de take profit, aprovechando la capacidad del patrón para anticipar reversiones del mercado. ¡Pasemos a la implementación!
Implementación en MQL5
Para crear el programa en MQL5, abre el MetaEditor, ve al Navegador, busca la carpeta «Indicadores», haz clic en la pestaña «Nuevo» y sigue las instrucciones para crear el archivo. Una vez creado, en el entorno de programación, tendremos que declarar algunas variables globales que utilizaremos a lo largo de todo el programa.
//+------------------------------------------------------------------+ //| Bat 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 Bat 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 //--------------------------------------------------------------------------- //--- Bat pattern definition: // //--- Bullish Bat: //--- 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.5*(A-X); Legs within specified ranges. // //--- Bearish Bat: //--- 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.5*(X-A); Legs within specified ranges. //--------------------------------------------------------------------------- //--- 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 bool g_tradeTaken = false; //--- Flag to indicate if a trade has been taken for the current pattern
Para sentar las bases del sistema patrón Bat, destinado a automatizar las operaciones de trading basadas en el patrón armónico Bat, en primer lugar, incluimos la biblioteca «<Trade\Trade.mqh>» y creamos una instancia de «obj_Trade» como objeto CTrade para gestionar 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 la personalización por parte del usuario: «PivotLeft» y «PivotRight», con 5 barras cada uno, para establecer el intervalo de retrospectiva para la detección de puntos de pivote; «Tolerance», con un valor de 0,10, para el margen de desviación de Fibonacci; «LotSize», con un valor de 0,01, para el volumen de la operación; y «AllowTrading», con el valor «true», para habilitar el trading automatizado.
A continuación, definimos la estructura «Pivot» con «time» (datetime), «price» (double) e «isHigh» (bool) para almacenar los puntos de oscilación; declaramos «pivots» como una matriz dinámica para guardar estos puntos, e inicializamos las variables globales «g_patternFormationBar» a -1 para el seguimiento de la formación de patrones, «g_lockedPatternX» a 0 para bloquear el tiempo del pivote X y «g_tradeTaken» a false para evitar operaciones múltiples en el mismo patrón, sentando así las bases del EA para detectar y operar con patrones Bat. Para la visualización, podemos disponer de funciones para trazar 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); } }
Para crear representaciones gráficas claras del patrón y sus niveles clave, desarrollamos la función «DrawTriangle», que utiliza ObjectCreate para dibujar un triángulo relleno (OBJ_TRIANGLE) con tres puntos definidos por momentos («t1», «t2», «t3») y precios («p1», «p2», «p3»), estableciendo OBJPROP_COLOR en el color especificado, «OBJPROP_STYLE» en «STYLE_SOLID», «OBJPROP_WIDTH» al ancho indicado, «OBJPROP_FILL» para activar o desactivar el relleno, y «OBJPROP_BACK» para establecer la posición en primer plano o fondo 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 utilizando el mismo formato que para los triángulos.
A continuación, implementamos la función «DrawDottedLine», que crea una línea punteada horizontal y define sus propiedades de forma similar. Por último, desarrollamos la función «DrawTextEx», que crea una etiqueta de texto (OBJ_TEXT) en las coordenadas («t», «p»), estableciendo «OBJPROP_TEXT» con 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 la propiedad «OBJPROP_ALIGN». Ahora podemos pasar al controlador de eventos OnTick e intentar encontrar puntos de pivote que podamos utilizar más adelante para la identificación 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; } } }
Procedemos a implementar la lógica inicial del controlador de eventos OnTick para que el sistema detecte los puntos de pivote de oscilación, que son esenciales para identificar el patrón armónico «Bat». 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 turno 1 para el símbolo y el periodo actuales; si no ha cambiado, salimos del bucle 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 garantizar un análisis nuevo.
A continuación, obtenemos el número total de barras con la función Bars, establecemos el rango de detección del pivote 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 se trata de 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 adyacentes 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 «tiempo» con «iTime», su «precio» en el máximo o el mínimo según «isPivotHigh» y el indicador «isHigh», y luego la añadimos a la matriz «pivots» mediante ArrayResize y la almacenamos. Al imprimir la matriz, obtenemos el siguiente resultado.

Con los datos, podemos extraer los puntos de pivote y, si tenemos suficientes, podemos analizarlos 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; g_tradeTaken = false; //--- 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 Bat pattern is found bool patternFound = false; //--- 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.5 * 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 extension double extension = D.price - A.price; //--- Verify that the extension is within tolerance of 0.886 and that D is below X if(MathAbs(extension - 0.886 * diff) <= Tolerance * diff && (D.price < X.price)) patternFound = true; } } } } //--- 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.5 * 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 extension double extension = A.price - D.price; //--- Verify that the extension is within tolerance of 0.886 and that D is above X if(MathAbs(extension - 0.886 * diff) <= Tolerance * diff && (D.price > X.price)) patternFound = true; } } } }
En este artículo, seguimos identificando patrones alcistas y bajistas utilizando criterios precisos basados en Fibonacci. En primer lugar, determinamos el número total de pivotes con «ArraySize (pivotes)» almacenado en «pivotCount» y salimos si se encuentran menos de 5 pivotes, restableciendo «g_patternFormationBar», «g_lockedPatternX» y «g_tradeTaken» a -1, 0 y false, respectivamente, ya que el patrón Bat requiere los puntos X, A, B, C y D. A continuación, procedemos a extraer los últimos cinco pivotes del array «pivots», asignándoles «X» (el más temprano), «A», «B», «C» y «D» (el más tardío) para formar la estructura del patrón.
A continuación, comprobamos si existe un patrón Bat bajista (X: máximo, A: mínimo, B: máximo, C: mínimo, D: máximo) calculando la diferencia del tramo XA («precio de X - precio de A»), asegurándonos de que sea positiva, calculando el punto B ideal como «precio de A + 0,5 * diff», verificando que B se encuentre dentro de «Tolerancia * diferencia» utilizando MathAbs, comprobando el tramo BC (entre 0,382 y 0,886 de AB) y confirmando la extensión AD (0,886 de XA con D por debajo de X), y estableciendo «patternFound» en «true» si se cumplen todas las condiciones. Por último, comprobamos si existe un patrón Bat alcista (X mínimo, A máximo, B mínimo, C máximo, D mínimo), calculando XA como «precio de A - precio de X», asegurándonos de que sea positivo, verificando que B se encuentre en el retroceso del 0,5 y que BC esté comprendido entre el 0,382 y el 0,886 de AB, y AD en el 0,886 de XA con D por encima de X, estableciendo «patternFound» en «true» si es válido, lo que garantiza una detección precisa de los patrones Bat para su posterior visualización y negociación. Si se encuentra el patrón, podemos proceder a visualizarlo en el gráfico.
//--- If a valid Bat pattern is detected if(patternFound) { //--- Print a message indicating the pattern type and detection time Print(patternType, " Bat 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 = "BAT_" + 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); }
En primer lugar, si se detecta un patrón válido («patternFound» es verdadero), inicializamos «patternType» como una cadena vacía y lo establecemos en «Bearish» si «D.price» es menor que «X.price» (lo que indica una señal de venta) o en «Bullish» si es mayor (lo que indica una señal de compra). A continuación, registramos la detección con Print, mostrando el «patternType» y la hora del pivote D formateada con TimeToString, incluyendo la fecha, los minutos y los segundos. A continuación, creamos un identificador único «signalPrefix» concatenando «BAT_» con «X.time», convertido a cadena mediante la función IntegerToString. Por último, establecemos «triangleColor» en azul para los patrones alcistas o en rojo para los bajistas, y llamamos a «DrawTriangle» dos veces: primero para dibujar el triángulo XAB que une los puntos de pivote X, A y B, y luego para dibujar el triángulo BCD que une los puntos de pivote B, C y D, utilizando «signalPrefix» con los sufijos «_Triangle1» y «_Triangle2», las horas y los precios de los puntos de pivote respectivos, «triangleColor», un ancho de 2, y activando el relleno y el fondo con los indicadores «true». Esta lógica garantizará una visualización clara de la estructura del patrón «Bat», lo que facilitará la toma de decisiones de negociación. Este es el resultado.

En la imagen podemos ver que podemos mapear y visualizar correctamente el patrón detectado. Ahora debemos continuar trazando las líneas de tendencia para delimitar completamente la figura y hacerla más visible y agregarles una etiqueta para facilitar la identificación de 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 Bat" : "Bearish Bat"); 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); }
Para que el patrón resulte aún más ilustrativo, añadimos elementos gráficos detallados que representan su estructura. 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, cada uno de ellos utilizando los tiempos y precios de pivote (por ejemplo, «X.time», «X.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, recuperamos el tamaño en puntos del símbolo con «SymbolInfoDouble(_Symbol, SYMBOL_POINT) » y calculamos un desplazamiento de 15 puntos para el posicionamiento de 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 («isHigh» verdadero) o un mínimo, para colocar las etiquetas por encima de los máximos o por debajo de los mínimos. Puedes modificarlo en función de la estética y el símbolo con el que estés operando.
A continuación, 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, situada en el momento 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 en «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 Bat» o «Bearish Bat» 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 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 contornos y las etiquetas al patrón, lo que lo hace más claro y ilustrativo. Lo siguiente que tenemos que hacer es determinar los niveles de negociación de la figura.
//--- Define start and end times for drawing horizontal dotted lines for obj_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 obj_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 obj_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 obj_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 operación del patrón detectado, primero establecemos «lineStart» en el momento del pivote D («D.time») y «lineEnd» en 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 la operación. A continuación, para un patrón alcista («patternType == 'Bullish'»), establecemos «entryPriceLevel» en el precio Ask actual con SymbolInfoDouble, «TP3Level» al 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, fijamos «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 la operación.
A continuación, 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 cierre de beneficios 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 período, creamos los textos de las etiquetas con los precios formateados mediante DoubleToString (por ejemplo, «COMPRAR (precio)» o «VENDER (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 precios. Tras la compilación, obtenemos el siguiente resultado.
Patrón bajista:

Patrón alcista:

En las imágenes podemos ver que hemos representado correctamente los niveles comerciales. Lo que tenemos que hacer ahora es iniciar las posiciones comerciales reales, 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 obj_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 obj_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) && !g_tradeTaken) { double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0; point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); bool tradeResult = false; //--- For a Bullish pattern, execute a BUY obj_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, "Bat Signal"); if(tradeResult) { Print("Buy order opened successfully."); g_tradeTaken = true; } else Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); } //--- For a Bearish pattern, execute a SELL obj_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, "Bat Signal"); if(tradeResult) { Print("Sell order opened successfully."); g_tradeTaken = true; } else Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); } } else { //--- If a position is already open, do not execute a new obj_Trade Print("A position is already open for ", _Symbol, ". No new obj_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; g_tradeTaken = false; Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation."); return; } } else { //--- If no valid Bat pattern is detected, reset the pattern lock variables g_patternFormationBar = -1; g_lockedPatternX = 0; g_tradeTaken = false; }
Aquí finalizamos la implementación de ticks para el sistema mediante la gestión de la ejecución de operaciones y la confirmación de patrones. En primer lugar, recuperamos 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»), asignamos a «g_patternFormationBar» el valor de «currentBarIndex», bloqueamos la hora del pivote X en «g_lockedPatternX» con «X.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»), registramos el repintado y salimos para evitar operaciones 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 se permite operar con «AllowTrading», si no hay posiciones abiertas a través de PositionSelect, y no se ha realizado ninguna operación («g_tradeTaken» falso); para un patrón alcista, establecemos «entryPriceTrade» en el precio de venta, calculamos «diffTrade» como «TP2Level - entryPriceTrade», fijamos «stopLoss» tres veces esta distancia por debajo, establecemos «takeProfit» en «TP2Level» y ejecutamos una compra con «obj_Trade. Buy» utilizando «LotSize» y «Bat Signal», registrando el éxito o el fracaso y estableciendo «g_tradeTaken» en verdadero; para un patrón bajista, utilizamos el precio Bid, fijamos «stopLoss» tres veces por encima y ejecutamos una venta con «obj_Trade. Sell»; si no se permite operar, existe una posición o ya se ha realizado una operación, registramos que no hay 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 que aún podemos operar en consecuencia una vez que se confirma, 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 exhaustivo backtesting, hemos obtenido los siguientes resultados.
Gráfico de backtesting:

Informe de backtesting:

Conclusión
En conclusión, hemos desarrollado un sistema denominado Bat Pattern en MQL5, que aprovecha la evolución del precio para detectar patrones armónicos «Bat» alcistas y bajistas con proporciones de Fibonacci precisas, automatiza las operaciones con puntos de entrada, stop loss y take-profit multinivel calculados, y visualiza los patrones mediante objetos gráficos como triángulos y líneas de tendencia.
Descargo de responsabilidad: Este artículo tiene fines exclusivamente educativos. Operar en los mercados financieros conlleva riesgos financieros significativos, y la volatilidad del mercado puede ocasionar pérdidas. Es fundamental realizar un backtesting exhaustivo y una gestión cuidadosa del riesgo antes de poner en marcha este programa en los mercados reales.
Aprovechando los conceptos y la implementación que se han presentado, podrás adaptar este sistema basado en el patrón «Bat» a tu estilo de negociación, mejorando así tus estrategias algorítmicas. ¡Buen trading!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19105
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.
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
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
Arbitraje estadístico mediante acciones cointegradas (Parte 4): actualización del modelo en tiempo real
Desarrollo de un indicador personalizado de sentimiento del mercado
- 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