English Русский 中文 Deutsch 日本語 Português
preview
Cómo detectar tendencias y patrones de gráficos usando MQL5

Cómo detectar tendencias y patrones de gráficos usando MQL5

MetaTrader 5Trading | 4 octubre 2023, 14:43
1 276 2
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introducción

Como tráders, todos tratamos con gráficos e intentamos leerlos correctamente para poder realizar los pronósticos correctos y tomar las decisiones necesarias. Existen muchos patrones que se pueden identificar en el gráfico y que predicen posibles movimientos de precios. Algunas herramientas útiles podrían facilitar nuestra tarea. El artículo analizará la detección de los patrones de precio más importantes.

Hoy trataremos los siguientes temas: 

Después de leer este artículo, podrá identificar máximos y mínimos, tipos de tendencias, picos dobles techos y valles dobles. Escriba usted mismo los códigos mencionados y pruebe y desarrolle lo que necesita para obtener mejores resultados antes de utilizarlos en una cuenta real. El objetivo principal del artículo es comprender la idea básica en la detección de máximos, mínimos y patrones de gráficos para mejorar su código.

En este artículo usaremos el entorno de desarrollo MQL5 integrado en el terminal comercial MetaTrader 5. Si no sabe cómo usar MQL5 y cómo descargar y usar el MetaEditor, le recomiendo que lea la sección "Escribiendo el código MQL5 en el MetaEditor" en mi artículo anterior.

¡Atención! Toda la información del presente artículo se ofrece «tal cual», únicamente con fines ilustrativos, y no supone ningún tipo de recomendación. El artículo no garantiza ningún resultado en absoluto. Todo lo que ponga en práctica usando este artículo como base, lo hará bajo su propia cuenta y riesgo; el autor no garantiza resultado alguno.

Detección de máximos y mínimos

En esta parte, comenzaremos definiendo los máximos y mínimos en un gráfico utilizando MQL5 y luego usaremos esta definición como base para declarar nuestras condiciones según cada patrón del gráfico.

Máximos:

La formación de un máximo significará que se ha dado un movimiento ascendente hasta un cierto nivel debido a la fuerza de los compradores, y luego han aparecido los vendedores, haciendo bajar el precio. A continuación se muestra un ejemplo.   

Máximo

Mínimos:

La formación de un máximo significará que se ha dado un movimiento descendente hasta un cierto nivel debido a la fuerza de los vendedores, y luego han aparecido los compradores, haciendo subir el precio. A continuación se muestra un ejemplo.

Mínimo

Ahora necesitamos crear un programa o asesor MQL5 que pueda detectar este tipo de movimientos: se puede crear de diferentes maneras. Presentaremos una de ellas.

Nuestra tarea consistirá en identificar niveles de precios específicos (máximo y mínimo), luego pasar a otros niveles de precios específicos (máximo y mínimo potencial) y comparar el máximo y el mínimo anteriores con el actual para determinar si tenemos otro máximo o mínimo. Para ello deberemos seguir ciertos pasos.

Comenzaremos creando una función desde el ámbito de OnTick() para retornar el máximo o el mínimo. La llamaremos (getNextMove) como una variable entera. A continuación se mostrarán los parámetros que deberemos configurar para dicha función:

  • Int move - máximo/mínimo.
  • int count - valor de startPos.
  • int startPos - posición inicial.
int getNextMove(int move, int count, int startPos)

Dentro de esta función, deberemos realizar la siguiente verificación usando una declaración if para determinar el valor de los parámetros de la función. Tendremos que verificar si (startPos) es menor que cero, añadir el valor de startPos al valor del contador y actualizar startPos con un valor cero para comenzar desde la barra actual.

   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }

Bien, hemos definido las variables (count) y (startPos) en la función. La variable (move) se identificará en el valor retornado usando un operador de retorno que finalizará la función y retornará el valor move. El operador ternario (?:) consta de tres expresiones, la primera de las cuales retornará datos de tipo bool. Si es true, se ejecutará la segunda expresión, y si es false, se ejecutará la tercera expresión.

Luego indicaremos si la variable move es igual al máximo en la primera declaración, y si es true, se retornará el máximo (High), mientras que si es false, se retornará el mínimo (Low).

Para verificar si el movimiento es máximo, usaremos las funciones MODE_HIGH, uno de los identificadores de series temporales usados en las funciones iHighest() e iLowest() para retornar el precio máximo. Parámetros para las funciones iHighest e iLowest para retornar el índice del valor más alto y más bajo:

  • symbol - símbolo actual como una línea const.
  • timeframe - use Period() para retornar el marco temporal actual como ENUM_TIMEFRAMES.
  • type - use (ENUM_SERIESMODE) para retornar el tipo de movimiento como identificador de serie temporal. Este tipo es el máximo para iHighest y el mínimo para iLowest.
  • count - utilice la variable entera (count) para retornar el número de elementos.
  • start - utilice la variable entera (startPos) para devolver el índice.
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));

Después de crear la función que retornará el siguiente movimiento, crearemos otra función entera que será la principal para obtener el máximo o mínimo del movimiento actual. La llamaremos (getmove) y le proporcionemos tres variables enteras como parámetros (move, count y startPos)

int getmove(int move, int count, int startPos)

Dentro de la función necesitaremos verificar si el movimiento es MODE_HIGH o MODE_LOW, el valor retornado será (-1).

if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);

Luego crearemos una nueva variable entera (currentBar) y se la asignaremos (startPos).

int currentBar=startPos;

Asimismo, crearemos una nueva variable entera (moveReturned) y se la asignaremos a la función getNextMove creada con los siguientes parámetros (move, (count*2+1), currentBar-count)).

int moveReturned=getNextMove(move,count*2+1,currentBar-count);

Después crearemos un ciclo usando While porque necesitamos comprobar la expresión. Si es true, la declaración se ejecutará. Aquí en la expresión comprobaremos si moveReturned es igual al valor de currentBar. Si es true, deberemos ejecutar las siguientes declaraciones:

  • Actualizaremos la variable (currentBar) usando getNextMove con los parámetros (move, count,currentBar+1).
  • A continuación, actualizaremos la variable (moveReturned) usando getNextMove con los parámetros (move,count*2+1,currentBar-count).
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }

Luego usaremos la función de retorno para completar la función retornando el valor de currentBar,

return(currentBar);

y entraremos en OnTick() y llamaremos a aquello que nos haya ayudado a detectar los máximos y los mínimos. Primero crearemos tres variables de tipo entero.

   int checkBars= 5; 
   int move1;
   int move2;

Acto seguido, actualizaremos move1 y move2 usando nuestra función getmove creada previamente con los parámetros (MODE_HIGH,checkBars,0) para move1 y (MODE_HIGH,checkBars,move1+1) para move2 para detectar los dos máximos.

   move1=getmove(MODE_HIGH,checkBars,0);
   move2=getmove(MODE_HIGH,checkBars,move1+1);

Crearemos una línea por encima de estos dos máximos siguiendo estos pasos:

Eliminaremos cualquier línea existente usando (ObjectDelete), que elimina el objeto con el nombre. Existen parámetros para esta función: el primero es chart_id para definir el identificador del gráfico y usaremos 0 para el gráfico actual. El segundo parámetro será el nombre del objeto: usaremos topLine como línea.

ObjectDelete(0,"topLine");

A continuación, crearemos un nuevo objeto topLine usando la función ObjectCreate. Parámetros:

  • chart_id - utiliza (0) para retornar el tipo long como identificador del gráfico.
  • name - utiliza topLine para retornar el tipo string como nombre del objeto.
  • type - utiliza OBJ_TREND para retornar el tipo u objeto ENUM_OBJECT.
  • nwin - utiliza (0) para el gráfico actual como índice de la ventana.
  • time1 - determina la hora del punto de anclaje move2 y retorna el tipo de datetime. Usaremos iTime(Symbol(),Period(),move2)
  • price1 - determina el precio del punto de anclaje move2 y retorna el tipo double. Usaremos iHigh(Symbol(),Period(),move2).
  • timeN=0 - determina la fecha y la hora del punto de anclaje move1 y retorna el tipo de datetime. Usaremos iTime(Symbol(),Period(),move1).
  • precioN=0 - determina el precio del punto de anclaje move1 y retorna el tipo double. Usaremos iHigh(Symbol(),Period(),move1).

Como podemos ver, la función iHigh retorna el precio máximo de la barra y sus parámetros son el símbolo, el marco temporal y el desplazamiento. La función iTime retorna la hora de apertura de la barra y sus parámetros son los mismos que los de la función iHigh.

ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iHigh(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iHigh(Symbol(),Period(),move1));

Ahora estableceremos el color, la anchura específica y el tipo de línea para el objeto creado usando la función ObjectSetInteger. Los parámetros serán los siguientes:

  • chart_id - ID del gráfico (0).
  • name - nombre del objeto (TopLine para los máximos).
  • prop_id - propiedad del objeto (OBJPROP_COLOR - color, OBJPROP_WIDTH - anchura y OBJPROP_RAY_RIGHT - tipo de línea).
  • prop_value - valor requerido (color - clrRed, anchura - 3, tipo de línea - true).
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);

Después obtendremos dos mínimos actualizando las variables move1 y move2 de la misma forma que lo hicimos para los máximos, pero el identificador de la serie temporal se establecerá en MODE_LOW.

   move1=getmove(MODE_LOW,checkBars,0);
   move2=getmove(MODE_LOW,checkBars,move1+1);

La eliminación y creación de un objeto de línea por debajo de estos dos mínimos se realizará de la misma forma que con los máximos, pero el nombre del objeto será bottomLine y el color será verde.

   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iLow(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iLow(Symbol(),Period(),move1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);

A continuación le mostramos el código completo en un bloque:

//+------------------------------------------------------------------+
//|                                                   moveFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5; 
   int move1;
   int move2;
   move1=getmove(MODE_HIGH,checkBars,0);
   move2=getmove(MODE_HIGH,checkBars,move1+1);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iHigh(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iHigh(Symbol(),Period(),move1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   move1=getmove(MODE_LOW,checkBars,0);
   move2=getmove(MODE_LOW,checkBars,move1+1);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iLow(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iLow(Symbol(),Period(),move1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

Tras compilar el código y ejecutarlo con éxito, obtendremos dos líneas en el gráfico para detectar los dos máximos con una línea roja encima y dos mínimos y una línea verde debajo de ellos. Aquí tenemos unos ejemplos:

moveFinder signal1

Como podemos ver, estas dos líneas pueden mostrar un patrón en el gráfico y también indicar un movimiento de precios. En el ejemplo anterior, se ve que tenemos un movimiento brusco hacia arriba porque hay dos líneas ascendentes y el ángulo de la superior es más amplio que el de la inferior. De esta forma, pueden suponer una herramienta muy útil para interpretar la acción del precio.

A continuación le mostramos otro ejemplo con un patrón distinto basado en diferentes acciones del precio:

 moveFinder signal2

Aquí tenemos una acción del precio diferente. Como vemos, hay dos líneas que se mueven en paralelo, pero la línea inferior sube y la línea superior baja, lo cual indica que hay un equilibrio entre compradores y vendedores, ya que los compradores hacen subir el precio, mientras que los vendedores hacen bajar el precio al mismo tiempo.

A continuación le mostramos un ejemplo de otro modelo de acción del precio:

moveFinder signal3

Aquí tenemos dos líneas paralelas descendentes que pueden indicar la fuerza de los vendedores, ya que son capaces de hacer bajar el precio.

Tras aprender en la parte anterior cómo podemos detectar máximos y mínimos en un gráfico, podemos desarrollar el código necesario para detectar tendencias en un gráfico. Asimismo, hemos aprendido a detectar los dos máximos y los dos mínimos. Esto es lo que necesitaremos para determinar la tendencia. Ahora mejoraremos nuestro el código anterior para detectar tendencias en el gráfico tanto como sea posible.

En términos simples, las tendencias representan el movimiento de los precios, y este movimiento puede ser ascendente, descendente o sin una dirección clara. Vamos a analizar los tres tipos de movimiento:

Tendencia alcista:

En este caso, la fuerza dominante del mercado será la fuerza de los compradores. En el gráfico vemos que el precio claramente alcanza mínimos y máximos cada vez más altos. La siguiente figura muestra un gráfico de tendencia alcista:

Tendencia alcista

Tendencia bajista:

En este caso, los vendedores serán la fuerza dominante en el mercado, haciendo bajar los precios. En el gráfico vemos que el precio alcanza máximos y mínimos más bajos.

Ejemplo de tendencia bajista:

Tendencia bajista

Movimiento lateral:

En este caso no existe una dirección definida. El movimiento lateral tiene multitud de formas. A continuación veremos algunas de ellas:

Ausencia de tendencia  Ausencia de tendencia 2  Ausencia de tendencia 3

Ahora necesitaremos crear un asesor MQL5 que pueda determinar si tenemos una tendencia (alcista o bajista) o no (movimiento lateral). El siguiente código está pensado para crear dicho asesor:

//+------------------------------------------------------------------+
//|                                                  trendFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5;
   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;
   high1=getmove(MODE_HIGH,checkBars,0);
   high2=getmove(MODE_HIGH,checkBars,high1+1);
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   low1=getmove(MODE_LOW,checkBars,0);
   low2=getmove(MODE_LOW,checkBars,low1+1);
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
   if(lowVal1>lowVal2&&highVal1>highVal2)
     {
      Comment("Uptrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }
   else
      if(highVal1<highVal2&&lowVal1<lowVal2)
        {
         Comment("Downtrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
      else
        {
         Comment("Sideways",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

A continuación le mostramos las diferencias en este código para identificar tendencias.

Vamos a crear cuatro variables enteras para los dos máximos y los dos mínimos y cuatro variables dobles más para los dos máximos y los dos mínimos dentro del ámbito de la función OnTick:

   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;

Ahora actualizaremos dos valores máximos (highVal1, highVal2) usando la función NormalizeDouble para redondear el resultado de los valores máximos. Sus parámetros serán:

  • El valor será el número que necesitamos normalizar. Usaremos la función iHigh para retornar el precio máximo, y sus parámetros serán el símbolo actual (_Symbol), el marco temporal actual (_Period) y el desplazamiento del índice (high1 y high2).
  • digits - número de dígitos decimales (5).
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);

Vamos a actualizar dos valores mínimos (lowVal1, lowVal2) usando la función NormalizeDouble con los mismos parámetros que mencionamos anteriormente, con las siguientes diferencias:

  • value - utiliza la función iLow para retornar el precio mínimo. Sus parámetros son los mismos, salvo el cambio de índice, que será low1y low.
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);

Condiciones que deberemos establecer para determinar las tendencias: usaremos una declaración if. Necesitaremos que el asesor verifique constantemente los cuatro máximos y mínimos, luego determine sus posiciones relacionadas entre sí y luego decida si tenemos una tendencia (alcista o bajista) o no (movimiento lateral).

Condiciones de tendencia alcista:

Si lowVal1 es mayor que lowVal2 y al mismo tiempo highVal1 es mayor que highVal2, tendremos una tendencia alcista y necesitaremos que el asesor retorne un comentario en el gráfico con el siguiente contenido:

  • Uptrend (tendencia alcista)
  • Current High (máximo actual)
  • Previous High (máximo anterior)
  • Current Low (mínimo actual)
  • Previous Low (mínimo anterior)
   if(lowVal1>lowVal2&&highVal1>highVal2)
     {
      Comment("Uptrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

Condiciones de tendencia bajista:

Si highVal1 está por debajo de highVal2 y al mismo tiempo lowVal1 está por debajo de lowVal2, tendremos una tendencia bajista y necesitaremos que el asesor retorne un comentario al gráfico con el siguiente contenido:

  • Downtrend (tendencia bajista)
  • Current High (máximo actual)
  • Previous High (máximo anterior)
  • Current Low (mínimo actual)
  • Previous Low (mínimo anterior)
   else
      if(highVal1<highVal2&&lowVal1<lowVal2)
        {
         Comment("Downtrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

Condiciones de movimiento lateral:

Si las posiciones de los cuatro valores representan algo más que las condiciones de tendencia alcista y bajista, tendremos una tendencia lateral y necesitaremos que el asesor retorne lo siguiente como comentario en el gráfico:

      else
        {
         Comment("Sideways",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

Al compilar el código y ejecutar el asesor, recibiremos las señales de tendencia necesarias. A continuación le mostramos ejemplos de pruebas por tipo de tendencia y condiciones.

Tendencia alcista:

TrendFinder - señal de tendencia alcista

En la figura podemos ver un ejemplo de una tendencia alcista, ya que tenemos un mínimo y un máximo más altos del precio. En la esquina superior izquierda del gráfico vemos una señal de tendencia alcista.

Tendencia bajista:

TrendFinder - señal de tendencia bajista

En la figura podemos ver un ejemplo de una tendencia bajista, ya que tenemos un máximo y mínimo más bajos del precio. Como comentario recibiremos una señal de tendencia bajista.

Movimiento lateral:

TrendFinder - señal de movimiento lateral

Tenemos un máximo más bajo y un mínimo más alto, y esto indica movimiento lateral. Como comentario recibiremos una señal de movimiento lateral.

Detección de picos dobles

Ahora ha llegado el momento de mejorar nuestro código. Vamos a obligarlo a detectar ciertos patrones de precio que puedan indicar un movimiento potencial.

En esta sección ofreceremos ejemplos de patrones gráficos con modificaciones menores en el código: así comprenderemos la idea básica y valoraremos el potencial de descubrir otros patrones importantes. Asimismo, analizaremos uno de los patrones gráficos más populares: el pico doble.

Se trata de un patrón que consta de máximos aproximadamente iguales, lo cual indica la debilidad de los compradores y la probabilidad de que los precios bajen. A continuación le mostramos un ejemplo:

Pico doble potencial

Ante nosotros tenemos un pico doble potencial. Se formará definitivamente si el precio cae por debajo del mínimo entre los dos máximos, como se muestra en la siguiente figura:

Pico doble

Ahora necesitaremos crear un asesor experto MQL5 que pueda usarse para detectar estos dos patrones en MetaTrader 5. Necesitaremos que el asesor verifique constantemente los dos máximos y los dos mínimos y determine sus posiciones entre sí, y luego retorne un resultado específico basado en una determinada condición del patrón "pico doble". Obviamente, en condiciones reales, los patrones rara vez se muestran a la perfección. Si el máximo actual es menor o igual al anterior y al mismo tiempo el mínimo actual es mayor que el anterior, esto será una señal de un posible pico doble. Si el máximo actual es menor o igual al anterior y al mismo tiempo el mínimo actual es menor que el anterior, esto será una señal de pico doble.

A continuación le mostramos el código completo:

//+------------------------------------------------------------------+
//|                                             DT patternFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5;
   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;
   high1=getmove(MODE_HIGH,checkBars,0);
   high2=getmove(MODE_HIGH,checkBars,high1+1);
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   low1=getmove(MODE_LOW,checkBars,0);
   low2=getmove(MODE_LOW,checkBars,low1+1);
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
   if(highVal1<=highVal2&&lowVal1>lowVal2)
     {
      Comment("Potential Double Top",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

   else
      if(highVal1<=highVal2&&lowVal1<lowVal2)
        {
         Comment("Double Top",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
      else
         Comment(" ");
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

El código se distingue en cuanto a las condiciones del patrón.

En el caso de un pico doble potencial, si highVal1 es menor o igual que highVal2 y lowVal1 es mayor que lowVal2, entonces necesitaremos obtener una señal como comentario en el gráfico con los siguientes valores:

  • Potential Double Top (pico doble potencial)
  • Current High (máximo actual)
  • Previous High (máximo anterior)
  • Current Low (mínimo actual)
  • Previous Low (mínimo anterior)
   if(highVal1<=highVal2&&lowVal1>lowVal2)
     {
      Comment("Potential Double Top",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

En el caso de un pico doble formado, si highVal1 es menor o igual que highVal2 y lowVal1 es menor que lowVal2, entonces necesitaremos obtener una señal en forma de comentario en el gráfico con los siguientes valores:

  • Double Top (pico doble)
  • Current High (máximo actual)
  • Previous High (máximo anterior)
  • Current Low (mínimo actual)
  • Previous Low (mínimo anterior)
   else
      if(highVal1<=highVal2&&lowVal1<lowVal2)
        {
         Comment("Double Top",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

Si no hay patrones, no se mostrará ningún comentario.

      else
         Comment(" ");

Tras realizar la compilación, iniciaremos el asesor en el gráfico.

Pico doble potencial:

DT PatternFinder: pico doble potencial

Como podemos ver, se cumplen las condiciones para la activación de las señales: los máximos son los mismos, y un mínimo es ligeramente superior al otro.

Pico doble formado:

 DT PatternFinder - pico doble formado

Condiciones de activación cumplidas: máximo inferior o igual y mínimo inferior.

Detección de valles dobles

El valle doble es lo opuesto al pico doble. Este es un patrón que consta de mínimos aproximadamente iguales, lo cual indica la debilidad de los vendedores y la probabilidad de que los precios suban. A continuación le mostramos un ejemplo:

 Valle doble potencial

Un valle doble se confirma cuando los precios cierran por encima de un máximo entre los dos mínimos, como se muestra en la siguiente figura:

Valle doble

Ahora necesitaremos crear otro asesor experto MQL5 que pueda usarse para detectar los dos patrones anteriores en MetaTrader 5. Necesitaremos que el asesor verifique constantemente los dos mínimos y los dos máximos y determine sus posiciones entre sí, y luego retorne un resultado específico basado en una determinada condición del patrón de valle doble. Si el mínimo actual es mayor o igual que el anterior y al mismo tiempo el máximo actual es menor que el anterior, esto será una señal de un posible valle doble. Si el mínimo actual es mayor o igual al anterior y al mismo tiempo el máximo actual es mayor que el anterior, esto será una señal de valle doble.

A continuación le mostramos el código completo:

//+------------------------------------------------------------------+
//|                                             DB patternFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5;
   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;
   high1=getmove(MODE_HIGH,checkBars,0);
   high2=getmove(MODE_HIGH,checkBars,high1+1);
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   low1=getmove(MODE_LOW,checkBars,0);
   low2=getmove(MODE_LOW,checkBars,low1+1);
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
   if(lowVal1>=lowVal2&&highVal1<highVal2)
     {
      Comment("Potential Double Bottom",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }
   else
      if(lowVal1>=lowVal2&&highVal1>highVal2)
        {
         Comment("Double Bottom",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
      else
         Comment(" ");
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

El código se distingue en cuanto a las condiciones del patrón.

En el caso de un valle doble potencial, si lowVal1 es mayor o igual que lowVal2 y highVal1 está por debajo de highVal2, entonces necesitaremos obtener una señal como comentario en el gráfico con los siguientes valores:

  • Potential Double Bottom (valle doble potencial)
  • Current High (máximo actual)
  • Previous High (máximo anterior)
  • Current Low (mínimo actual)
  • Previous Low (mínimo anterior)
   if(lowVal1>=lowVal2&&highVal1<highVal2)
     {
      Comment("Potential Double Bottom",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

En el caso de un valle doble formado, si lowVal1 es mayor o igual que lowVal2 y highVal1 es mayor que highVal2, entonces necesitaremos obtener una señal en forma de comentario en el gráfico con los siguientes valores:

  • Double Bottom (valle doble)
  • Current High (máximo actual)
  • Previous High (máximo anterior)
  • Current Low (mínimo actual)
  • Previous Low (mínimo anterior)
   else
      if(lowVal1>=lowVal2&&highVal1>highVal2)
        {
         Comment("Double Bottom",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

Luego compilaremos el código del asesor y este aparecerá en la ventana del navegador en el terminal, desde donde lo iniciaremos en el gráfico. A continuación, le mostraremos un ejemplo de su funcionamiento:

Valle doble potencial:

 DB PatternFinder: valle doble potencial

Condiciones de activación cumplidas: máximo inferior y mínimo igual o superior.

Valle doble formado:

Valle doble

Condiciones de activación cumplidas: máximo superior y mínimo igual o superior.

Conclusión

Una comprensión profunda de la acción del precio permite a los tráders tomar mejores decisiones comerciales y de inversión. La acción del precio crea muchos patrones que debemos conocer. Intentaremos simplificar esta tarea creando asesores MQL5 para su uso en el terminal comercial MetaTrader 5.

Tras aprender a identificar los máximos y los mínimos, hemos aprendido a identificar las tendencias (ascendente, descendente y lateral) y uno de los patrones gráficos populares: el pico doble, además del valle doble, opuesto al anterior; así como a crear asesores basados ​​​​en ellos. Puede aprovechar las ideas de este artículo y desarrollar más sistemas que detecten otros patrones gráficos como cabeza y hombros, triángulos, rectángulos, etc. Espero que el presente artículo le resulte útil y le ayude a desarrollar sistemas comerciales y alcanzar sus objetivos.

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

Archivos adjuntos |
moveFinder.mq5 (2.24 KB)
trendFinder.mq5 (3.18 KB)
Aitor Esteban Yague
Aitor Esteban Yague | 4 oct. 2023 en 17:25
Muy buen aporte, felicitaciones, muchas gracias! 
Hilario Miguel Ofarril Gonzalez
Hilario Miguel Ofarril Gonzalez | 6 oct. 2023 en 07:51
Bastante claro y visible .igual a interesante 
Desarrollamos un indicador Heiken Ashi personalizado utilizando MQL5 Desarrollamos un indicador Heiken Ashi personalizado utilizando MQL5
En este artículo, aprenderemos cómo crear nuestro propio indicador usando MQL5 según nuestras preferencias. Dicho indicador se utilizará en MetaTrader 5 para interpretar gráficos o como parte de asesores expertos.
Previsión usando modelos ARIMA en MQL5 Previsión usando modelos ARIMA en MQL5
En este artículo, continuaremos el desarrollo de la clase CArima para construir modelos ARIMA añadiendo métodos de predicción intuitivos.
Desarrollamos el indicador True Strength Index personalizado utilizando MQL5 Desarrollamos el indicador True Strength Index personalizado utilizando MQL5
Les presento un nuevo artículo sobre la creación de indicadores personalizados. Esta vez trabajaremos con el True Strength Index (TSI) y crearemos un asesor basado en él.
Cómo convertirse en un proveedor de señales exitoso en MQL5.com Cómo convertirse en un proveedor de señales exitoso en MQL5.com
El objetivo principal de este artículo es mostrar un camino sencillo y pormenorizado para convertirse en el mejor proveedor de señales en MQL5.com. Basándome en mis conocimientos y experiencia, explicaré lo que cualquier proveedor de señales necesita para tener éxito, incluido cómo encontrar, probar y optimizar una buena estrategia. Además, le ofreceré consejos sobre cómo publicar su señal, escribir una descripción convincente y realizar una promoción y gestión efectivas.