English Русский Deutsch 日本語
preview
Introducción a MQL5 (Parte 21): Automatización de la detección de patrones armónicos

Introducción a MQL5 (Parte 21): Automatización de la detección de patrones armónicos

MetaTrader 5Trading |
29 0
ALGOYIN LTD
Israel Pelumi Abioye

Introducción

¡Bienvenidos de nuevo a la parte 21 de la serie «Introducción a MQL5»! En la parte 20, os hablé de los patrones armónicos y os expliqué los conceptos matemáticos que subyacen a los retrocesos y extensiones de Fibonacci, así como la forma de implementarlos en MQL5. En este artículo, daremos un paso más allá y nos centraremos en automatizar la detección del patrón armónico de Gartley.

Aprenderás a reconocer posibles patrones de Gartley de forma programática en tus gráficos utilizando los niveles de Fibonacci, las oscilaciones de precios y los objetos gráficos. La lógica y los métodos que analizamos aquí pueden aplicarse a otros patrones armónicos, como el Bat, el Butterfly, el Crab y el Deep Crab, aunque, en aras de la simplicidad, nos centraremos en el patrón Gartley.

Este artículo destaca por su estilo accesible para principiantes y la forma en que simplificamos los conceptos difíciles dividiéndolos en etapas más fáciles de asimilar. En lugar de abrumarte con teoría, trabajaremos con una metodología basada en proyectos en la que crearemos un Asesor Experto (EA) que reconozca automáticamente el patrón de Gartley.


Configuración del proyecto

El objetivo de este proyecto es crear un asesor experto (EA) capaz de reconocer automáticamente los patrones armónicos de Gartley en un gráfico. Basándose en criterios predeterminados, el EA evaluará las oscilaciones del precio, calculará los niveles importantes de retroceso y extensión de Fibonacci e identificará posibles formaciones de Gartley.

El patrón de Gartley se caracteriza por cuatro tramos principales (XA, AB, BC y CD) y unas relaciones específicas de Fibonacci: el punto B debe retroceder aproximadamente el 78,6 % del tramo XA; el punto C debe retroceder entre el 38,2 % y el 88,6 % del tramo AB; y el punto D debe retroceder aproximadamente el 78,6 % del tramo XA. El EA es flexible y fácil de usar para los principiantes, ya que permite a los operadores modificar los rangos de retroceso y extensión de Fibonacci para adaptarlos a su propio estilo de negociación.

Lógica para comprar

Para detectar un patrón alcista de Gartley, el EA seguirá estos pasos:

  • Identifique X como el punto más bajo del swing.
  • Detectar A como un máximo significativo después de X.
  • Localice B, que debe retroceder dentro del rango especificado del tramo XA.
  • Confirme C, que retrocede dentro del rango especificado del tramo AB.
  • Calcule el posible punto D, que debería estar dentro de un rango de retroceso especificado del tramo XA.
  • Asegúrese de que todos los tramos siga la alineación de Fibonacci requerida para una estructura Gartley válida.
  • Una vez confirmado el punto D, el EA generará una señal de compra, esperando que el precio revierta su tendencia al alza desde este nivel.
  • Además, el EA permitirá a los usuarios ajustar todos los porcentajes de retroceso y extensión de Fibonacci a través de la configuración de entrada, lo que proporciona flexibilidad en función de las diferentes condiciones del mercado.

Una vez que el EA confirma un patrón Gartley alcista válido:

  • Entrada: Se abre una operación de compra en el punto D, justo después de que se complete el patrón.
  • Stop Loss (SL): Se coloca en el mínimo del swing que formó el punto D.
  • Take Profit (TP): Establecido utilizando una relación riesgo-recompensa de 1:3, lo que significa que la distancia al TP será tres veces la distancia del SL.

Lógica para vender

Para detectar un patrón bajista de Gartley, el EA seguirá estos pasos:

  • Identifique X como el máximo de oscilación.
  • Detectar A como un mínimo significativo después de X.
  • Localice B, que debe retroceder dentro del rango especificado del tramo XA.
  • Confirme C, que retrocede dentro del rango especificado del tramo AB.
  • Calcule el posible punto D, que debería estar dentro de un rango del segmento XA.
  • Asegúrese de que todos los tramos siga la alineación de Fibonacci requerida para una estructura Gartley válida.
  • Una vez confirmado el punto D, el EA generará una señal de venta, esperando que el precio gire a la baja desde ese nivel.
  • Además, el EA permitirá a los usuarios ajustar todos los porcentajes de retroceso y extensión de Fibonacci a través de la configuración de entrada, lo que proporciona flexibilidad en función de las diferentes condiciones del mercado.

Una vez que el EA confirma un patrón bajista válido de Gartley:

  • Entrada: Se abre una operación de venta en el punto D, justo después de que se complete el patrón.
  • Stop Loss (SL): Se coloca en el máximo del swing que formó el punto D.
  • Take Profit (TP): Establecido utilizando una relación riesgo-recompensa de 1:3, lo que significa que la distancia al TP será tres veces la distancia del SL.


Identificación del patrón bajista de Gartley

En el artículo anterior, expliqué en detalle la diferencia entre los patrones armónicos alcistas y bajistas. Como siempre digo, al trabajar con patrones gráficos, necesitarás datos de velas y deberás ser capaz de identificar máximos y mínimos de oscilación. El punto de inicio X para el patrón bajista de Gartley debe ser un máximo de oscilación. La estructura sigue entonces los niveles necesarios de retroceso y extensión de Fibonacci a medida que avanza hacia los tramos XA, AB, BC y CD. El EA puede trazar la estructura XABCD y determinar si se ajusta a los parámetros de un Gartley bajista mediante la identificación precisa de estas oscilaciones.

Ejemplo:

input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // TIMEFRAME


double open[];
double close[];
double low[];
double high[];
datetime time[];

datetime time_bar;
int bars_check = 500;
int total_symbol_bars;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   total_symbol_bars = Bars(_Symbol, timeframe);
   time_bar = iTime(_Symbol, timeframe, 0);

   CopyOpen(_Symbol, timeframe, time_bar, bars_check, open);
   CopyClose(_Symbol, timeframe, time_bar, bars_check, close);
   CopyLow(_Symbol, timeframe, time_bar, bars_check, low);
   CopyHigh(_Symbol, timeframe, time_bar, bars_check, high);
   CopyTime(_Symbol, timeframe, time_bar, bars_check, time);

  }

//+------------------------------------------------------------------+
//| FUNCTION FOR SWING LOW                                           |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i])
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| FUNCTION FOR SWING HIGH                                          |
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high_price[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i])
         return false; // If the current high is not the highest, return false.
     }
   return true;
  }

Explicación:

El primer paso del procedimiento consiste en ajustar el marco temporal del Asesor Experto para que coincida con el gráfico en el que opera. Para registrar datos históricos de velas, incluyendo apertura, cierre, máximo, mínimo y hora, se preparan los arrays. La marca de tiempo de la barra más reciente, el número total de barras disponibles y el número de barras a analizar se registran mediante otras variables. Posteriormente, los datos de mercado se almacenarán en estas arrays para permitir un análisis detallado de las fluctuaciones de precios.

El programa registra la hora de la barra más reciente y determina cuántas barras hay disponibles para cada actualización del mercado. Para garantizar que el análisis se base siempre en el estado del mercado, recopila los datos más recientes de precios y tiempo. Esto ofrece una base sólida para realizar cálculos adicionales, así como para detectar tendencias técnicas en los datos de precios.

Al comparar cada barra con sus vecinas, las funciones personalizadas identifican máximos y mínimos significativos. Una función verifica un mínimo claro, mientras que otra verifica un máximo claro. Los patrones de Gartley alcistas y bajistas son ejemplos de patrones armónicos que el ordenador detecta sistemáticamente siguiendo únicamente estos puntos cuando se forma una nueva barra.

Identificando X

Encontrar el punto X es el primer paso para identificar un patrón bajista de Gartley. Para que la configuración sea bajista, el punto X debe ser un máximo en el gráfico. Esto implica que debería ser evidente como un pico donde el precio aumenta antes de disminuir.

Ejemplo:

double X;
datetime X_time;
string X_letter;
int z = 4;
long chart_id = ChartID();
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   total_symbol_bars = Bars(_Symbol, timeframe);
   time_bar = iTime(_Symbol, timeframe, 0);

   CopyOpen(_Symbol, timeframe, time_bar, bars_check, open);
   CopyClose(_Symbol, timeframe, time_bar, bars_check, close);
   CopyLow(_Symbol, timeframe, time_bar, bars_check, low);
   CopyHigh(_Symbol, timeframe, time_bar, bars_check, high);
   CopyTime(_Symbol, timeframe, time_bar, bars_check, time);

   if(total_symbol_bars >= bars_check)
     {

      for(int i = z ; i < bars_check - z; i++)
        {

         if(IsSwingHigh(high, i, z))
           {

            X = high[i];
            X_time = time[i];
            X_letter = StringFormat("X  %d",i);

            ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,X_time,X);
            ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

           }
        }
     }
  }

Explicación:

Para garantizar que haya suficientes velas disponibles para el análisis, el programa comienza con una comprobación de seguridad. Se asegura de que haya al menos 500 barras en el gráfico antes de continuar, ya que requiere esta cantidad. De esta forma, se evitan errores y el Asesor Experto garantiza el uso exclusivo de datos de mercado completos. Después de esta comprobación, el software utiliza un bucle y un período de retrospectiva (z) para recorrer las velas y comparar cada una con sus vecinas. Para evitar errores por acceso fuera de rango y determinar con precisión los puntos de inflexión, por ejemplo, se establece z en 4, de modo que cada vela se compara con las cuatro velas que la precedieron y la siguieron.

El algoritmo establece el punto X, el primer ancla del patrón bajista de Gartley, dentro de este bucle buscando un máximo de oscilación. Cuando se identifica un máximo de oscilación legítimo, se registran el precio, el tiempo y el índice, y el gráfico se marca inmediatamente con una etiqueta de texto que dice "X". Además de resaltar el punto de oscilación crucial, este marcador visual ofrece una referencia clara para las fases subsiguientes en la determinación del patrón Gartley restante.

Identificación de A

La siguiente etapa consiste en buscar el punto A después de que X haya sido marcado correctamente como un máximo de oscilación legítimo. A suele ser un mínimo oscilante que se desarrolla después de X en un patrón armónico bajista. El EA avanza desde la barra donde se detectó X para localizar esto. De forma similar a como usamos IsSwingHigh para X, utiliza la función IsSwingLow para buscar un mínimo de oscilación comprobando cada barra del rango.

Ejemplo:

double A;
datetime A_time;
string A_letter;
string xa_line;
if(total_symbol_bars >= bars_check)
     {

      for(int i = z ; i < bars_check - z; i++)
        {

         if(IsSwingHigh(high, i, z))
           {

            X = high[i];
            X_time = time[i];
            X_letter = StringFormat("X  %d",i);

            ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,X_time,X);
            ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

            for(int j = i; j < bars_check - z; j++)
              {

               if(IsSwingLow(low, j, z) && low[j] < X)
                 {

                  A = low[j];
                  A_time = time[j];
                  A_letter = StringFormat("A %d",j);

                  ObjectCreate(chart_id,A_letter,OBJ_TEXT,0,A_time,A);
                  ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");
                  
                  xa_line = StringFormat("XA Line  %d",i);
                  ObjectCreate(chart_id,xa_line,OBJ_TREND,0,X_time,X,A_time,A);
                  
                  break;

                 }
              }
           }
        }
     }

Resultado:

Figura 1. XA

Explicación:

El Asesor Experto comienza rápidamente a buscar el punto A, el siguiente punto crucial del patrón, después de determinar que el punto X es un máximo de oscilación legítimo. Comienza en la posición X y analiza cada barra que le sigue para determinar un mínimo que sea apropiado para ser un mínimo de oscilación. Para asegurarse de que la estructura se ajusta a un patrón armónico bajista, el programa verifica durante esta búsqueda si el nuevo mínimo es, de hecho, inferior al máximo indicado previamente. Cuando una barra cumple estos requisitos, el software marca este nivel crucial en el gráfico con una etiqueta y registra el precio y la hora como punto A.

El EA traza una etiqueta de texto "A" en el punto seleccionado utilizando ObjectCreate para que la identificación en el gráfico sea clara. A continuación, utiliza el objeto OBJ_TREND para crear una línea de tendencia desde X hasta A. Esta línea conecta el primer máximo de oscilación con el mínimo de oscilación subsiguiente y representa gráficamente el tramo XA del patrón armónico. Para asegurar que la computadora tome como referencia el punto A más cercano y pertinente después de X en lugar de buscar más, la búsqueda finaliza utilizando la instrucción break; cuando se localiza el primer mínimo de swing válido.

El bucle externo (for(int i = z; i < bars_check - z; i++)) está hecho para recorrer cada barra en el rango cuando el EA está buscando X (el máximo del swing). Para identificar posibles puntos de partida para un patrón armónico, el EA debe examinar cada uno de los posibles máximos de oscilación de los datos. El EA pasaría por alto otros puntos X legítimos que podrían desarrollar patrones más adelante en el gráfico si se introdujera una instrucción break aquí, porque el bucle se detendría después de identificar el primer máximo del swing.

Por otro lado, el EA buscará instantáneamente el A comparable (un mínimo de oscilación que sigue a X) después de encontrar un X. En este caso, dado que define el tramo XA, el programa solo requiere el primer mínimo de oscilación válido que sigue a X. Esta pausa garantiza que el EA no siga buscando mínimos después del primer A legítimo, lo que podría causar confusión o superposición con los tramos XA. Encontrar el X más cercano a A, asegurándose de que cada A esté emparejado con el X legítimo más cercano, es la solución. De este modo, el tramo XA se mantiene consistente y evita las detecciones superpuestas.

Ejemplo:
if(total_symbol_bars >= bars_check)
  {

   for(int i = z ; i < bars_check - z; i++)
     {

      if(IsSwingHigh(high, i, z))
        {

         for(int j = i; j < bars_check - z; j++)
           {

            if(IsSwingLow(low, j, z) && low[j] < high[i])
              {

               A = low[j];
               A_time = time[j];
               A_letter = StringFormat("A %d",j);

               for(int a = j; a >= i; a--)
                 {

                  if(IsSwingHigh(high, a, z) && high[a] > A)
                    {

                     X = high[a];
                     X_time = time[a];
                     X_letter = StringFormat("X  %d",a);

                     ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,X_time,X);
                     ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

                     ObjectCreate(chart_id,A_letter,OBJ_TEXT,0,A_time,A);
                     ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");

                     xa_line = StringFormat("XA Line  %d",i);
                     ObjectCreate(chart_id,xa_line,OBJ_TREND,0,X_time,X,A_time,A);

                     break;

                    }
                 }

               break;

              }
           }
        }
     }
  }
  }
  }

  }

Resultado: 

Figura 3. X

Explicación:

Solo utilizamos high[i] como referencia temporal cuando usamos por primera vez la condición low[j] < high[i] para comprobar si hay un mínimo oscilante. Aún no se ha decidido qué altura máxima de swing debería seleccionarse formalmente, ya que todavía no se ha determinado formalmente cuál debe ser X. La estructura sigue siendo apropiada para comenzar el tramo XA porque usar high[i] simplemente garantiza que A (el mínimo del swing) sea más bajo que al menos un máximo del swing anterior. Dicho de otro modo, high[i] sirve como una comprobación de marcador de posición que elimina los valores bajos que son inconsistentes con A.

Dado que ese máximo oscilante podría no ser el contendiente más fuerte o más cercano antes de A, no podemos simplemente elegir high[i] como X una vez que se ha determinado un A legítimo. El bucle inverso for(int a = j; a >= i; a--) se introduce para mejorar esto. Este bucle retrocede desde la posición A hasta el máximo inicial. Es la opción más sensata para X, ya que busca identificar el máximo del swing más cercano a A y superior a este. Esto garantiza que, en lugar de basarse en un máximo anterior arbitrario, nuestro tramo XA se genere de forma segura y adecuada.

Dado que solo queremos dibujar estos elementos después de verificar que X es válido, finalmente transferimos el proceso de creación de objetos (marcar X, marcar A y dibujar la línea XA) dentro de este bucle inverso. Trazar X y A demasiado pronto, posiblemente con la altura de oscilación incorrecta, sería el resultado de mantener la creación de objetos fuera del alcance. Nos aseguramos de que el gráfico solo muestre el tramo XA una vez que se haya verificado el máximo de oscilación válido más cercano haciéndolo dentro del bucle.

Identificación de la BC

Tras finalizar el tramo XA, el siguiente paso es encontrar el tramo BC. Dos puntos, B y C, conforman esta sección de la estructura en un patrón bajista de Gartley. El punto C debe formarse como un mínimo oscilatorio que sigue al punto B, y el punto B debe aparecer como un máximo oscilatorio que sigue al punto A. Comenzamos a delinear la segunda parte del patrón armónico estableciendo estas dos ubicaciones.

Actualmente, no se están introduciendo condiciones de retroceso o extensión de Fibonacci. En cambio, el enfoque se centra en determinar los puntos de inflexión naturales del movimiento de los precios.

Ejemplo:
double B;
datetime B_time;
string B_letter;

double C;
datetime C_time;
string C_letter;
string ab_line;
string bc_line;
for(int k = j; k < bars_check - z; k++)
  {

   if(IsSwingHigh(high, k, z) && high[k] > A)
     {

      B = high[k];
      B_time = time[k];
      B_letter =  StringFormat("B %d",k);


      for(int l = k; l < bars_check - z; l++)
        {

         if(IsSwingLow(low, l, z) && low[l] < B)
           {

            C = low[l];
            C_time = time[l];
            C_letter = StringFormat("C  %d",l);

            ObjectCreate(chart_id,B_letter,OBJ_TEXT,0,B_time,B);
            ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B");

            ObjectCreate(chart_id,C_letter,OBJ_TEXT,0,C_time,C);
            ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C");

            ab_line = StringFormat("AB Line  %d",i);
            ObjectCreate(chart_id,ab_line,OBJ_TREND,0,A_time,A,B_time,B);

            bc_line = StringFormat("BC Line  %d", i);
            ObjectCreate(chart_id,bc_line,OBJ_TREND,0,B_time,B,C_time,C);

            ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,X_time,X);
            ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

            ObjectCreate(chart_id,A_letter,OBJ_TEXT,0,A_time,A);
            ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");

            xa_line = StringFormat("XA Line  %d",i);
            ObjectCreate(chart_id,xa_line,OBJ_TREND,0,X_time,X,A_time,A);

            i = l+1;

            break;
           }
        }

      break;
     }
  }

Resultado:

Figura 3. BC

Explicación:

Partiendo del punto A, el primer bucle, for(int k = j; k < bars_check - z; k++), comienza a buscar B. El software utiliza IsSwingHigh para buscar un máximo de oscilación dentro de este bucle. También verifica que high[k] > A, lo que indica que el máximo de oscilación está por encima del punto A, para asegurarse de que B tenga sentido como componente de la estructura. El software registra el valor, la hora y la etiqueta de B una vez que se cumple esta condición.

Para determinar C, que debe ser un mínimo de oscilación que surge después de B, se emplea un segundo bucle, for(int l = k; l < bars_check - z; l++). La estructura descendente adecuada del modelo bajista de Gartley se mantiene mediante el criterio low[l] < B, que garantiza que C sea, de hecho, inferior al máximo de oscilación B. Los detalles (precio, hora y etiqueta) se guardan cuando se descubre un valor válido de C. El programa vuelve a dibujar X y A para mantener la claridad del patrón después de identificar los puntos B y C y etiquetarlos en el gráfico. 

El algoritmo avanza rápidamente y evita detectar continuamente los mismos lugares utilizando la expresión i = l+1; para avanzar el bucle exterior. El algoritmo puede pasar por alto muchas señales legítimas de Gartley si se usa i = l+1 después de identificar C. Se pierden posibles puntos de inflexión porque avanza demasiado rápido. Esta estrategia debe evitarse y, en su lugar, se debe permitir que los bucles funcionen con normalidad o refinar las señales utilizando técnicas de filtrado para preservar las oportunidades.

Identificación de D

Encontrar el punto D es la siguiente etapa para completar el patrón de Gartley una vez que se hayan identificado correctamente X, A, B y C. Dado que el punto D es la posible zona de entrada, constituye la última etapa de la construcción y es crucial. Buscamos un máximo de oscilación que se forme después del punto C para identificar D. Para completar el tramo CD, este movimiento debería viajar preferiblemente en la dirección opuesta a BC.

Ejemplo:

double D;
datetime D_time;
string D_letter;
string cd_line;
for(int m = l; m < bars_check - z; m++)
  {

   if(IsSwingHigh(high, m, z) && high[m] > B)
     {

      D = high[m];
      D_time = time[m];
      D_letter = StringFormat("D  %d",m);

      ObjectCreate(chart_id,D_letter,OBJ_TEXT,0,D_time,D);
      ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D");

      cd_line = StringFormat("CD Line  %d",i);
      ObjectCreate(chart_id,cd_line,OBJ_TREND,0,C_time,C,D_time,D);

      ObjectCreate(chart_id,B_letter,OBJ_TEXT,0,B_time,B);
      ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B");

      ObjectCreate(chart_id,C_letter,OBJ_TEXT,0,C_time,C);
      ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C");

      ab_line = StringFormat("AB Line  %d",i);
      ObjectCreate(chart_id,ab_line,OBJ_TREND,0,A_time,A,B_time,B);

      bc_line = StringFormat("BC Line  %d", i);
      ObjectCreate(chart_id,bc_line,OBJ_TREND,0,B_time,B,C_time,C);

      ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,X_time,X);
      ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

      ObjectCreate(chart_id,A_letter,OBJ_TEXT,0,A_time,A);
      ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");

      xa_line = StringFormat("XA Line  %d",i);
      ObjectCreate(chart_id,xa_line,OBJ_TREND,0,X_time,X,A_time,A);

      i = m+1;
      break;
     }
  }

Resultado:

Figura 4. D

Explicación:

El bucle comienza a escanear el gráfico desde el índice del punto C (l) hacia adelante, buscando posibles máximos de oscilación que podrían ser el punto D. La condición IsSwingHigh(high, m, z) && high[m] > B garantiza que el D seleccionado sea más alto que el punto B y no simplemente cualquier máximo de oscilación. Dado que en un patrón Gartley bajista es necesario que el punto D supere el nivel B para preservar la estructura adecuada, esta regla es crucial.

Tras identificar dicho máximo de oscilación, el código crea la línea CD para conectarlo con el punto C, la etiqueta como "D" en el gráfico e introduce sus datos de precio y tiempo en las variables D. El código redibuja simultáneamente las líneas que corresponden a los demás puntos significativos.

El siguiente paso consiste en confirmar que cada punto representa correctamente el swing dominante dentro de cada tramo una vez que los movimientos se han identificado de manera efectiva. Marcar los máximos y los mínimos es insuficiente, ya que incluso pequeños ajustes podrían hacer que el marco se vuelva confuso. Es necesario implementar directrices estrictas para verificar la legitimidad de cada punto de inflexión y así evitar señales falsas.

Primero, entre C y D, el punto C debe ser el mínimo más bajo. De este modo, se garantiza que el tramo CD comience desde un mínimo real del swing en lugar de una caída breve. Para garantizar que el tramo BC retroceda desde un pico dominante legítimo, el punto B también debe ser el punto más alto entre B y C.

De igual modo, debe verificarse que el punto A sea el mínimo entre los puntos A y B. Esta condición confirma que, en lugar de comenzar en una fluctuación aleatoria, el tramo AB comienza en el mínimo correcto. Por último, entre X y A, el punto X debe ser el punto más alto. Gracias a esta comprobación, la pierna XA comenzará en el punto más alto del swing en esa área.

Ejemplo:

int x_a_bars;
int x_highest_index;
double x_a_hh;
datetime x_a_hh_t;

int a_b_bars;
int a_lowest_index;
double a_b_ll;
datetime a_b_ll_t;

int b_c_bars;
int b_highest_index;
double b_c_hh;
datetime b_c_hh_t;

int c_d_bars;
int c_lowest_index;
double c_d_ll;
datetime c_d_ll_t;
for(int m = l; m < bars_check - z; m++)
  {

   if(IsSwingHigh(high, m, z) && high[m] > B)
     {

      D = high[m];
      D_time = time[m];
      D_letter = StringFormat("D  %d",m);

      c_d_bars = Bars(_Symbol,PERIOD_CURRENT,C_time,D_time);
      c_lowest_index = ArrayMinimum(low,l,c_d_bars);
      c_d_ll = low[c_lowest_index];
      c_d_ll_t = time[c_lowest_index];

      b_c_bars = Bars(_Symbol, PERIOD_CURRENT, B_time, c_d_ll_t);
      b_highest_index = ArrayMaximum(high, k, b_c_bars);
      b_c_hh = high[b_highest_index];
      b_c_hh_t = time[b_highest_index];

      a_b_bars = Bars(_Symbol,PERIOD_CURRENT,A_time,b_c_hh_t);
      a_lowest_index = ArrayMinimum(low,j,a_b_bars);
      a_b_ll = low[a_lowest_index];
      a_b_ll_t = time[a_lowest_index];

      x_a_bars = Bars(_Symbol, PERIOD_CURRENT, X_time, a_b_ll_t);
      x_highest_index = ArrayMaximum(high, a, x_a_bars);
      x_a_hh = high[x_highest_index];
      x_a_hh_t = time[x_highest_index];

      ObjectCreate(chart_id,D_letter,OBJ_TEXT,0,D_time,D);
      ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D");

      cd_line = StringFormat("CD Line  %d",i);
      ObjectCreate(chart_id,cd_line,OBJ_TREND,0,c_d_ll_t,c_d_ll,D_time,D);

      ObjectCreate(chart_id,B_letter,OBJ_TEXT,0,b_c_hh_t,b_c_hh);
      ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B");

      ObjectCreate(chart_id,C_letter,OBJ_TEXT,0,c_d_ll_t,c_d_ll);
      ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C");

      ab_line = StringFormat("AB Line  %d",i);
      ObjectCreate(chart_id,ab_line,OBJ_TREND,0,a_b_ll_t,a_b_ll,b_c_hh_t,b_c_hh);

      bc_line = StringFormat("BC Line  %d", i);
      ObjectCreate(chart_id,bc_line,OBJ_TREND,0,b_c_hh_t,b_c_hh,c_d_ll_t,c_d_ll);

      ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,x_a_hh_t,x_a_hh);
      ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

      ObjectCreate(chart_id,A_letter,OBJ_TEXT,0,a_b_ll_t,a_b_ll);
      ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");

      xa_line = StringFormat("XA Line  %d",i);
      ObjectCreate(chart_id,xa_line,OBJ_TREND,0,x_a_hh_t,x_a_hh,a_b_ll_t,a_b_ll);

      i = m+1;
      break;
     }
  }

Explicación:

Los principales objetivos de esta sección del código son verificar la exactitud de los puntos de inflexión encontrados y asegurarse de que la estructura de Gartley se derive de los máximos y mínimos más notables en cada tramo. Cuando se descubre un nuevo máximo oscilatorio que es más alto que el punto B después del punto C, comienza el proceso. Aunque al punto D se le asigna este máximo de oscilación, el código primero verifica todos los puntos anteriores antes de concluir el patrón.

El rango C-D es donde comienza la validación. El código en esta parte busca el mínimo más bajo para asegurar que el punto C sea el mínimo real entre esos dos puntos y no simplemente una caída transitoria. Para determinar el valor máximo, se examina el rango entre B y C. Esto garantiza que, en lugar de un pequeño repunte que pueda distorsionar el patrón, el punto B sea el pico más pronunciado en esa sección.

Para verificar que el punto A es, de hecho, el valle más prominente en ese tramo, se examina el rango entre A y B para encontrar el mínimo más bajo. Para confirmar que X es el pico más pronunciado antes del descenso a A, finalmente se examina el rango entre X y A para encontrar el máximo. El código garantiza que todos los puntos X, A, B y C sean las oscilaciones estructurales adecuadas para crear el patrón armónico mediante la realización de estas comprobaciones.


Retroceso de Fibonacci

El siguiente paso, tras la identificación y validación de los puntos de inflexión, es determinar si la estructura cumple realmente los criterios para ser considerada un patrón de Gartley. Dado que no todos los conjuntos de swings crean un patrón armónico legítimo, el simple hecho de tener los puntos X, A, B, C y D indicados en el gráfico es insuficiente. Debemos utilizar los criterios de retroceso y extensión de Fibonacci en los distintos tramos estructurales para distinguirlos.

Los "filtros" que verifican si los movimientos de precios coinciden con las proporciones necesarias para un patrón de Gartley son estas mediciones de Fibonacci. Por ejemplo, ciertos rangos de Fibonacci deben cumplirse mediante la expansión del punto D en relación con los demás movimientos, el retroceso del punto B desde XA y el retroceso del punto C desde AB. La estructura puede clasificarse como un patrón Gartley legítimo si se cumplen estos requisitos.

Ejemplo:

input double b_xa_max = 78.6; // MAX B RETRACEMENT LEVEL FOR XA
input double b_xa_min = 61.8; // MIN B RETRACEMENT LEVEL FOR XA

input double c_ab_max = 88.6; // MAX C RETRACEMENT LEVEL FOR AB
input double c_ab_min = 38.2; // MIN C RETRACEMENT LEVEL FOR AB

input double d_xa_max = 76.0; // MAX D RETRACEMENT LEVEL FOR XA
input double d_xa_min = 80.0; // MIN D RETRACEMENT LEVEL FOR XA

double lvl_max_b;
double lvl_min_b;

double lvl_max_c;
double lvl_min_c;

double lvl_max_d;
double lvl_min_d;
if(IsSwingHigh(high, m, z) && high[m] > B)
  {

   D = high[m];
   D_time = time[m];
   D_letter = StringFormat("D  %d",m);

   c_d_bars = Bars(_Symbol,PERIOD_CURRENT,C_time,D_time);
   c_lowest_index = ArrayMinimum(low,l,c_d_bars);
   c_d_ll = low[c_lowest_index];
   c_d_ll_t = time[c_lowest_index];

   b_c_bars = Bars(_Symbol, PERIOD_CURRENT, B_time, c_d_ll_t);
   b_highest_index = ArrayMaximum(high, k, b_c_bars);
   b_c_hh = high[b_highest_index];
   b_c_hh_t = time[b_highest_index];

   a_b_bars = Bars(_Symbol,PERIOD_CURRENT,A_time,b_c_hh_t);
   a_lowest_index = ArrayMinimum(low,j,a_b_bars);
   a_b_ll = low[a_lowest_index];
   a_b_ll_t = time[a_lowest_index];

   x_a_bars = Bars(_Symbol, PERIOD_CURRENT, X_time, a_b_ll_t);
   x_highest_index = ArrayMaximum(high, a, x_a_bars);
   x_a_hh = high[x_highest_index];
   x_a_hh_t = time[x_highest_index];

   lvl_min_b = a_b_ll + ((b_xa_min / 100) * (x_a_hh - a_b_ll));
   lvl_max_b = a_b_ll + ((b_xa_max / 100) * (x_a_hh - a_b_ll));

   lvl_min_c = b_c_hh - ((c_ab_min / 100) * (b_c_hh - a_b_ll));
   lvl_max_c = b_c_hh - ((c_ab_max / 100) * (b_c_hh - a_b_ll));

   lvl_min_d = a_b_ll + ((d_xa_min / 100) * (x_a_hh - a_b_ll));
   lvl_max_d = a_b_ll + ((d_xa_max / 100) * (x_a_hh - a_b_ll));

   if(b_c_hh >= lvl_min_b && b_c_hh <= lvl_max_b && c_d_ll <= lvl_min_c && c_d_ll >= lvl_max_c
      && D >= lvl_min_d && D <= lvl_max_d)
     {

      ObjectCreate(chart_id,D_letter,OBJ_TEXT,0,D_time,D);
      ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D");

      cd_line = StringFormat("CD Line  %d",i);
      ObjectCreate(chart_id,cd_line,OBJ_TREND,0,c_d_ll_t,c_d_ll,D_time,D);

      ObjectCreate(chart_id,B_letter,OBJ_TEXT,0,b_c_hh_t,b_c_hh);
      ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B");

      ObjectCreate(chart_id,C_letter,OBJ_TEXT,0,c_d_ll_t,c_d_ll);
      ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C");

      ab_line = StringFormat("AB Line  %d",i);
      ObjectCreate(chart_id,ab_line,OBJ_TREND,0,a_b_ll_t,a_b_ll,b_c_hh_t,b_c_hh);

      bc_line = StringFormat("BC Line  %d", i);
      ObjectCreate(chart_id,bc_line,OBJ_TREND,0,b_c_hh_t,b_c_hh,c_d_ll_t,c_d_ll);

      ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,x_a_hh_t,x_a_hh);
      ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

      ObjectCreate(chart_id,A_letter,OBJ_TEXT,0,a_b_ll_t,a_b_ll);
      ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");

      xa_line = StringFormat("XA Line  %d",i);
      ObjectCreate(chart_id,xa_line,OBJ_TREND,0,x_a_hh_t,x_a_hh,a_b_ll_t,a_b_ll);

     }

// i = m+1;
   break;
  }

  }

Explicación:

Comenzamos con los parámetros de entrada. Para determinar si la estructura de X, A, B, C y D es un patrón de Gartley válido, el Asesor Experto empleará estos niveles de retroceso de Fibonacci definidos por el usuario. Por ejemplo, dependiendo de los datos de entrada, el punto B debería estar entre el retroceso indicado del tramo XA, la posición C en el retroceso dado del tramo AB y el punto D en el retroceso de Fibonacci proporcionado del tramo XA.

Las variables calculadas representan los niveles de precios reales en los que debería formarse cada punto de inflexión. Basándose en los principios de Fibonacci establecidos en los datos de entrada, se calculan a partir de los precios máximos y mínimos de los tramos XA y AB. Por ejemplo, el retroceso del tramo XA establece el rango válido para el punto B, y el mismo método se utiliza para calcular los rangos para los puntos C y D en relación con AB y XA.

Se verifica que cada punto del patrón se encuentre dentro del rango de Fibonacci necesario mediante la última comprobación de la condición. Confirma que se cumplen las condiciones de retroceso para los puntos B, C y D. Cuando se cumplen todos estos requisitos, el programa genera objetos gráficos para representar visualmente el patrón de Gartley reconocido, identificando los puntos X, A, B, C y D y dibujando las líneas que los conectan.


Ejecución de operaciones

La ejecución de la operación se produce tras la identificación satisfactoria de un patrón Gartley legítimo. Para garantizar que la estructura cumpla con las directrices de formación de Gartley, el Asesor Experto se ha centrado hasta ahora en examinar los datos de precios, identificar los máximos y mínimos de oscilación y verificar las relaciones de Fibonacci. Sin embargo, el mero hecho de identificar el patrón le brinda al operador una posible oportunidad de trading; no genera ganancias por sí solo. Cuando el EA realiza operaciones en el mercado utilizando este conocimiento, ahí empieza la operativa real.

En realidad, esto significa que el EA ahora tiene que elegir dónde entrar, dónde colocar el take profit y dónde establecer el stop loss. Por ejemplo, el EA normalmente iniciaría una orden de venta en el punto D en un patrón bajista de Gartley. Para evitar la invalidación del patrón, el stop loss se colocaría ligeramente por encima del máximo del swing en D. El take profit podría determinarse utilizando objetivos de extensión de Fibonacci o una relación riesgo-recompensa como 1:3. El EA garantiza un rendimiento constante, sin vacilaciones ni sesgos emocionales, mediante la automatización de este proceso.

Ejemplo:

#include <Trade/Trade.mqh>
CTrade trade;
input int MagicNumber = 61626;
input double lot_size = 0.1;

double ask_price;
datetime time_price[];
double take_p;

string XAB;
string BCD;
datetime lastTradeBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   trade.SetExpertMagicNumber(MagicNumber);
   ArraySetAsSeries(time_price,true);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

   ObjectsDeleteAll(chart_id);

  }
CopyTime(_Symbol, timeframe, 0, 2, time_price);
ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
datetime currentBarTime = iTime(_Symbol,timeframe,0);

if(b_c_hh >= lvl_min_b && b_c_hh <= lvl_max_b && c_d_ll <= lvl_min_c && c_d_ll >= lvl_max_c
   && D >= lvl_min_d && D <= lvl_max_d)
  {

   ObjectCreate(chart_id,D_letter,OBJ_TEXT,0,D_time,D);
   ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D");

   cd_line = StringFormat("CD Line  %d",i);
   ObjectCreate(chart_id,cd_line,OBJ_TREND,0,c_d_ll_t,c_d_ll,D_time,D);

   ObjectCreate(chart_id,B_letter,OBJ_TEXT,0,b_c_hh_t,b_c_hh);
   ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B");

   ObjectCreate(chart_id,C_letter,OBJ_TEXT,0,c_d_ll_t,c_d_ll);
   ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C");

   ab_line = StringFormat("AB Line  %d",i);
   ObjectCreate(chart_id,ab_line,OBJ_TREND,0,a_b_ll_t,a_b_ll,b_c_hh_t,b_c_hh);

   bc_line = StringFormat("BC Line  %d", i);
   ObjectCreate(chart_id,bc_line,OBJ_TREND,0,b_c_hh_t,b_c_hh,c_d_ll_t,c_d_ll);

   ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,x_a_hh_t,x_a_hh);
   ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");

   ObjectCreate(chart_id,A_letter,OBJ_TEXT,0,a_b_ll_t,a_b_ll);
   ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");

   xa_line = StringFormat("XA Line  %d",i);
   ObjectCreate(chart_id,xa_line,OBJ_TREND,0,x_a_hh_t,x_a_hh,a_b_ll_t,a_b_ll);

   XAB = StringFormat("XAB TRIANGLE %d", i);
   BCD = StringFormat("BCD TRIANGLE %d", i);

   ObjectCreate(chart_id,XAB,OBJ_TRIANGLE,0,x_a_hh_t,x_a_hh,a_b_ll_t,a_b_ll,b_c_hh_t,b_c_hh);
   ObjectSetInteger(chart_id,XAB,OBJPROP_FILL,true);
   ObjectSetInteger(chart_id,XAB,OBJPROP_COLOR,clrPink);
   ObjectSetInteger(chart_id,XAB,OBJPROP_BACK,true);

   ObjectCreate(chart_id,BCD,OBJ_TRIANGLE,0,b_c_hh_t,b_c_hh,c_d_ll_t,c_d_ll,D_time,D);
   ObjectSetInteger(chart_id,BCD,OBJPROP_FILL,true);
   ObjectSetInteger(chart_id,BCD,OBJPROP_COLOR,clrPink);
   ObjectSetInteger(chart_id,BCD,OBJPROP_BACK,true);

   if(time[m+z] == time_price[1] && currentBarTime != lastTradeBarTime)
     {

      take_p = ask_price - (MathAbs(D - ask_price) * 3);
      trade.Sell(lot_size,_Symbol,ask_price,D,take_p);

      lastTradeBarTime = currentBarTime;

     }

  }

Resultado:

Figura 5. Patrón bajista

Explicación:

Esta sección del programa gestiona la representación visual del patrón Gartley en el gráfico, así como la lógica operativa. La biblioteca de operaciones MQL5 estándar se incluye primero mediante #include<Trade/Trade.mqh> . A partir de esta biblioteca, se crea un objeto CTrade llamado trade para gestionar todas las tareas relacionadas con las operaciones, incluyendo la apertura y el cierre de posiciones. Se declara un parámetro de entrada llamado MagicNumber y se utiliza con las transacciones para diferenciar las operaciones realizadas por este Asesor Experto de las demás mediante trade.SetExpertMagicNumber(MagicNumber);.

El volumen que se operará está determinado por otros factores cruciales como el tamaño del lote. Para almacenar el precio de mercado, los datos de tiempo y los niveles de take profit (TP) calculados, se declaran variables auxiliares como ask_price, time_price[] y take_p. Como medida de precaución, se utiliza la variable lastTradeBarTime para garantizar que no se abra una nueva transacción en la misma barra más de una vez.

El elemento más reciente se mantiene en el índice cero aplicando ArraySetAsSeries(time_price, true); para garantizar que los datos de precio se traten adecuadamente. Los tiempos de apertura de las dos últimas barras se copian luego en el array time_price mediante el método CopyTime(_Symbol, timeframe, 0, 2, time_price). Ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); devuelve el precio Ask actual, y currentBarTime = iTime(_Symbol, timeframe, 0); almacena la marca de tiempo de la barra más reciente.

El ordenador puede utilizar estos datos específicos para determinar el instante preciso en que se cumplen los requisitos comerciales. Al comparar los tiempos de cada barra, la comprobación condicional garantiza que el programa solo reaccione una vez por cada barra. Si es válida, se ejecuta una orden de venta con trade, y se determina un nivel de take profit (TP) usando take_p = ask_price - (MathAbs(D - ask_price) * 3);. Sell(lot_size, D, take_p, ask_price, _Symbol);. Para evitar que la misma barra active otra transacción, se modifica el valor de lastTradeBarTime.

El programa no solo ejecuta operaciones, sino que también muestra el patrón de Gartley directamente en el gráfico. Por ejemplo, XAB = StringFormat("XAB TRIANGLE %d", i); y BCD = StringFormat("BCD TRIANGLE %d", i); son cadenas que se construyen utilizando StringFormat para nombrar de forma única los objetos gráficos. A continuación, se utiliza ObjectCreate para dibujar dos triángulos utilizando estos identificadores. La parte XAB del patrón está representada por el primer triángulo, y la parte BCD por el segundo. Según los puntos de inflexión correspondientes encontrados previamente en el software, se establecen sus coordenadas.

Las propiedades de los triángulos se configuran mediante ObjectSetInteger, donde se rellenan, se les da un tono rosa y se envían al fondo para que estos patrones destaquen. El programa no solo reacciona a los patrones de Gartley reconocidos, sino que también los pone a disposición del operador para su verificación gracias a la combinación de la ejecución automática de operaciones y una visualización gráfica clara.


Conclusión

En este artículo, exploramos cómo construir y operar con el patrón Gartley en MQL5. Comenzamos identificando los puntos de inflexión (X, A, B, C y D), aplicamos los niveles de retroceso y extensión de Fibonacci para confirmar la estructura y, una vez que el patrón fue válido, automatizamos la ejecución de las operaciones. Por último, añadimos objetos gráficos para una visualización más clara, lo que facilita ver el patrón a medida que se va formando. Este proyecto muestra cómo se pueden codificar y operar automáticamente patrones armónicos complejos. Con esta base, puedes refinar las reglas, añadir confirmaciones o extender la lógica a otros patrones armónicos. 

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

Motor de decisión Multi-IA para MQL5 (Parte 2): Voto ponderado que aprende en cuál IA confiar, más gestión de riesgo Motor de decisión Multi-IA para MQL5 (Parte 2): Voto ponderado que aprende en cuál IA confiar, más gestión de riesgo
Esta segunda parte convierte el motor multi-IA en un sistema que aprende en qué modelos confiar. Se registra cada predicción, se evalúa su acierto tras un horizonte y se actualiza un hit-rate por proveedor (EMA), para ponderar el voto por confianza × acierto real. Además, se añade gestión de riesgo: SL/TP basada en ATR con ratio recompensa-riesgo fijado en el código y tamaño de posición escalado por la confianza. Útil para pruebas de demostración.
De novato a experto: Implementación de estrategias de Fibonacci en el trading posterior al NFP De novato a experto: Implementación de estrategias de Fibonacci en el trading posterior al NFP
En los mercados financieros, las leyes del retroceso siguen siendo una de las fuerzas más indiscutibles. Una regla general es que el precio siempre retrocede, ya sea en movimientos amplios o incluso en los patrones de tick más pequeños, que a menudo adoptan la forma de un zigzag. Sin embargo, el patrón de retroceso en sí mismo nunca es fijo; sigue siendo incierto y solo puede anticiparse. Esta incertidumbre explica por qué los operadores se basan en múltiples niveles de Fibonacci, cada uno de los cuales conlleva una determinada probabilidad de influir en el mercado. En este análisis, presentamos una estrategia perfeccionada que aplica técnicas de Fibonacci para hacer frente a los retos que plantea operar poco después de los anuncios de acontecimientos económicos importantes. Al combinar los principios del retroceso con el comportamiento del mercado impulsado por eventos, nuestro objetivo es identificar oportunidades de entrada y salida más fiables. Te invitamos a seguir leyendo para conocer el análisis completo y ver cómo adaptar Fibonacci a la operativa posterior al evento.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Del básico al intermedio: Colas, listas y árboles (I) Del básico al intermedio: Colas, listas y árboles (I)
En este artículo comenzaremos a explorar una pequeña serie de conceptos de suma importancia para quienes realmente desean aprender a programar correctamente. Como al principio puede resultar muy complicado, aunque se base en elementos sencillos, lo veremos poco a poco. Entonces, aquí comenzaremos a ver qué son las colas de datos.