English Русский Deutsch 日本語
preview
Introducción a MQL5 (Parte 24): Creación de un asesor experto (EA) que opere a partir de objetos del gráfico

Introducción a MQL5 (Parte 24): Creación de un asesor experto (EA) que opere a partir de objetos del gráfico

MetaTrader 5Trading |
54 0
ALGOYIN LTD
Israel Pelumi Abioye

Introducción

¡Bienvenidos de nuevo a la parte 24 de la serie Introducción a MQL5! En este artículo, daremos un paso importante al combinar el análisis manual de gráficos con la ejecución automatizada de operaciones comerciales. En concreto, crearemos un asesor experto que ejecute operaciones basadas directamente en objetos del gráfico, como rectángulos, líneas de tendencia y líneas de soporte y resistencia.

Este método cierra la brecha entre el trading automatizado y el discrecional. Con solo esbozar elementos en tu gráfico, ahora puedes guiar visualmente tu asesor experto en lugar de depender únicamente de parámetros técnicos predeterminados. Una vez identificados estos elementos, el asesor experto realizará un seguimiento de las variaciones de precio e iniciará operaciones automáticamente cada vez que el precio interactúe con ellos.

En este artículo también aprenderás a detectar objetos del gráfico mediante programación y a utilizar esas coordenadas para calcular los puntos de entrada y salida de las operaciones. Además, configuraremos el asesor experto para que reaccione de forma dinámica, lo que significa que se adaptará al instante a cualquier cambio que realices en los elementos de tu gráfico.

 

Cómo funciona el asesor experto

Crearemos un asesor experto que detecte las reversiones de tendencia en zonas estratégicas utilizando objetos gráficos. Crearemos un asesor experto de soporte y resistencia, en el que el usuario marque manualmente los posibles puntos de reversión en el gráfico mediante un rectángulo. El asesor experto estará atento a esa zona y esperará a que el precio llegue hasta ella, si colocas un rectángulo por encima del precio para indicar una zona de resistencia.

El asesor experto esperará a que se produzca un cambio de carácter (ChoCH) bajista para validar que se está formando una posible reversión antes de ejecutar una operación una vez que el precio alcance la zona. El asesor experto iniciará automáticamente una operación de venta si el precio se mantiene dentro del rectángulo sin romperla por arriba y se detecta un cambio de carácter (ChoCH) bajista.

Cuando el mercado empieza a formar máximos y mínimos más bajos (estructura bajista), en lugar de máximos y mínimos cada vez más altos (estructura alcista), se dice que ha experimentado un cambio de carácter (ChoCH) a la baja. El asesor experto buscará cuatro movimientos significativos de los precios para identificar esto:

  • Un mínimo,
  • seguido de un máximo,
  • luego un mínimo más alto,
  • luego un máximo más alto,
  • y, por último, una ruptura a la baja por debajo del mínimo más alto anterior.

La estructura alcista se ha roto y se ha producido un cambio de carácter (ChoCH) a la baja, tal y como confirma esa última ruptura por debajo del mínimo más alto. El asesor experto coloca una orden de venta cuando se produce este cambio de estructura dentro del rectángulo de resistencia o cerca de él, lo que indica una alta probabilidad de que se produzca un cambio en la estructura del mercado.

Figura 1: Resistencia

Una zona de soporte sigue la misma lógica. Para simbolizar el soporte, el usuario dibujará un rectángulo debajo del precio, lo que indica un posible punto de reversión alcista. El asesor experto espera la confirmación de un cambio alcista de carácter antes de realizar cualquier operación, una vez que el precio alcance esta zona. También se pueden utilizar cuatro oscilaciones para identificar el cambio alcista de tendencia:

  • Un máximo,
  • seguido de un mínimo,
  • luego un máximo más bajo,
  • luego un mínimo más bajo,
  • y, por último, una ruptura al alza por encima del máximo anterior más bajo.

La estructura del mercado ha pasado de bajista a alcista, tal y como demuestra esa última ruptura al alza por encima del máximo decreciente. Esto indica una probable reversión alcista, y el asesor experto ejecutará automáticamente una operación de compra si se produce dentro del rectángulo de soporte o cerca de él.

Figura 2: Soporte


Identificación de los objetos gráficos

Dado que determina cómo selecciona el asesor experto qué rectángulo utilizar como soporte o resistencia, la identificación de los objetos del gráfico es un componente fundamental de este proyecto. Un asesor experto puede identificar los objetos del gráfico que ha creado por sí mismo, pero no puede identificar los objetos que un usuario haya dibujado manualmente. El asesor experto debe ser capaz de determinar con precisión cuáles de los muchos elementos, rectángulos, líneas de tendencia, etiquetas de texto o formas que suelen estar presentes en el gráfico debe utilizar como base de su lógica.

Utilizamos la función ObjectCreate() al escribir código para crear objetos gráficos. El asesor experto puede dibujar diversos objetos en el gráfico mediante esta función. Pero es importante tener en cuenta que se puede acceder a las propiedades de un objeto o modificarlas en cualquier momento una vez que se ha creado. Estos atributos pueden ser el color, el estilo, la visibilidad, el tiempo y los niveles de precio, entre otros.

Para obtener una explicación más detallada sobre el funcionamiento de la función ObjectCreate(), puedes consultar la Parte 9 de esta serie, donde explico cómo crear objetos gráficos en tu código.

Lo más importante es que, en MQL5, el nombre del objeto es la clave para acceder a todas sus propiedades. Puedes obtener o modificar los datos de un objeto con solo conocer su nombre, independientemente de si lo ha creado manualmente el usuario o automáticamente el asesor experto. Solicitar los niveles de precio máximo y mínimo o comprobar los límites temporales de un objeto rectangular utilizado como resistencia es sencillo si conoces su nombre.

Por este motivo, el nombre del objeto actúa como una especie de enlace entre la lógica del asesor experto y el gráfico del usuario. Allana el camino para la toma de decisiones automatizadas precisas, al permitir que el asesor experto lea las coordenadas exactas de la zona trazada y utilice esa información para determinar cuándo el precio entra en dicha zona.

El problema que se nos plantea es cómo incluir en nuestro código el nombre de un objeto que aún no conocemos. Piénsalo: antes incluso de que el usuario dibuje el objeto en el gráfico, el software ya se ha compilado y está en funcionamiento. Por lo tanto, ¿cómo es posible que el asesor experto sepa de antemano el nombre del objeto que debe utilizar?

El nombre del objeto puede consultarse directamente en el gráfico, incluso después de dibujarlo, de forma sencilla y eficaz. Esta técnica permite que el asesor experto detecte dinámicamente los elementos creados por el usuario sin necesidad de que el nombre esté codificado de antemano. Veamos cómo funciona eso.

Ejemplo:
input string h_line = ""; // Horizontal Line

ulong chart_id = ChartID();
double line_price;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   line_price =  ObjectGetDouble(chart_id,h_line,OBJPROP_PRICE,0);

   Comment(line_price);
  }

Explicación:

Prever que el usuario trazará una línea horizontal en el gráfico es la primera etapa de este enfoque. Por ello, hemos desarrollado el parámetro de entrada de cadena «h_line». El usuario puede introducir manualmente o copiar el nombre exacto de la línea horizontal que ha trazado en el gráfico mediante este campo de entrada. De este modo, el asesor experto sabrá qué objeto concreto debe tratar.

A continuación, utilizamos la función ChartID() para obtener el identificador del gráfico. Cuando hay varios gráficos abiertos en MetaTrader 5, esto garantiza que el asesor experto se comunique con el gráfico correspondiente. Para obtener el nivel de precio del objeto cuyo nombre se ha indicado en la entrada, utilizamos la función ObjectGetDouble() dentro de la función OnTick(). Entre los parámetros que se pasan a esta función se encuentran el identificador del gráfico, el nombre del objeto, la propiedad a la que queremos acceder (OBJPROP_PRICE) y un valor de índice. El índice de una línea horizontal se establece en 0.

Para comprobar que el asesor experto ha localizado y extraído correctamente los datos del objeto creado manualmente, la función Comment() muestra, por último, el valor del nivel de precio del objeto directamente en el gráfico. Este método hace que el asesor experto sea versátil y fácil de usar, ya que le permite funcionar de forma dinámica con cualquier nombre de objeto que el usuario le proporcione.

Resultado:

Solo tienes que hacer clic con el botón derecho del ratón sobre el objeto que has dibujado en el gráfico y seleccionar «Propiedades» en el menú para ver cuál es su nombre. Aparecerá un campo «Nombre» en la ventana de propiedades. El nombre debe copiarse tal y como aparece y pegarse en los ajustes de entrada del asesor experto. Si lo prefieres, puedes cambiarle el nombre por otro más fácil de recordar, como «ResistanceZone» o «SupportArea».

Figura 4: Nombre del objeto

A continuación, vaya a la configuración de entrada del asesor experto y pegue el nombre del objeto en el campo de entrada. Este es el mismo parámetro de entrada que creamos anteriormente para recibir el nombre del objeto.

Figura 5: Introducción del nombre

Al hacer esto, estás conectando el objeto de gráfico creado manualmente al asesor experto. Tras introducir el nombre, el asesor experto puede identificar ese objeto concreto, recuperar su información, incluidas las coordenadas o los niveles de precio, y utilizar esos datos para decidir cuándo realizar una operación.

El siguiente paso, después de aprender a identificar los objetos del gráfico, es recuperar los datos que se encuentran en ellos. En MQL5, cada objeto de gráfico contiene datos únicos; sin embargo, el tipo de datos a los que se puede acceder varía según el tipo de objeto. Por ejemplo, dado que una línea horizontal abarca todo el gráfico a un nivel fijo, solo contiene información de precios.

Figura 6: Línea horizontal

En cambio, una línea vertical solo contiene información temporal, ya que representa un momento concreto.

Figura 7: Línea vertical


Recuperación de datos de objetos de resistencia

Los rectángulos son importantes ya que nuestro proyecto depende de su uso para mostrar las zonas de soporte y resistencia. Pueden almacenar información tanto de tiempo como de precios. Cada rectángulo contiene dos puntos y tiene sus propias coordenadas de tiempo y precio.

Figura 8: Puntos de anclaje

El asesor experto puede determinar la ubicación precisa del rectángulo en el gráfico obteniendo estas coordenadas. Como resultado, permite identificar el intervalo de tiempo y el rango de precios de la zona de soporte o resistencia trazada por el usuario, lo cual es esencial para determinar cuándo el mercado entra en esa zona y si se está gestando una posible oportunidad de operación.

Un objeto rectangular se define mediante dos puntos que representan esquinas opuestas, cada uno con coordenadas de tiempo y precio. Estos puntos pueden cambiar de función dependiendo de cómo se dibuje el rectángulo. Debido a esta flexibilidad, el asesor experto no debe presuponer que un punto de anclaje representa la parte superior o inferior de la zona al obtener datos de un rectángulo. Para identificar correctamente la zona de soporte o resistencia, debe verificar ambas coordenadas y utilizar el precio más bajo como límite inferior y el precio más alto como límite superior.

Ejemplo:

input string reistance = ""; // Resistance Object Name

ulong chart_id = ChartID();

double res_anchor1_price;
double res_anchor2_price;

double res_max_price;
double res_min_price;

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

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits);
   res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits);

   res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits);
   res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits);

   Comment("Anchor 1 Price: ",res_anchor1_price,"\nAnchor 2 Price: ",res_anchor2_price,
           "\n\nMax Resistance Price: ",res_max_price,"\nMin Resistance Price: ",res_min_price);
  }

Resultado:

Figura 9: Resistencia máxima y mínima

Explicación:

El nombre del objeto se genera como parámetro de entrada. Permite copiar y pegar manualmente el nombre del objeto rectangular del gráfico en la configuración de entrada. Los niveles de precios de los dos puntos de referencia en el rectángulo se almacenan luego en dos variables. Los puntos de anclaje 1 y 2 son las dos esquinas de cada rectángulo, y cada esquina tiene una coordenada tanto para el tiempo como para el precio. El asesor experto recupera el precio de ambos anclajes, por lo que la zona de resistencia se puede definir utilizando únicamente los valores de precio.

El asesor experto lee los valores de precio de los dos anclajes del rectángulo mediante la función ObjectGetDouble. En esta función, el punto 1 se representa con el índice 0, y el punto 2 con el índice 1. Ambos puntos de anclaje tienen sus propias coordenadas de tiempo y precio, y cada punto de anclaje representa una esquina del rectángulo. El asesor experto solo obtiene los datos de precios para cada punto de anclaje porque solo nos interesan los niveles de precios para este proyecto.

El asesor experto determina cuál de estos valores es mayor y cuál es menor después de obtenerlos. Los límites superior e inferior de la zona de resistencia están representados por los precios máximo y mínimo, respectivamente.

Gracias a esta lógica, el asesor experto leerá con precisión los límites de la zona independientemente de si el usuario dibuja el rectángulo de arriba abajo o de abajo arriba. Estas dos fronteras se utilizarán para realizar un seguimiento de la evolución de los precios durante el proyecto. Antes de realizar una operación de venta, el asesor experto esperará a que se produzca un cambio bajista en el comportamiento del mercado a medida que se acerque a esta zona de resistencia.

Por lo tanto, el asesor experto puede determinar qué punto de anclaje representa el comienzo y cuál indica el final del rectángulo comparando los valores de tiempo de las dos anclas. Al interpretar correctamente la ubicación horizontal del rectángulo en el gráfico, este paso ayuda al asesor experto a asegurarse de que solo reacciona cuando la hora actual del mercado está dentro del rango válido de la zona dibujada.

Ejemplo:
input string reistance = ""; // Resistance Object Name

ulong chart_id = ChartID();

double res_anchor1_price;
double res_anchor2_price;

double res_max_price;
double res_min_price;

long res_anchor1_time;
long res_anchor2_time;

datetime res_start_time;
datetime res_end_time;

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

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits);
   res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits);

   res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits);
   res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits);

   res_anchor1_time =  ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,0);
   res_anchor2_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,1);

   res_start_time = (datetime)MathMin(res_anchor1_time,res_anchor2_time);
   res_end_time = (datetime)MathMax(res_anchor1_time,res_anchor2_time);

   Comment("RESISTANCE START TIME: ",res_start_time,"\nRESISTANCE END TIME: ",res_end_time);
  }

Resultado:

Figura 10: Hora de inicio y fin de la resistencia

Al localizar el primer y el segundo punto (0 y 1), esta parte obtiene las coordenadas temporales del rectángulo de resistencia. Después, establece la hora más temprana como hora de inicio y la más tardía como hora de finalización, comparándolas para ver cuál ocurrió primero. Esto garantiza que, independientemente de cómo se haya dibujado el rectángulo, el asesor experto determine con precisión el intervalo de tiempo de la zona de resistencia.

 

Cambio de carácter (ChoCH) bajista

El siguiente paso consiste en detectar un cambio de carácter (ChoCH) bajista, que indique una posible reversión desde la zona de resistencia. El asesor experto realizará un seguimiento de los cambios de precios para identificar si se produce un ajuste estructural significativo y así verificarlo. En concreto, verificará que la zona de resistencia definida por el usuario contiene el máximo del mercado dentro del rango analizado. Esto garantiza que el precio interactuó realmente con la zona y que es legítimo. Se confirma un cambio de carácter (ChoCH) bajista cuando el mercado alcanza este máximo dentro de la zona de resistencia y luego cae por debajo del mínimo anterior, lo que sugiere una posible reversión a la baja desde esa zona.

Ejemplo:

input string reistance = ""; // Resistance Object Name
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // TIME-FRAME

ulong chart_id = ChartID();

double res_anchor1_price;
double res_anchor2_price;

double res_max_price;
double res_min_price;

long res_anchor1_time;
long res_anchor2_time;

datetime res_start_time;
datetime res_end_time;

int res_total_bars;
double res_close[];
double res_open[];
double res_high[];
double res_low[];
datetime res_time[];

double high;
datetime high_time;
double low;
datetime low_time;
double higher_low;
datetime higher_low_time;
double higher_high;
datetime higher_high_time;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(res_close,true);
   ArraySetAsSeries(res_open,true);
   ArraySetAsSeries(res_high,true);
   ArraySetAsSeries(res_low,true);
   ArraySetAsSeries(res_time,true);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   Comment("");
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits);
   res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits);

   res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits);
   res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits);

   res_anchor1_time =  ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,0);
   res_anchor2_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,1);

   res_start_time = (datetime)MathMin(res_anchor1_time,res_anchor2_time);
   res_end_time = (datetime)MathMax(res_anchor1_time,res_anchor2_time);

   res_total_bars = Bars(_Symbol,timeframe,TimeCurrent(),res_start_time);

   CopyOpen(_Symbol, timeframe, TimeCurrent(), res_start_time, res_open);
   CopyClose(_Symbol, timeframe, TimeCurrent(), res_start_time, res_close);
   CopyLow(_Symbol, timeframe, TimeCurrent(), res_start_time,  res_low);
   CopyHigh(_Symbol, timeframe, TimeCurrent(), res_start_time, res_high);
   CopyTime(_Symbol, timeframe, TimeCurrent(), res_start_time, res_time);

   for(int i = 4; i < res_total_bars-3; i++)
     {
      if(IsSwingHigh(res_high, i, 3))
        {
         higher_high = res_high[i];
         higher_high_time = res_time[i];

         for(int j = i; j < res_total_bars-3; j++)
           {
            if(IsSwingLow(res_low,j,3))
              {
               higher_low = res_low[j];
               higher_low_time = res_time[j];

               for(int k = j; k < res_total_bars-3; k++)
                 {
                  if(IsSwingHigh(res_high, k, 3))
                    {
                     high = res_high[k];
                     high_time = res_time[k];

                     for(int l = k; l < res_total_bars-3; l++)
                       {
                        if(IsSwingLow(res_low,l,3))
                          {
                           //   ObjectCreate(chart_id,"kk",OBJ_VLINE,0,res_time[l],0);
                           ObjectDelete(chart_id,"kk");

                           low = res_low[l];
                           low_time = res_time[l];

                           for(int m = i; m > 0; m--)
                             {
                              if(res_close[m] < higher_low && res_open[m] > higher_low)
                                {
                                 if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low)
                                   {
                                    ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low);
                                    ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed);
                                    ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2);

                                    ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high);
                                    ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed);
                                    ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2);

                                    ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low);
                                    ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed);
                                    ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2);

                                    ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low);
                                    ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed);
                                    ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2);
                                   }

                                 break;
                                }
                             }

                           break;
                          }
                       }

                     break;
                    }
                 }

               break;
              }
           }

         break;
        }
     }
  }

//+------------------------------------------------------------------+
//| 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;
  }

Resultado:

Figura 11: Cambio de carácter (ChoCH)

Explicación:

El usuario puede seleccionar primero el intervalo de tiempo del gráfico que analizará el asesor experto mediante el campo de selección de intervalo de tiempo. Puedes modificarlo para que utilice cualquier período compatible, pero por defecto utiliza el gráfico actual. Esto es significativo, ya que el marco temporal se utiliza para todos los métodos posteriores de identificación de la estructura del mercado, incluidos los marcadores de tiempo, la detección de oscilaciones y el conteo de barras. El periodo adecuado es una elección de diseño que logra un equilibrio entre ruido y capacidad de respuesta.

Los arreglos que contendrán datos históricos están configurados para funcionar como series temporales durante el proceso de inicialización. Al configurarlos como series, las barras más antiguas se ubican en índices crecientes y la barra más reciente queda en el índice 0. Al buscar fluctuaciones recientes, es sencillo recorrer la array desde la más reciente hasta la más antigua gracias a este ordenamiento. Además, la lógica de su bucle puede suponer que el índice cero es la barra finalizada más reciente porque se ajusta a la forma en que MQL5 suele mostrar los datos de series en tiempo real.

A continuación, el programa determina el número de barras que separan el inicio del rectángulo de la hora actual. El tamaño de la ventana a copiar viene determinado por el número de barras. El asesor experto copia en los arrays los datos de apertura, máximo, mínimo, cierre y tiempo para cada barra en ese rango después de conocer ese recuento. El conjunto de datos que examina el asesor experto está compuesto por esas matrices duplicadas. El asesor experto solo considera la acción del precio que realmente interactuó con la zona que dibujó el usuario, limitando los datos a la duración del rectángulo.

La detección de mínimos y máximos en las oscilaciones son dos funciones auxiliares menores que el asesor experto utiliza para identificar puntos de inflexión reales. Cada función compara varias velas situadas a la izquierda y a la derecha de una vela candidata. En ambos lados, la función de mínimo oscilante devuelve verdadero solo si el mínimo candidato es inferior a los vecinos designados. Si el máximo del candidato es mayor que los valores vecinos a ambos lados, la función de máximo oscilante devuelve verdadero. Además de garantizar que las oscilaciones observadas sean puntos de inflexión relevantes a nivel local y no ruido aleatorio, el uso de una ventana de retrospectiva en ambos lados ayuda a evitar falsos positivos derivados de fluctuaciones aisladas.

Una vez que todo está ensamblado, el procedimiento funciona de la siguiente manera. Para elegir un candidato para el máximo de oscilación, el asesor experto examina primero las matrices de precios duplicadas. Tras identificar uno, sigue buscando el siguiente mínimo, que se convierte en el candidato a un mínimo más alto. Luego busca un mínimo posterior tras buscar otro máximo. Tras combinar estos cuatro puntos de inflexión en el patrón previsto, el asesor experto busca una ruptura clara, que sería un cierre de vela por debajo del mínimo más alto que se determinó previamente. Esa ruptura se considera una prueba del cambio de carácter.

El asesor experto confirma entonces las correlaciones entre las oscilaciones, como que el mínimo más alto está, de hecho, por debajo del máximo más alto y que el máximo y el mínimo subsiguientes están posicionados de una manera coherente con la estructura prevista.

Para verificar la precisión del indicador «Change of Character» (ChoCH) bajista, a continuación hay que realizar dos comprobaciones fundamentales. El siguiente paso consiste en determinar si ninguna barra ha superado el precio de resistencia máximo desde el inicio del rectángulo. Si es así, la resistencia dejará de ser válida. Primero debemos recorrer cada barra, recopilar todos los máximos desde el inicio del rectángulo hasta la barra actual y, a continuación, determinar cuál de ellos es el más alto.  

Ejemplo:
int max_high_index;
double max_high;
if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low)
  {

   ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low);
   ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2);

   ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high);
   ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2);

   ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low);
   ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2);

   ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low);
   ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2);

   max_high_index = ArrayMaximum(res_high,0,res_total_bars);
   max_high = res_high[max_high_index];
   Comment(max_high);
  }

Resultado:

Figura 12: Máximo más alto

Explicación:

El precio más alto dentro del rango elegido y su ubicación dentro del conjunto están determinados por dos variables. En una se conserva el precio real que corresponde a ese índice, y en la otra se guarda el índice (posición) con el valor máximo más alto. La aplicación recupera entonces el valor de precio correspondiente tras localizar el índice de precio más alto del array.

La autenticidad de la zona de resistencia ahora puede verificarse utilizando dos condiciones. El primer requisito es que ninguna vela haya superado la zona de resistencia si el máximo más alto es inferior al precio de resistencia máximo. El segundo requisito es que el valor máximo del cambio de carácter debe ser superior al precio mínimo de resistencia, pero inferior al precio máximo. Esto indica un punto de reacción legítimo donde podría producirse un posible cambio bajista de carácter, ya que este ha alcanzado la zona de resistencia.

Ejemplo:
if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low)
  {

   ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low);
   ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2);

   ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high);
   ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2);

   ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low);
   ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2);

   ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low);
   ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2);

   max_high_index = ArrayMaximum(res_high,0,res_total_bars);
   max_high = res_high[max_high_index];

   if(max_high < res_max_price && higher_high > res_min_price && higher_high < res_max_price)
     {

     }
  }


Ejecución de operaciones

La ejecución de las operaciones se produce después de asegurarse de que el máximo superior se encuentra dentro de la zona de resistencia y de que se cumplen todos los requisitos previos. Esto indica que se ha producido un cambio bajista de carácter y que el mercado ha mostrado indicios de respetar la zona de resistencia. Siguiendo las directrices de la estrategia, el Asesor Experto puede ahora iniciar una posición de venta o llevar a cabo cualquier otra actividad de negociación predeterminada. El take profit (TP) se determinará mediante una relación riesgo-recompensa especificada por el usuario, y el stop loss (SL) se establecerá en el máximo del cambio de carácter en lugar de en el máximo superior.

Ejemplo:

#include <Trade/Trade.mqh>
CTrade trade;
int MagicNumber = 533915;  // Unique Number
double lot_size = 0.2; // Lot Size
double ask_price;
double take_profit;
datetime lastTradeBarTime = 0;
if(max_high < res_max_price && higher_high > res_min_price && higher_high < res_max_price)
  {

   if(res_time[1] == res_time[m] && currentBarTime != lastTradeBarTime)
     {
      take_profit = MathAbs(ask_price - ((high - ask_price) * RRR));

      trade.Sell(lot_size,_Symbol,ask_price,high,take_profit);
      lastTradeBarTime = currentBarTime;
     }

  }

Resultado:

Figura 13: Ejecución de operaciones

Explicación:

Una vez que se cumplen todos los requisitos comerciales, esta parte del software se encarga de preparar y ejecutar la operación. En primer lugar se incluye la biblioteca de operaciones, que ofrece los recursos necesarios para realizar transacciones comerciales. Para que el asesor experto pueda iniciar, modificar y cerrar operaciones, se crea una instancia de la clase de negociación. Para evitar que interfieran en las operaciones de otros asesores expertos o en posiciones manuales, cada operación realizada por este asesor experto en concreto se identifica mediante un número mágico especial. El volumen negociado por orden viene determinado por el tamaño del lote.

Para evitar que se abran varias operaciones dentro de la misma vela, el programa también especifica variables para el precio ask (que es el precio actual al que se puede ejecutar una orden de venta), el nivel take profit y una variable que almacena la hora de la transacción anterior realizada. Después de eso, registra la hora de la barra actual y obtiene el precio ask actual del símbolo.

El asesor experto verifica dos cosas antes de realizar una operación: primero, que la hora corresponda al punto de validación preciso de la zona de resistencia; y segundo, que la hora de la barra actual difiera de la hora de la barra de la operación anterior (para evitar entradas repetidas). El software determina el nivel take profit utilizando una relación riesgo-recompensa especificada por el usuario e inicia una operación de venta si se cumplen estos requisitos. El nivel take profit se determina utilizando la relación calculada, y el stop loss se establece en el punto máximo del cambio de carácter.

 

Cambio de carácter alcista en la zona de soporte

Dado que la mayor parte del razonamiento que empleamos para identificar el cambio de carácter bajista en la zona de resistencia también se aplica aquí, solo lo mencionaremos brevemente. Para evitar malentendidos o problemas, es necesario abordar algunos detalles cruciales. Por lo tanto, iremos directamente al grano y nos concentraremos principalmente en las distinciones fundamentales en esta parte.

Ejemplo:

input string support = ""; // Support Object Name
double sup_anchor1_price;
double sup_anchor2_price;

double sup_max_price;
double sup_min_price;

long sup_anchor1_time;
long sup_anchor2_time;

datetime sup_start_time;
datetime sup_end_time;

int sup_total_bars;
double sup_close[];
double sup_open[];
double sup_high[];
double sup_low[];
datetime sup_time[];

double lower_high;
datetime lower_high_time;
double lower_low;
datetime lower_low_time;

int min_low_index;
double min_low;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ArraySetAsSeries(res_close,true);
   ArraySetAsSeries(res_open,true);
   ArraySetAsSeries(res_high,true);
   ArraySetAsSeries(res_low,true);
   ArraySetAsSeries(res_time,true);

   ArraySetAsSeries(sup_close,true);
   ArraySetAsSeries(sup_open,true);
   ArraySetAsSeries(sup_high,true);
   ArraySetAsSeries(sup_low,true);
   ArraySetAsSeries(sup_time,true);

//---
   return(INIT_SUCCEEDED);
  }
sup_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,support,OBJPROP_PRICE,0),_Digits);
sup_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,support,OBJPROP_PRICE,1),_Digits);

sup_max_price = NormalizeDouble(MathMax(sup_anchor1_price,sup_anchor2_price),_Digits);
sup_min_price = NormalizeDouble(MathMin(sup_anchor1_price,sup_anchor2_price),_Digits);

sup_anchor1_time =  ObjectGetInteger(chart_id,support,OBJPROP_TIME,0);
sup_anchor2_time = ObjectGetInteger(chart_id,support,OBJPROP_TIME,1);

sup_start_time = (datetime)MathMin(sup_anchor1_time,sup_anchor2_time);
sup_end_time = (datetime)MathMax(sup_anchor1_time,sup_anchor2_time);

sup_total_bars = Bars(_Symbol,timeframe,TimeCurrent(),sup_start_time);

CopyOpen(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_open);
CopyClose(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_close);
CopyLow(_Symbol, timeframe, TimeCurrent(), sup_start_time,  sup_low);
CopyHigh(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_high);
CopyTime(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_time);

for(int i = 4; i < sup_total_bars-3; i++)
  {
   if(IsSwingLow(sup_low, i, 3))
     {
      lower_low = sup_low[i];
      lower_low_time = sup_time[i];

      for(int j = i; j < sup_total_bars-3; j++)
        {
         if(IsSwingHigh(sup_high,j,3))
           {
            lower_high = sup_high[j];
            lower_high_time = sup_time[j];

            for(int k = j; k < sup_total_bars-3; k++)
              {
               if(IsSwingLow(sup_low, k, 3))
                 {
                  low = sup_low[k];
                  low_time = sup_time[k];

                  for(int l = k; l < sup_total_bars-3; l++)
                    {
                     if(IsSwingHigh(sup_high,l,3))
                       {
                        high = sup_high[l];
                        high_time = sup_time[l];

                        for(int m = i; m > 0; m--)
                          {
                           if(sup_close[m] > lower_high && sup_open[m] < lower_high)
                             {
                              if(lower_high > lower_low && low < lower_high && low > lower_low && high > low && high > lower_high)
                                {

                                 ObjectCreate(chart_id,"LLLH",OBJ_TREND,0,lower_low_time,lower_low,lower_high_time,lower_high);
                                 ObjectSetInteger(chart_id,"LLLH",OBJPROP_COLOR,clrRed);
                                 ObjectSetInteger(chart_id,"LLLH",OBJPROP_WIDTH,2);

                                 ObjectCreate(chart_id,"LHL",OBJ_TREND,0,lower_high_time,lower_high,low_time,low);
                                 ObjectSetInteger(chart_id,"LHL",OBJPROP_COLOR,clrRed);
                                 ObjectSetInteger(chart_id,"LHL",OBJPROP_WIDTH,2);

                                 ObjectCreate(chart_id,"LH",OBJ_TREND,0,low_time,low,high_time,high);
                                 ObjectSetInteger(chart_id,"LH",OBJPROP_COLOR,clrRed);
                                 ObjectSetInteger(chart_id,"LH",OBJPROP_WIDTH,2);

                                 ObjectCreate(chart_id,"S Cross Line",OBJ_TREND,0,lower_high_time,lower_high,sup_time[m],lower_high);
                                 ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_COLOR,clrRed);
                                 ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_WIDTH,2);

                                 min_low_index = ArrayMinimum(sup_low,0,sup_total_bars);
                                 min_low = sup_low[min_low_index];

                                 if(min_low > sup_min_price && lower_low < sup_max_price && lower_low > sup_min_price)
                                   {
                                    if(sup_time[1] == sup_time[m] && currentBarTime != lastTradeBarTime)
                                      {
                                       take_profit = MathAbs(ask_price + ((ask_price - low) * RRR));

                                       trade.Buy(lot_size,_Symbol,ask_price,low,take_profit);
                                       lastTradeBarTime = currentBarTime;

                                      }
                                   }
                                }
                              break;
                             }
                          }
                        break;
                       }
                    }
                  break;
                 }
              }
            break;
           }
        }
      break;
     }
  }

Explicación:

La lógica empleada en esta sección del programa es prácticamente idéntica a la de la zona de resistencia. La principal diferencia radica en la dirección del comportamiento de los precios y la estructura del mercado. Esperamos un cambio positivo en el carácter de la zona de soporte, lo que significa que el mercado debería comenzar a subir y rechazar la zona inferior.

Para conservar los datos de las velas japonesas, en particular para la zona de soporte, se declaró un conjunto distinto de matrices, lo cual es un aspecto importante del código. Esto comprende matrices para datos de velas. Debido a que el tiempo de inicio del objeto rectángulo de soporte suele diferir del tiempo de inicio del rectángulo de resistencia, se utilizan matrices nuevas. El uso de matrices separadas garantiza que los datos analizados coincidan exactamente con la zona de soporte elegida en el gráfico. El asesor experto puede analizar cada nivel de forma independiente y, como resultado, evitar la superposición o confusión de datos entre las dos zonas.

Nota:

La estrategia presentada en este artículo tiene un enfoque puramente didáctico y orientado a proyectos y tiene como objetivo enseñar a los lectores MQL5 a través de aplicaciones prácticas en el mundo real. No es un método que garantice obtener beneficios en el trading en vivo.


Conclusión

En este artículo, hemos combinado la ejecución automatizada de operaciones con el análisis manual de gráficos para crear un asesor experto que reacciona ante elementos gráficos como las zonas de soporte y resistencia. Para salvar la brecha entre el trading automatizado y el discrecional, has aprendido a extraer e interpretar los datos de los gráficos para ejecutar operaciones de forma automática. 

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

Archivos adjuntos |
Redes neuronales en el trading: Modelo multidimensional de extremo a extremo para la previsión de series temporales (Componentes principales) Redes neuronales en el trading: Modelo multidimensional de extremo a extremo para la previsión de series temporales (Componentes principales)
Hoy les invitamos a explorar una nueva implementación de los componentes clave del framework GinAR, un algoritmo adaptativo para trabajar con series temporales de grafos. El artículo ofrece un análisis paso a paso de la arquitectura y los algoritmos de propagación directa e inversa del error.
Automatización de estrategias de trading en MQL5 (Parte 18): Estrategia de scalping «Trend Bounce» con envolventes: Ejecución de operaciones y gestión del riesgo (Parte II) Automatización de estrategias de trading en MQL5 (Parte 18): Estrategia de scalping «Trend Bounce» con envolventes: Ejecución de operaciones y gestión del riesgo (Parte II)
En este artículo, implementamos la ejecución de operaciones y la gestión de riesgos para la estrategia de scalping Envelopes Trend Bounce en MQL5. Implementamos la apertura de órdenes y controles de riesgo, como la orden de stop-loss y el dimensionamiento de posiciones. Concluimos con el backtesting y la optimización, partiendo de los fundamentos de la Parte 18.
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.
De novato a experto: Utilidad de control de parámetros De novato a experto: Utilidad de control de parámetros
Imagínese transformar las propiedades de entrada tradicionales de un EA o indicador en una interfaz de control en tiempo real directamente en el gráfico. Este análisis se basa en nuestro trabajo fundamental sobre el indicador Market Periods Synchronizer, lo que supone una evolución significativa en la forma en que visualizamos y gestionamos las estructuras de mercado en marcos temporales superiores (HTF). Aquí, convertimos ese concepto en una herramienta totalmente interactiva: un panel de control que ofrece control dinámico y una visualización mejorada de la acción del precio en múltiples períodos directamente en el gráfico. Acompáñanos a explorar cómo esta innovación transforma la manera en que los traders interactúan con sus herramientas.