English Русский 中文 Deutsch 日本語
preview
Automatización de estrategias de trading en MQL5 (Parte 28): Creación de un patrón armónico «Bat» basado en la acción del precio con retroalimentación visual

Automatización de estrategias de trading en MQL5 (Parte 28): Creación de un patrón armónico «Bat» basado en la acción del precio con retroalimentación visual

MetaTrader 5 — Sistemas comerciales |
28 0
Allan Munene Mutiiria
Allan Munene Mutiiria

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:

  1. Comprensión del marco del patrón armónico Bat
  2. Implementación en MQL5
  3. Backtesting
  4. Conclusión

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 DE MURCIÉLAGO ALCISTA

Patrón armónico bajista Bat:

PATRÓN DE MURCIÉLAGO 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 (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.

DATOS DE PIVOTES

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.

CONJUNTO DE TRIÁNGULOS

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.

PATRÓN CON ETIQUETAS Y BORDES

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 BAJISTA

Patrón alcista:

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 BAJISTA

Señal alcista:

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:

GRÁFICO

Informe de backtesting:

INFORME


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

Archivos adjuntos |
Bat_Pattern_EA.mq5 (50.85 KB)
Automatización de estrategias de trading en MQL5 (Parte 29): Creación de un sistema de patrón armónico de Gartley basado en la acción del precio Automatización de estrategias de trading en MQL5 (Parte 29): Creación de un sistema de patrón armónico de Gartley basado en la acción del precio
En este artículo, desarrollamos un sistema basado en el patrón de Gartley en MQL5 que identifica patrones armónicos de Gartley alcistas y bajistas usando puntos de pivote y ratios de Fibonacci, y que ejecuta operaciones con niveles precisos de entrada, stop loss y take profit. Mejoramos la comprensión de los operadores con información visual mediante elementos gráficos como triángulos, líneas de tendencia y etiquetas, para mostrar con claridad la estructura del patrón XABCD.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 14): cómo considerar las transformaciones de datos como parámetros de ajuste de nuestro controlador de realimentación Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 14): cómo considerar las transformaciones de datos como parámetros de ajuste de nuestro controlador de realimentación
El preprocesamiento es un parámetro de ajuste muy eficaz, aunque a menudo se pase por alto. Vive a la sombra de sus hermanos mayores: los optimizadores y las arquitecturas de modelos llamativas. Unas pequeñas mejoras porcentuales en este ámbito pueden tener efectos acumulativos desproporcionadamente grandes sobre la rentabilidad y el riesgo. Con demasiada frecuencia, esta ciencia en gran medida inexplorada se reduce a una simple rutina, vista solo como un medio para alcanzar un fin, cuando en realidad es ahí donde la señal puede amplificarse directamente o, con la misma facilidad, destruirse.
Arbitraje estadístico mediante acciones cointegradas (Parte 4): actualización del modelo en tiempo real Arbitraje estadístico mediante acciones cointegradas (Parte 4): actualización del modelo en tiempo real
Este artículo describe un pipeline de arbitraje estadístico sencillo pero completo para operar con una cesta de acciones cointegradas. Incluye un script de Python totalmente funcional para la descarga y el almacenamiento de datos; pruebas de correlación, cointegración y estacionariedad, junto con una implementación de ejemplo de un Servicio de MetaTrader 5 para la actualización de la base de datos y el Asesor Experto correspondiente. Aquí se recogen algunas decisiones de diseño a modo de referencia y para facilitar la reproducción del experimento.
Desarrollo de un indicador personalizado de sentimiento del mercado Desarrollo de un indicador personalizado de sentimiento del mercado
En este artículo desarrollaremos un indicador personalizado de sentimiento del mercado para clasificar las condiciones como alcistas, bajistas, de apetito por el riesgo (risk-on), de aversión al riesgo (risk-off) o neutras. Utilizando varios marcos temporales, el indicador puede ofrecer a los operadores una perspectiva más clara del sesgo general del mercado y de las confirmaciones a corto plazo.