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

Automatización de estrategias de trading en MQL5 (Parte 33): Creación de un sistema basado en el patrón armónico Shark y la acción del precio

MetaTrader 5 — Trading |
21 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En nuestro artículo anterior (Parte 32), desarrollamos un sistema basado en el patrón 5 Drives (5-0) en MetaQuotes Language 5 (MQL5) que detectaba patrones armónicos 5 Drives alcistas y bajistas mediante ratios de Fibonacci y automatizaba las operaciones con niveles personalizables de stop loss y take profit, con una representación visual mediante objetos gráficos como triángulos y líneas de tendencia. En la parte 33, desarrollamos un sistema basado en el patrón Shark que identifica patrones armónicos Shark alcistas y bajistas mediante puntos pivote y retrocesos y extensiones de Fibonacci específicos. Este sistema ejecuta operaciones con opciones flexibles de entrada, stop loss y take profit en varios niveles, y utiliza triángulos, líneas de tendencia y etiquetas para representar claramente los patrones. Hoy hablaremos de los siguientes temas:

  1. Comprendiendo los fundamentos del patrón armónico Shark
  2. Implementación en MQL5
  3. Backtesting
  4. Conclusión

Al final, dispondrá de una estrategia sólida en MQL5 para operar con el patrón Shark armónico, lista para personalizarla. ¡Manos a la obra!


Comprendiendo los fundamentos del patrón armónico Shark

El patrón Shark es una formación armónica de trading definida por cinco puntos de oscilación clave —X, A, B, C y D— que se presenta en variantes alcistas y bajistas y está diseñada para identificar zonas de reversión de alta probabilidad mediante retrocesos de Fibonacci y extensiones específicos. En un patrón Shark alcista, la estructura forma una secuencia mínimo-máximo-mínimo-máximo-mínimo, en la que X es un mínimo de oscilación, A un máximo de oscilación, B un mínimo de oscilación (con un retroceso de entre 0,32 y 0,50 veces XA), C un máximo de oscilación (con una extensión de entre 1,13 y 1,618 veces AB) y D un mínimo de oscilación (con una extensión de entre 1,618 y 2,24 veces BC, por debajo de B); un patrón Shark bajista invierte esta secuencia, con D por encima de B. Aquí podemos ver una representación gráfica de los patrones:

Patrón armónico Shark bajista:

PATRÓN ARMÓNICO SHARK BAJISTA

Patrón armónico Shark alcista:

PATRÓN ARMÓNICO SHARK ALCISTA

Nuestro enfoque consiste en detectar estos puntos pivote de oscilación dentro de un rango de barras especificado, validar los tramos del patrón según criterios de Fibonacci definidos por el usuario, visualizar la estructura X-A-B-C-D con objetos gráficos como triángulos y líneas de tendencia, y ejecutar operaciones en el punto D con niveles personalizables de stop loss (basados en Fibonacci o fijos) y take profit (un tercio, dos tercios o el punto pivote C) para aprovechar las reversiones previstas. ¡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, tendremos que declarar en el entorno de programación algunas variables globales que utilizaremos a lo largo de todo el programa.

//+------------------------------------------------------------------+
//|                                             Shark 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 Shark 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
//--- Enumeration for TP levels
enum ENUM_TAKE_PROFIT_LEVEL {
   TP1 = 1, // One-third of the move to C
   TP2 = 2, // Two-thirds of the move to C
   TP3 = 3  // Pivot C Price
};
//--- Enumeration for SL types
enum ENUM_STOP_LOSS_TYPE {
   SL_FIBO = 1, // Fibonacci Extension
   SL_FIXED = 2 // Fixed Points
};
//--- 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 MinRetrace = 0.32;                      // Minimum retracement for AB (strictness control)
input double MaxRetrace = 0.50;                      // Maximum retracement for AB (strictness control)
input double MinExt1 = 1.13;                         // Minimum extension for BC (strictness control)
input double MaxExt1 = 1.618;                        // Maximum extension for BC (strictness control)
input double MinExt2 = 1.618;                        // Minimum extension for CD (strictness control)
input double MaxExt2 = 2.24;                         // Maximum extension for CD (strictness control)
input double LotSize = 0.01;                         // Lot size for new orders
input bool AllowTrading = true;                      // Enable or disable trading
input ENUM_TAKE_PROFIT_LEVEL TakeProfitLevel = TP2;  // Take Profit Level
input ENUM_STOP_LOSS_TYPE StopLossType = SL_FIBO;    // Stop Loss Type
input double SL_FiboExtension = 1.618;               // Fibonacci Extension for SL
input double SL_FixedPoints = 50;                    // Fixed Points for SL (in points)
//---------------------------------------------------------------------------

//--- 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
//--- Global array to track traded patterns (using X.time as identifier)
datetime tradedPatterns[];

Para sentar las bases del sistema del patrón Shark, primero incluimos la biblioteca «<Trade\Trade.mqh>» y creamos una instancia de «CTrade» llamada «obj_Trade» como objeto CTrade para gestionar las operaciones de trading, como la ejecución de órdenes de compra y venta. A continuación, definimos las enumeraciones «ENUM_TAKE_PROFIT_LEVEL» (TP1 para un tercio, TP2 para dos tercios, TP3 para el precio del punto pivote C) y «ENUM_STOP_LOSS_TYPE» (SL_FIBO para la extensión de Fibonacci, SL_FIXED para un número fijo de puntos) para permitir una configuración flexible de las operaciones, y establecemos los parámetros de entrada: «PivotLeft» y «PivotRight» en 5 barras para la detección de puntos pivote, «Tolerance» en 0,10 para la desviación de Fibonacci, «MinRetrace» en 0,32 y «MaxRetrace» en 0,50 para el tramo AB, «MinExt1» en 1,13 y «MaxExt1» en 1,618 para el tramo BC, «MinExt2» en 1,618 y «MaxExt2» en 2,24 para el tramo CD, «LotSize» en 0,01, «AllowTrading» en «true», «TakeProfitLevel» en «TP2», «StopLossType» en «SL_FIBO», «SL_FiboExtension» en 1,618 y «SL_FixedPoints» en 50.

A continuación, definimos la estructura «Pivot» con los campos «time», «price» e «isHigh» para almacenar los puntos de oscilación, declaramos «pivots» como un array dinámico e inicializamos las variables globales «g_patternFormationBar» en -1 para realizar un seguimiento de la formación del patrón y «g_lockedPatternX» en 0 para fijar la marca temporal del punto pivote X, y declaramos «tradedPatterns» como un array para llevar un registro de los patrones ya operados mediante la marca temporal de X. Esta configuración proporciona la estructura básica para detectar patrones Shark y operar con ellos. Para la visualización, podemos utilizar funciones que permitan 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 representar con claridad en el gráfico el patrón Shark armónico 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»). Establecemos la propiedad OBJPROP_COLOR, en el color especificado, «OBJPROP_STYLE» en «STYLE_SOLID», «OBJPROP_WIDTH» en el grosor indicado, «OBJPROP_FILL» para activar o desactivar el relleno y «OBJPROP_BACK» para situarlo en segundo o primer plano, utilizando ObjectSetInteger. Después creamos la función «DrawTrendLine», que traza una línea de tendencia («OBJ_TREND») entre dos puntos.

A continuación, implementamos la función «DrawDottedLine», que crea una línea punteada horizontal (OBJ_TREND) a un precio determinado, desde «t1» hasta «t2». Por último, desarrollamos la función «DrawTextEx», que crea una etiqueta de texto (OBJ_TEXT) en las coordenadas («t», «p») usando «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 usar más adelante para el reconocimiento de patrones. Esta es la lógica que usamos para lograrlo.

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

Aquí implementamos la lógica inicial de la función OnTick para detectar los puntos pivote de las oscilaciones, que son esenciales para identificar el patrón armónico Shark. En primer lugar, declaramos una variable estática «lastBarTime», inicializada en 0, para llevar un registro de la última barra procesada y comparamos su valor con «currentBarTime», obtenido mediante iTime con un desplazamiento de 1 para el símbolo y el periodo actuales. Si no ha cambiado, salimos de la función para evitar un procesamiento redundante; cuando se detecta una nueva barra, actualizamos «lastBarTime». A continuación, vaciamos el array «pivots» con ArrayResize para garantizar un análisis desde cero. Luego obtenemos el número total de barras con Bars, establecemos el rango de detección de puntos pivote asignando a «start» el valor de «PivotLeft» y a «end» el número total de barras menos «PivotRight», y recorremos las barras seleccionadas.

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 colindante 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 según «isPivotHigh», y el indicador «isHigh»; a continuación, la añadimos al array «pivots» utilizando ArrayResize y la almacenamos. Al imprimir la estructura «Pivot», obtenemos el siguiente conjunto de datos.

DATOS DE LOS PUNTOS PIVOTE ANALIZADOS

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

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

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

//--- Initialize a flag to indicate if a valid Shark pattern is found
bool patternFound = false;
//--- Initialize pattern type
string patternType = "";
//--- 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 retracement from A to B
      double retrace = A.price - B.price;
      //--- Verify retracement is within user-defined range
      if((retrace >= MinRetrace * diff) && (retrace <= MaxRetrace * diff)) {
         //--- Calculate the extension from B to C
         double extension1 = C.price - B.price;
         //--- Verify extension is within user-defined range of retrace
         if((extension1 >= MinExt1 * retrace) && (extension1 <= MaxExt1 * retrace)) {
            //--- Calculate the extension from C to D
            double extension2 = C.price - D.price;
            //--- Verify extension is within user-defined range of previous extension
            if((extension2 >= MinExt2 * extension1) && (extension2 <= MaxExt2 * extension1) && (D.price < B.price)) {
               patternFound = true;
               patternType = "Bullish";
            }
         }
      }
   }
}
//--- 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 retracement from A to B
      double retrace = B.price - A.price;
      //--- Verify retracement is within user-defined range
      if((retrace >= MinRetrace * diff) && (retrace <= MaxRetrace * diff)) {
         //--- Calculate the extension from B to C
         double extension1 = B.price - C.price;
         //--- Verify extension is within user-defined range of retrace
         if((extension1 >= MinExt1 * retrace) && (extension1 <= MaxExt1 * retrace)) {
            //--- Calculate the extension from C to D
            double extension2 = D.price - C.price;
            //--- Verify extension is within user-defined range of previous extension
            if((extension2 >= MinExt2 * extension1) && (extension2 <= MaxExt2 * extension1) && (D.price > B.price)) {
               patternFound = true;
               patternType = "Bearish";
            }
         }
      }
   }
}

En primer lugar, determinamos el número total de puntos pivote mediante «ArraySize(pivots)» y lo almacenamos en «pivotCount». Si se encuentran menos de 5, restablecemos «g_patternFormationBar» y «g_lockedPatternX» a -1 y 0, respectivamente, y salimos de la función, ya que el patrón Shark requiere los puntos X, A, B, C y D. Luego extraemos los últimos cinco puntos pivote del array «pivots» y les asignamos las denominaciones «X» (el más antiguo), «A», «B», «C» y «D» (el más reciente).

A continuación, para un patrón alcista (mínimo en X, máximo en A, mínimo en B, máximo en C y mínimo en D), calculamos la diferencia de precios del tramo XA («A.price - X.price») y nos aseguramos de que sea positiva. Verificamos que el retroceso AB («A.price - B.price») se encuentre entre «MinRetrace» (0,32) y «MaxRetrace» (0,50) de XA, comprobamos que la extensión BC («C.price - B.price») se encuentre entre «MinExt1» (1,13) y «MaxExt1» (1,618) de AB, y confirmamos que la extensión CD («C.price - D.price») se encuentre entre «MinExt2» (1,618) y «MaxExt2» (2,24) de BC y que se cumpla «D.price < B.price». Si se cumplen estas condiciones, establecemos «patternFound» en «true» y «patternType» en «Bullish». Por último, para un patrón bajista (máximo en X, mínimo en A, máximo en B, mínimo en C y máximo en D), aplicamos comprobaciones similares para XA («X.price - A.price»), el retroceso AB («B.price - A.price»), la extensión BC («B.price - C.price») y la extensión CD («D.price - C.price»), con la condición «D.price > B.price». Si se cumplen estas condiciones, establecemos «patternFound» en «true» y «patternType» en «Bearish». Si se encuentra el patrón, podemos proceder a visualizarlo en el gráfico.

//--- If a valid Shark pattern is detected
if(patternFound) {
   //--- Print a message indicating the pattern type and detection time
   Print(patternType, " Shark 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 = "SH_" + 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);
}

Aquí comenzamos a visualizar los patrones detectados en el gráfico. En primer lugar, si se detecta un patrón válido («patternFound» es verdadero), registramos la detección con Print, mostrando el «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 «SH_» con «X.time», convertido a cadena mediante IntegerToString, para garantizar que los objetos del gráfico tengan nombres distintos.

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 XAB que une los puntos pivote X, A y B, y después para dibujar el triángulo BCD que une los puntos pivote B, C y D, utilizando «signalPrefix» con los sufijos «_Triangle1» y «_Triangle2», los tiempos y precios de los pivotes respectivos, «triangleColor», un ancho de 2 y los indicadores de relleno y visualización en segundo plano establecidos en true. Obtenemos el siguiente resultado.

CONJUNTO DE TRIÁNGULOS

En la imagen se puede observar que podemos representar y visualizar correctamente el patrón detectado. Ahora tenemos que seguir trazando las líneas de tendencia para delimitar por completo el patrón y añadirle una etiqueta que permita identificar más fácilmente los niveles.

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

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

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

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

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

Mejoraremos aún más la visualización de los patrones detectados añadiendo elementos gráficos detallados que representan con claridad la estructura de dichos patrones. En primer lugar, trazamos seis líneas de tendencia continuas mediante «DrawTrendLine» con el identificador único «signalPrefix» para conectar los puntos pivote clave mediante los tramos XA, AB, BC, CD, XB y BD, utilizando los tiempos y los precios de los pivotes (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. Después obtenemos el tamaño del punto del símbolo con «SymbolInfoDouble(_Symbol, SYMBOL_POINT)» y calculamos un desplazamiento de 15 puntos para posicionar las etiquetas; determinamos las coordenadas Y («textY_X», «textY_A», «textY_B», «textY_C», «textY_D») sumando o restando el desplazamiento en función de si cada pivote es un máximo de oscilación («isHigh» es verdadero) o un mínimo de oscilación, para colocar las etiquetas por encima de los máximos o por debajo de los mínimos.

Acto seguido, usamos «DrawTextEx» para crear etiquetas de texto para los pivotes X, A, B, C y D con «signalPrefix» y sufijos como «_Text_X», mostrando la letra correspondiente, situadas en el tiempo del pivote y con la coordenada Y ajustada, utilizando «clrBlack», tamaño de fuente 11 y el estado «isHigh» del pivote para el anclaje. Por último, calculamos la posición de la etiqueta central: «centralTime» como el punto medio entre «X.time» y «B.time», y «centralPrice» con el valor de «D.price»; creamos con «ObjectCreate» un objeto de texto denominado «signalPrefix + '_Text_Center'», establecemos OBJPROP_TEXT en «Bullish Shark» o «Bearish Shark» en función de «patternType» y configuramos «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». Al ejecutar el programa, obtenemos la siguiente visualización.

PATRÓN CON ETIQUETAS Y CONTORNOS

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

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

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

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

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

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

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

Para definir y visualizar los niveles de operación del patrón detectado, establecemos «lineStart» en el tiempo del pivote D («D.time») y «lineEnd» dos periodos después mediante «PeriodSeconds(_Period) * 2», y declaramos las variables «entryPriceLevel», «TP1Level», «TP2Level», «TP3Level» y «tradeDiff» para los cálculos de las operaciones. A continuación, para un patrón alcista («patternType == 'Bullish'»), establecemos «entryPriceLevel» en el precio ask (precio de compra para el trader) actual mediante la función SymbolInfoDouble y «TP3Level» en el precio del pivote C; calculamos «tradeDiff» como «TP3Level - entryPriceLevel» y obtenemos «TP1Level» y «TP2Level» sumando a «entryPriceLevel» un tercio y dos tercios de «tradeDiff», respectivamente. Para un patrón bajista, utilizamos el precio bid (precio de venta para el trader), fijamos «TP3Level» en el precio de C, calculamos «tradeDiff» como «entryPriceLevel - TP3Level» y obtenemos «TP1Level» y «TP2Level» restando de «entryPriceLevel» un tercio y dos tercios de «tradeDiff», respectivamente.

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 la función 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, un tamaño de fuente de 11 y el anclaje 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 se puede observar que hemos representado correctamente los niveles de operación. Lo único que nos queda por hacer ahora es abrir las posiciones de trading.

//--- Retrieve the index of the current bar
int currentBarIndex = Bars(_Symbol, _Period) - 1;
//--- If no pattern has been previously locked, lock the current pattern formation
if(g_patternFormationBar == -1) {
   g_patternFormationBar = currentBarIndex;
   g_lockedPatternX = X.time;
   //--- Print a message that the pattern is detected and waiting for confirmation
   Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar.");
   return;
}
//--- If still on the same formation bar, the pattern is considered to be repainting
if(currentBarIndex == g_patternFormationBar) {
   Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No trade yet.");
   return;
}
//--- If we are on a new bar compared to the locked formation
if(currentBarIndex > g_patternFormationBar) {
   //--- Check if the locked pattern still corresponds to the same X pivot
   if(g_lockedPatternX == X.time) {
      Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening trade on bar ", currentBarIndex, ".");
      //--- Update the pattern formation bar to the current bar
      g_patternFormationBar = currentBarIndex;
      //--- Only proceed with trading if allowed and if there is no existing position
      if(AllowTrading && !PositionSelect(_Symbol)) {
         //--- Check if this pattern has already been traded
         bool alreadyTraded = false;
         for(int k = 0; k < ArraySize(tradedPatterns); k++) {
            if(tradedPatterns[k] == X.time) {
               alreadyTraded = true;
               break;
            }
         }
         if(alreadyTraded) {
            Print("This pattern has already been traded. No new trade executed.");
            return;
         }
         double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0;
         point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
         bool tradeResult = false;
         //--- Select TP level based on user input
         switch(TakeProfitLevel) {
            case TP1:
               takeProfit = TP1Level;
               break;
            case TP2:
               takeProfit = TP2Level;
               break;
            case TP3:
               takeProfit = TP3Level;
               break;
            default:
               takeProfit = TP2Level; // Fallback to TP2
         }
         //--- Calculate SL based on user-selected method
         if(patternType=="Bullish") { //--- BUY signal
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            if(StopLossType == SL_FIBO) {
               double second_drive = C.price - D.price;
               stopLoss = D.price - (SL_FiboExtension - 1.0) * second_drive;
            } else { // SL_FIXED
               stopLoss = entryPriceTrade - SL_FixedPoints * point;
            }
            // Ensure SL is below entry for BUY
            if(stopLoss >= entryPriceTrade) {
               stopLoss = entryPriceTrade - 10 * point;
            }
            tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Shark 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);
            if(StopLossType == SL_FIBO) {
               double second_drive = D.price - C.price;
               stopLoss = D.price + (SL_FiboExtension - 1.0) * second_drive;
            } else { // SL_FIXED
               stopLoss = entryPriceTrade + SL_FixedPoints * point;
            }
            // Ensure SL is above entry for SELL
            if(stopLoss <= entryPriceTrade) {
               stopLoss = entryPriceTrade + 10 * point;
            }
            tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Shark Signal");
            if(tradeResult)
               Print("Sell order opened successfully.");
            else
               Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription());
         }
         //--- If trade was successful, mark the pattern as traded
         if(tradeResult) {
            int size = ArraySize(tradedPatterns);
            ArrayResize(tradedPatterns, size + 1);
            tradedPatterns[size] = X.time;
         }
      }
      else {
         //--- If a position is already open, do not execute a new trade
         Print("A position is already open for ", _Symbol, ". No new trade executed.");
      }
   }
   else {
      //--- If the pattern has changed, update the lock with the new formation bar and X pivot
      g_patternFormationBar = currentBarIndex;
      g_lockedPatternX = X.time;
      Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation.");
      return;
   }

}
else {
   //--- If no valid Shark pattern is detected, reset the pattern lock variables
   g_patternFormationBar = -1;
   g_lockedPatternX = 0;
}

Aquí finalizamos la implementación del controlador de eventos OnTick gestionando la ejecución de la operación y la confirmación del patrón detectado. 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»), establecemos «g_patternFormationBar» en «currentBarIndex», fijamos el tiempo del pivote X en «g_lockedPatternX» con «X.time», registramos la detección con Print indicando que se espera una confirmación y salimos.

Después, si aún estamos en la barra de formación («currentBarIndex == g_patternFormationBar»), registramos el repintado y salimos para evitar operar prematuramente. Por último, si se ha formado una nueva barra («currentBarIndex > g_patternFormationBar») y el pivote X coincide con «g_lockedPatternX», confirmamos el patrón, actualizamos «g_patternFormationBar» y comprobamos si se permite operar y si no hay posiciones abiertas mediante la función PositionSelect; verificamos que no se haya operado ya con el patrón comprobando «tradedPatterns», seleccionamos el nivel de take profit («TP1Level», «TP2Level» o «TP3Level») según su selección, calculamos de nuevo el stop loss según la selección del usuario, nos aseguramos de que el stop loss sea válido (por debajo del precio de entrada para la compra y por encima para la venta, con un ajuste de 10 puntos si es necesario), ejecutamos una compra o una venta con «obj_Trade.Buy» o «obj_Trade.Sell» utilizando «LotSize» y «Shark Signal», registramos el éxito o el fracaso y marcamos el patrón en «tradedPatterns» para indicar que ya se ha operado con él; si no se permite operar, existe una posición o ya se ha operado con el patrón, registramos que no se realiza ninguna operación; si el patrón cambia, actualizamos el bloqueo y esperamos; si no se encuentra ningún patrón, restablecemos las variables globales. Tras la compilación, obtenemos el siguiente resultado.

Señal bajista:

SEÑAL BAJISTA CONFIRMADA

Señal alcista:

SEÑAL ALCISTA CONFIRMADA

En la imagen podemos ver que trazamos el patrón armónico y que, una vez confirmado, seguimos pudiendo negociar en consecuencia, logrando así nuestro objetivo de identificar, trazar y operar con el patrón. Solo queda realizar pruebas con datos históricos del programa, lo que se aborda en la siguiente sección.


Backtesting

Tras realizar pruebas exhaustivas con datos históricos, hemos obtenido los siguientes resultados.

Gráfico de las pruebas con datos históricos:

GRÁFICO

Informe de las pruebas con datos históricos:

INFORME


Conclusión

En conclusión, hemos desarrollado un sistema basado en el patrón Shark en MQL5 que aprovecha la acción del precio para detectar patrones armónicos Shark alcistas y bajistas mediante ratios de Fibonacci, automatiza las operaciones con puntos de entrada, stop loss y take profit multinivel personalizables, y visualiza los patrones mediante objetos gráficos como triángulos y líneas de tendencia.

Aviso legal: el presente artículo tiene fines únicamente 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, usted puede adaptar este sistema basado en el patrón Shark a su estilo de trading y mejorar así sus estrategias algorítmicas. ¡Le deseo un buen trading!

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

Archivos adjuntos |
Crypto Exchange Integration with MQL5 (Part 1): Connecting to MEXC Futures Crypto Exchange Integration with MQL5 (Part 1): Connecting to MEXC Futures
This article introduces the foundations of connecting MetaTrader 5 to a cryptocurrency exchange using MQL5 and the MEXC Futures API. It covers terminal configuration, JSON response processing, WebRequest() execution and validation, and conversion of API data into native MQL5 types. Practical examples demonstrate retrieving and processing exchange data. These components establish the basis for a reusable MEXC Futures client, developed progressively throughout the series. The series focuses exclusively on exchange integration; how readers use the retrieved data remains outside its scope.
Redes neuronales en el trading: De los Transformer a las neuronas de impulsos (SpikingBrain) Redes neuronales en el trading: De los Transformer a las neuronas de impulsos (SpikingBrain)
El framework SpikingBrain presenta un enfoque único para el procesamiento de datos: las neuronas solo responden a eventos significativos, lo que permite filtrar eficazmente el ruido. Su arquitectura basada en eventos reduce los costes computacionales al tiempo que conserva la información clave sobre la evolución de los precios. Los umbrales adaptativos y la posibilidad de utilizar módulos preentrenados garantizan la flexibilidad y la escalabilidad del modelo.
Herramientas de trading de MQL5 (Parte 9): Desarrollo de un asistente de configuración inicial del usuario para asesores expertos (EAs) con una guía desplazable Herramientas de trading de MQL5 (Parte 9): Desarrollo de un asistente de configuración inicial del usuario para asesores expertos (EAs) con una guía desplazable
En este artículo, desarrollamos en MQL5 un asistente de configuración inicial del usuario para asesores expertos (EAs) que incluye una guía desplazable con un panel de control interactivo, formato de texto dinámico y controles visuales, como botones y una casilla de verificación, que permiten a los usuarios recorrer las instrucciones y configurar los parámetros de negociación de forma eficaz. Los usuarios del programa pueden hacerse una idea de en qué consiste y qué deben hacer la primera vez que lo ejecutan; es más bien una especie de guía de orientación.
Características del Wizard MQL5 que debe conocer (Parte 80): Uso de patrones Ichimoku y ADX-Wilder con aprendizaje por refuerzo TD3 Características del Wizard MQL5 que debe conocer (Parte 80): Uso de patrones Ichimoku y ADX-Wilder con aprendizaje por refuerzo TD3
Este artículo da continuidad a la "Parte 74", en la que examinamos la combinación de Ichimoku y el ADX en un marco de aprendizaje supervisado, y centra ahora la atención en el aprendizaje por refuerzo. Ichimoku y ADX forman una combinación complementaria para identificar los niveles de soporte y resistencia y determinar la fuerza de la tendencia. En esta entrega, analizamos cómo se puede utilizar el algoritmo Twin Delayed Deep Deterministic Policy Gradient (TD3) con este conjunto de indicadores. Al igual que en las partes anteriores de la serie, la implementación se realiza en una clase de señales personalizada diseñada para integrarse con el Asistente de MQL5, lo que facilita la creación de asesores expertos sin complicaciones.