Patrones de reversión: Testeando el patrón "Cabeza-Hombros"

Dmitriy Gizlyk | 18 febrero, 2019

Contenido

Introducción

En el artículo «Patrones de viraje: poniendo a prueba el patrón Pico/Valle doble» hemos considerado y hemos testeado la estrategia del trading con el patrón de reversión «Pico doble». Este artículo continúa el tema ya empezado, y ahora yo propongo analizar otro patrón de reversión del análisis técnico, llamado «Cabeza-Hombros».


1. Aspectos teóricos de la formación del patrón

El patrón «Cabeza-Hombros» y «Cabeza-Hombros invertidos» pertenecen a los patrones más conocidos y ampliamente usados en el análisis gráfico clásico. Su propio nombre describe claramente su estructura gráfica. El patrón «Cabeza-Hombros» se forma al final de una tendencia alcista y genera las señales de venta del instrumento. El patrón se compone de tres picos consecutivos en el gráfico de precios. Además, el extremo medio se encuentra por encima de dos extremos adyacentes (como la cabeza por encima de los hombros). El pico medio se llama la «Cabeza», otros dos son los hombros. La línea que une los valles entre los picos del patrón se llama la línea del cuello. Además, las señales del patrón se consideran más fuertes cuando la línea del cuello está inclinada a la izquierda. El patrón «Cabeza-Hombros invertidos» es una copia espejada del patrón «Cabeza-Hombros» y es un modelo alcista.

Patrón «Cabeza-Hombros»

Con mayor frecuencia, el patrón se forma cuando ocurre una ruptura falsa del nivel de soporte/resistencia. Una pequeña corrección desde el nivel de precios (pico del hombro izquierdo) se considera por los traders como una buena posibilidad de aumentar su posición, lo que lleva a la vuelta del precio hacia la tendencia actual y a la ruptura del nivel del hombro izquierdo. Después de la ruptura del nivel actual de soporte/resistencia, la tendencia debilitada se rompe por los traders contratendenciales que aumentan su posición, lo que provoca una nueva corrección. Así, se forma la inversión de la tendencia y el precio vuelve debajo del nivel (se forma la cabeza del patrón). Otro intento de retomar la tendencia demuestra su debilidad, se forma un pequeño movimiento (hombro derecho). En este momento, los participantes del mercado notan un cambio de la tendencia. Los traders salen de sus posiciones en masa, mientras que los traders contratendenciales aumentan su posición. Eso provoca un movimiento fuerte, generando una tendencia nueva.


2. Estrategia comercial según el patrón

Igual como en el caso del patrón «Pico/valle doble», hay varias estrategias para negociar según el patrón «Cabeza-Hombros». En muchas ocasiones, ellas recuerdan las estrategias del trading según el patrón anterior.

2.1. Caso 1

La primera estrategia se basa en la ruptura de la línea del cuello. En este caso, la orden se abre después de la ruptura de la línea por el precio Close del timeframe analizado. Aquí, el Stop Loss se coloca en el nivel del extremo de la cabeza del patrón, o con un margen no muy grande. Además, hay que considerar el spread del instrumento.

Estrategia 1

Una de las desventajas de este enfoque consiste en lo siguiente: en caso de un movimiento brusco, la barra puede cerrarse lejos de la línea del cuello. Eso puede causar la pérdida de un beneficio potencial y aumentar el riesgo de pérdida en un patrón determinado.

Como una variación de este escenario, se puede considerar la apertura de una transacción después de que el precio supere una cierta distancia fija desde la línea del cuello en la dirección de la tendencia en formación.

2.2. Caso 2

La segunda versión del procesamiento de este patrón es la apertura de una posición en el rebote del precio en dirección de la línea del cuello tras su ruptura. En este escenario, el Stop Loss se coloca después del último extremo, eso lo hace más corto y permite abrir una posición del tamaño más grande con el mismo riesgo. Generalmente, eso aumenta la relación entre el beneficio potencial y la pérdida potencial.

Caso 2

Como en el caso del patrón «Pico/valle doble», no siempre se puede ver la vuelta del precio a la línea del cuello tras su ruptura. Este hecho hace que una parte significativa de los patrones sea omitida. Por tanto, cuando usamos este escenario, omitimos una parte de los patrones y nos encontramos más tiempo fuera del mercado.

Al usar ambos escenarios, se recomienda colocar el Take Profit mínimo a una distancia de la línea del cuelo igual al movimiento del precio desde la cabeza hasta la línea del cuello.

Take Profit


3. Creando el Asesor Experto para simular la estrategia

Un lector atento puede notar que los enfoques del trading según los patrones «Pico/valle doble» y «Cabeza/Hombros» sean idénticos. Pues, aprovecharemos de esta circunstancia, tomando como base el algoritmo del artículo anterior para desarrollar el nuevo EA.

La mayor parte del trabajo ya fue hecha en el artículo [1]. Para buscar los patrones, creamos la clase CHS_Pattern. Para aprovechar de los avances que alcanzamos en el artículo anterior, vamos a hacer que se deriva de la clase CPattern. De esta manera, tendremos disponibles todos los métodos de la clase padre. Aquí, añadimos sólo los elementos que faltan y reescribimos los métodos de inicialización de la clase, búsqueda del patrón y punto de entrada.

class CHS_Pattern : public CPattern
  {
protected:
   s_Extremum     s_HeadExtremum;         //Punto del extremo de la cabeza
   s_Extremum     s_StartRShoulder;       //Punto del inicia del hombro derecho

public:
                     CHS_Pattern();
                    ~CHS_Pattern();
//--- Inicialización de la clase
   virtual bool      Create(CTrends *trends, double min_correction, double max_correction);
//--- Métodos de la búsqueda del patrón y punto de entrada
   virtual bool      Search(datetime start_time);
   virtual bool      CheckSignal(int &signal, double &sl, double &tp1, double &tp2);
//---
   s_Extremum        HeadExtremum(void)      const {  return s_HeadExtremum;     }
   s_Extremum        StartRShoulder(void)    const {  return s_StartRShoulder;   }
  };

En el método de la inicialización de la clase, primero inicializamos la clase padre, luego preparamos las estructuras adicionadas.

bool CHS_Pattern::Create(CTrends *trends,double min_correction,double max_correction)
  {
   if(!CPattern::Create(trends,min_correction,max_correction))
      return false;
//---
   s_HeadExtremum.Clear();
   s_StartRShoulder.Clear();
//---
   return true;
  }

En el método de la búsqueda de patrones, primero verificamos si el número de los extremos encontrados para determinar el patrón es suficiente.

bool CHS_Pattern::Search(datetime start_time)
  {
   if(CheckPointer(C_Trends)==POINTER_INVALID || C_Trends.Total()<6)
      return false;

Luego, determinamos el número de orden del extremo correspondiente a la fecha especificada del inicio de la búsqueda de patrones. Si no ha sido formado ningún extremo después de la fecha especificada, salimos del método con el resultado false.

   int start=C_Trends.ExtremumByTime(start_time);
   if(start<0)
      return false;

Después de eso, cargamos en el ciclo los datos sobre seis últimos extremos, y comprobamos si los movimientos de precios corresponden al patrón buscado. Si el patrón ha sido encontrado, salimos del método con el resultado true. Si el patrón no ha sido encontrado, nos desplazamos a un patrón hacia el lado del momento actual y repetimos el ciclo. Si después de iterar todos los extremos, el patrón no ha sido encontrado, salimos fuera del método con el resultado false.

   b_found=false; 
   for(int i=start;i>=0;i--)
     {
      if((i+5)>=C_Trends.Total())
         continue;
      if(!C_Trends.Extremum(s_StartTrend,i+5) || !C_Trends.Extremum(s_StartCorrection,i+4) ||
         !C_Trends.Extremum(s_EndCorrection,i+3)|| !C_Trends.Extremum(s_HeadExtremum,i+2) ||
         !C_Trends.Extremum(s_StartRShoulder,i+1) || !C_Trends.Extremum(s_EndTrend,i))
         continue;
//---
      double trend=MathAbs(s_StartCorrection.Price-s_StartTrend.Price);
      double correction=MathAbs(s_StartCorrection.Price-s_EndCorrection.Price);
      double header=MathAbs(s_HeadExtremum.Price-s_EndCorrection.Price);
      double revers=MathAbs(s_HeadExtremum.Price-s_StartRShoulder.Price);
      double r_shoulder=MathAbs(s_EndTrend.Price-s_StartRShoulder.Price);
      if((correction/trend)<d_MinCorrection || header>(trend-correction)   ||
         (1-fmin(header,revers)/fmax(header,revers))>=d_MaxCorrection      ||
         (1-r_shoulder/revers)<d_MinCorrection || (1-correction/header)<d_MinCorrection)
         continue;
      b_found= true; 
//---
      break;
     }
//---
   return b_found;
  }

En el siguiente etapa, reescribimos el método de la búsqueda de los puntos de entrada. Al principio del método, comprobamos si el patrón encontrado antes se encontraba en la instancia analizada de la clase. Si el patrón no ha sido encontrado, salimos fuera del método con el resultado false.

bool CHS_Pattern::CheckSignal(int &signal, double &sl, double &tp1, double &tp2)
  {
   if(!b_found)
      return false;

En seguida, comprobamos cuántas barras han sido formadas después de la formación del patrón. Si el patrón acaba de formarse, salimos del método con el resultado false.

   string symbol=C_Trends.Symbol();
   if(symbol=="Not Initilized")
      return false;
   datetime start_time=s_EndTrend.TimeStartBar+PeriodSeconds(C_Trends.Timeframe());
   int shift=iBarShift(symbol,e_ConfirmationTF,start_time);
   if(shift<0)
      return false;

Después de eso, cargamos el historial de cotizaciones necesario y preparamos las variables auxiliares.

   MqlRates rates[];
   int total=CopyRates(symbol,e_ConfirmationTF,0,shift+1,rates);
   if(total<=0)
      return false;
//---
   signal=0;
   sl=tp1=tp2=-1;
   bool up_trend=C_Trends.IsHigh(s_EndTrend);
   int shift1=iBarShift(symbol,e_ConfirmationTF,s_EndCorrection.TimeStartBar,true);
   int shift2=iBarShift(symbol,e_ConfirmationTF,s_StartRShoulder.TimeStartBar,true);
   if(shift1<=0 || shift2<=0)
      return false;
   double koef=(s_StartRShoulder.Price-s_EndCorrection.Price)/(shift1-shift2);
   bool break_neck=false;

Luego, buscamos en el ciclo la ruptura de la línea del cuello con el siguiente ajuste del precio. En caso de la ruptura de la línea del cuello, determinamos los niveles potenciales del Take Profit. Luego, para buscar el retroceso del precio hacia la línea del cuello, monitoreamos si el precio ha alcanzado el nivel del Take Profit potencial. Si el precio alcanza el nivel de Take Profit potencial antes del retroceso hacia la línea del cuello, el patrón se considera invalido y salimos del método con el resultado false. Si se detecta un punto de entrada, determinamos el nivel de Stop Loss y salimos del método con el resultado true. Hay que tener en cuenta que un punto de entrada se considera valido si no ha sido formado antes de dos últimas barras del timeframe analizado; de lo contrario, salimos del método con el resultado false.

   for(int i=0;i<total;i++)
     {
      if(up_trend)
        {
         if((tp1>0 && rates[i].low<=tp1) || rates[i].high>s_HeadExtremum.Price)
            return false;
         double neck=koef*(shift2-shift-i)+s_StartRShoulder.Price;
         if(!break_neck)
           {
            if(rates[i].close>neck)
               continue;
            break_neck=true;
            tp1=neck-(s_HeadExtremum.Price-neck)*0.9;
            tp2=neck-(neck-s_StartTrend.Price)*0.9;
            tp1=fmax(tp1,tp2);
            continue;
           }
         if(rates[i].high>neck)
           {
            if(sl==-1)
               sl=rates[i].high;
            else
               sl=fmax(sl,rates[i].high);
           }
         if(rates[i].close>neck || sl==-1)
            continue;
         if((total-i)>2)
            return false;
//---
         signal=-1;
         break;
        }
      else
        {
         if((tp1>0 && rates[i].high>=tp1) || rates[i].low<s_HeadExtremum.Price)
            return false;
         double neck=koef*(shift2-shift-i)+s_StartRShoulder.Price;
         if(!break_neck)
           {
            if(rates[i].close<neck)
               continue;
            break_neck=true;
            tp1=neck+(neck-s_HeadExtremum.Price)*0.9;
            tp2=neck+(s_StartTrend.Price-neck)*0.9;
            tp1=fmin(tp1,tp2);
            continue;
           }
         if(rates[i].low<neck)
           {
            if(sl==-1)
               sl=rates[i].low;
            else
               sl=fmin(sl,rates[i].low);
           }
         if(rates[i].close<neck || sl==-1)
            continue;
         if((total-i)>2)
            return false;
//---
         signal=1;
         break;
        }
     }   
//---
   return true;
  }

Puede encontrar el código completo de todos los métodos y funciones en el anexo.

El código del EA para la simulación de la estrategia ha sido cogido desde el artículo [1] prácticamente sin alteraciones. Los cambios fueron introducidos sólo en la parte de la sustitución de la clase para el trabajo con el patrón. Encontrará el código completo del EA en los archivos adjuntos.

Como resultado, el EA fue probado en el período de 10 meses de 2018. Los parámetros de la prueba se muestran en las capturas de pantalla a continuación.

Parámetros de la prueba Parámetros de la prueba

Los resultados de la prueba muestran que el EA es capaz de generar ganancias en el intervalo de tiempo analizado. Más de 57% de transacciones fueron cerradas con ganancias y el factor del beneficio alcanzó 2,17. No obstante, el número de transacciones desanima: pues, en 10 meses había sólo 14 transacciones.

Resultados de las pruebas Resultados de las pruebas


4. Combinando dos patrones en un Asesor Experto

Como resultado del trabajo realizado en dos artículos, obtenemos dos EAs rentables que trabajan en los patrones gráficos de reversión. Por esa razón, es natural que para aumentar la eficacia general del trading, nos gustaría combinar ambas estrategias en un solo programa de trading. Pues, me gustaría subrayar que fue por algo por que construimos una nueva clase de la búsqueda de los patrones usando el heredero de la clase anterior. Esta técnica facilitará mucho nuestro trabajo en cuanto a la combinación de las estrategias en un EA.

Primero, identificamos las clases. La clase base CObject contiene el método virtual Type. Añadimos este método a la descripción de nuestras clases. En la clase CPattern, este método va a devolver el valor 101, y en la clase CHS_Pattern, el valor 102. En este momento usamos sólo dos patrones, por eso podemos limitarnos con constantes numéricas. Al aumentar el número de patrones, yo recomendaría usar las enumeraciones con el fin de subir la legibilidad del código.

Después de eso, completamos el método de comparación de las clases con el bloque de comparación de sus tipos. Como resultado, el código del método será así:

int CPattern::Compare(const CPattern *node,const int mode=0) const
  {
   if(Type()>node.Type())
      return -1;
   else
      if(Type()<node.Type())
         return 1;
//---
   if(s_StartTrend.TimeStartBar>node.StartTrend().TimeStartBar)
      return -1;
   else
      if(s_StartTrend.TimeStartBar<node.StartTrend().TimeStartBar)
         return 1;
//---
   if(s_StartCorrection.TimeStartBar>node.StartCorrection().TimeStartBar)
      return -1;
   else
      if(s_StartCorrection.TimeStartBar<node.StartCorrection().TimeStartBar)
         return 1;
//---
   if(s_EndCorrection.TimeStartBar>node.EndCorrection().TimeStartBar)
      return -1;
   else
      if(s_EndCorrection.TimeStartBar<node.EndCorrection().TimeStartBar)
         return 1;
//---
   return 0;
  }

Pues, ya hemos terminado la modificación del código de las clases. Vamos a proceder a la modificación del código del EA. Aquí, también habrá algunas adiciones. Primero, creamos la función que va a crear una instancia nueva de la clase correspondiente dependiendo del parámetro recibido. El código de esta función es bastante simple y consiste sólo en el uso del conmutador switch que va a llamar al método de la creación de la clase necesaria.

CPattern *NewClass(int type)
  {
   switch(type)
     {
      case 0:
        return new CPattern();
        break;
      case 1:
        return new CHS_Pattern();
        break;
     }
//---
   return NULL;
  }

Las siguientes modificaciones se hacen en la función OnTick. En este caso, las modificaciones van a alterar sólo el bloque de la búsqueda de nuevos patrones. El bloque de la búsqueda de los puntos de apertura de posiciones en los patrones ya encontrados no requiere ningunos cambios debido a la herencia de las clases.

Aquí, realizamos un ciclo en el que vamos a buscar consecutivamente los patrones en el historial: primero, de un tipo; luego, de otro. Para eso, en los puntos de creación de una nueva instancia de la clase, colocamos la llamada a la función que acabamos de añadir; y en los parámetros vamos a pasarle el número del paso actual del ciclo. El algoritmo del trabajo del EA se queda sin alteraciones.

void OnTick()
  {
//---
.........................
.........................
.........................
//---
   for(int pat=0;pat<2;pat++)
     {
      Pattern=NewClass(pat);
      if(CheckPointer(Pattern)==POINTER_INVALID)
         return;
      if(!Pattern.Create(ar_Objects.At(1),d_MinCorrection,d_MaxCorrection))
        {
         delete Pattern;
         continue;
        }
//---
      datetime ss=start_search;
      while(!IsStopped() && Pattern.Search(ss))
        {
         ss=fmax(ss,Pattern.EndTrendTime()+PeriodSeconds(e_TimeFrame));
         bool found=false;
         for(int i=2;i<ar_Objects.Total();i++)
           {
            CPattern *temp=ar_Objects.At(i);
            if(Pattern.Compare(temp,0)==0)
              {
               found=true;
               break;
              }
           }
         if(found)
            continue;
         if(!CheckPattern(Pattern))
            continue;
         if(!ar_Objects.Add(Pattern))
            continue;
         Pattern=NewClass(pat);
         if(CheckPointer(Pattern)==POINTER_INVALID)
            break;
         if(!Pattern.Create(ar_Objects.At(1),d_MinCorrection,d_MaxCorrection))
           {
            delete Pattern;
            break;
           }
        }
      if(CheckPointer(Pattern)!=POINTER_INVALID)
         delete Pattern;
     }
//---
   return;
  }

Pues, hemos terminado la modificación del código del EA. El código completo del EA nuevo se encuentra en el archivo adjunto TwoPatterns.mq5.

Después de introducir modificaciones necesarias, realizamos la prueba con los mismos parámetros.

Parámetros de la prueba Parámetros de la prueba

Los resultados de la prueba muestran que las estrategias completan una a otra. Durante el período de la prueba, el EA realizó 128 transacciones, de las cuales más de 60% fueron cerradas con ganancias. Como resultado, el factor de beneficio del trabajo del EA fue de 1,94 y el factor de recuperación fue de 3,85. Los resultados completos de la prueba se muestran en las capturas de pantalla a continuación.

Resultados de las pruebasResultados de las pruebas

5. Simulando el sistema comercial

Con el fin de comprobar la estabilidad del trabajo de nuestro sistema comercial, fue realizada la simulación del EA en 6 instrumentos sin cambiar los parámetros de la simulación.

InstrumentoBeneficioFactor de beneficio
EURUSD743,571,94
EURJPY125,131,47
GBPJPY
33,931,04
EURGBP-191,7
0,82
GBPUSD-371,050,60
USDJPY
-657,380,31

Como se puede ver de los resultados mostrados en la tabla, el EA obtubo ganancias en la mitad de los instrumentos, mientras que en el resto tuvo pérdidas. Este hecho confirma otra vez que los gráficos de precios de cada instrumento son individuales, y antes de usar el EA hay que realizar su optimización de acuerdo con las condiciones especificadas. Por ejemplo, la optimización según el parámetro "Stop Loss Backstep" permitió aumentar el beneficio en 4 instrumentos y reducir pérdidas en 2. En general, eso aumentó la rentabilidad en toda la cesta de los instrumentos. Los resultados de la tabla se muestran más abajo.

InstrumentoBeneficioFactor de beneficioStop Loss Backstep
EURUSD1020.281.78350
EURJPY532.541.52400
GBPJPY208.691.17300
EURGBP91.451.05450
GBPUSD-315.870.55100
USDJPY-453.080.33100


6. Invirtiendo las señales comerciales en los instrumentos no rentables

De acuerdo con los resultados de la simulación de nuestro EA el capítulo anterior, se destacan dos instrumentos que no fue posible hacer rentables, GBPUSD y USDJPY. Eso puede indicar en una fuerza insuficiente de los patrones analizados para la reversión de la tendencia de los instrumentos especificados. Un rebajamiento persistente del balance hace pensar en la reversión de las transacciones cuando se recibe una señal.

¿Qué objetivo podemos establecer para una transacción de reversión y dónde hay que colocar el Stop Loss? Para responder a esas preguntas, vamos a ver qué es lo que hemos obtenido como resultado de la simulación anterior. La simulación demostró que las transacciones abiertas se cerraban con más frecuencia según el Stop Loss antes de que el precio llegara al nivel del primer Take Profit. Por tanto, al invertir la transacción, podemos cambiar de posición Stop Loss y Take Profit 1.

El estudio detallado de los gráficos de las transacciones no rentables demostró que en el rango de precios de las señales falsas, a menudo se formaban los canales. En el análisis gráfico clásico, los canales son las figuras de la continuación de la tendencia. Por eso, podemos esperar un movimiento del precio conmensurable con el movimiento anterior. Al usar el código del método CheckSignal de nuestra clase de la búsqueda de los patrones, es fácil de notar que el tamaño del movimiento anterior es fijado por Take Profit 2. Lo único que tenemos que hacer es colocar Take Profit 2 a la misma distancia desde el precio actual, sólo en la dirección opuesta.

Este enfoque nos permite invertir las transacciones introduciendo los cambios sólo en el código del EA, sin alterar el código de las clases de la búsqueda de los patrones.

Para implementar esta funcionalidad, añadimos a nuestro EA el parámetro ReverseTrade, que servirá de la bandera de activación/desactivación de la función del trading de reversión.

input bool  ReverseTrade   =  true; //Reverse Deals

No obstante, no podemos cambiar la lógica de la apertura de las transacciones añadiendo un solo parámetro. Vamos a modificar la función CheckPattern. En el bloque para declarar las variables locales, añadimos la variable temp, la que vamos a usar como un repositorio temporal durante el cambio de los precios de Stop Loss y Take Profit 1.

bool CheckPattern(CPattern *pattern)
  {
   int signal=0;
   double sl=-1, tp1=-1, tp2=-1;
   if(!pattern.CheckSignal(signal,sl,tp1,tp2))
      return false;
//---
   double price=0;
   double to_close=100;
   double temp=0;
//---

Luego, en el cuerpo del conmutador switch, añadimos la verificación del estado del indicador ReverseTrade. Si el indicador es false, usamos la lógica antigua. Al usar el trading de reversión, alternamos el valor de Stop Loss y Take Profit 1. Luego, recalculamos los valores de los Take Profit en puntos del instrumento, y tansferimos los valores obtenidas a la clase CLimitTakeProfit. Después de concluir con éxito todas las iteraciones, abrimos una transacción opuesta a la señal recibida.

//---
   switch(signal)
     {
      case 1:
        CLimitTakeProfit::Clear();
        if(!ReverseTrade)
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
           if((tp1-price)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(CLimitTakeProfit::AddTakeProfit((uint)((tp1-price)/_Point),(fabs(tp1-tp2)>=_Point ? 50 : 100)))
                 to_close-=(fabs(tp1-tp2)>=_Point ? 50 : 100);
           if(to_close>0 && (tp2-price)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(!CLimitTakeProfit::AddTakeProfit((uint)((tp2-price)/_Point),to_close))
                 return false;
           if(Trade.Buy(d_Lot,_Symbol,price,sl-i_SL*_Point,0,NULL))
              return false;
          }
        else
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
           temp=tp1;
           tp1=sl-i_SL*_Point;
           sl=temp;
           tp1=(price-tp1)/_Point;
           tp2=(tp2-price)/_Point;
           if(tp1>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(CLimitTakeProfit::AddTakeProfit((uint)(tp1),((tp2-tp1)>=1? 50 : 100)))
                 to_close-=((tp2-tp1)>=1 ? 50 : 100);
           if(to_close>0 && tp2>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(!CLimitTakeProfit::AddTakeProfit((uint)(tp2),to_close))
                 return false;
           if(Trade.Sell(d_Lot,_Symbol,price,sl,0,NULL))
              return false;
          }
        break;
      case -1:
        CLimitTakeProfit::Clear();
        if(!ReverseTrade)
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
           if((price-tp1)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(CLimitTakeProfit::AddTakeProfit((uint)((price-tp1)/_Point),(fabs(tp1-tp2)>=_Point ? 50 : 100)))
                 to_close-=(fabs(tp1-tp2)>=_Point ? 50 : 100);
           if(to_close>0 && (price-tp2)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(!CLimitTakeProfit::AddTakeProfit((uint)((price-tp2)/_Point),to_close))
                 return false;
           if(Trade.Sell(d_Lot,_Symbol,price,sl+i_SL*_Point,0,NULL))
              return false;
          }
        else
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
           temp=tp1;
           tp1=sl+i_SL*_Point;
           sl=temp;
           tp1=(tp1-price)/_Point;
           tp2=(price-tp2)/_Point;
           if(tp1>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(CLimitTakeProfit::AddTakeProfit((uint)(tp1),((tp2-tp1)>=1 ? 50 : 100)))
                 to_close-=((tp2-tp1)>=1 ? 50 : 100);
           if(to_close>0 && tp2>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(!CLimitTakeProfit::AddTakeProfit((uint)(tp2),to_close))
                 return false;
           if(Trade.Buy(d_Lot,_Symbol,price,sl,0,NULL))
              return false;
          }
        break;
     }
//---
   return true;
  }

Aquí, terminamos de completar el EA. El código completo de todas las clases y funciones se encuentra en el anexo.

Hagamos la simulación de la función del trading de reversión. La primera prueba será en el instrumento GBPUSD, usando el mismo intervalo temporal y timeframe. Los parámetros del EA se muestran en las capturas de pantalla a continuación.

GBPUSD prueba de reversiónGBPUSD prueba de reversión

Los resultados de la prueba han demostrado el éxito de esta decisión. El EA con la función activada del trading de reversión demostró ganancias en el intervalo temporal establecido. De 127 transacciones realizadas, 95 (75,59%) se cerraron con ganancias. Factor de beneficio - 2,35. Los resultados completos de la prueba se muestran en las capturas de pantalla a continuación.

GBPUSD resultado de la prueba de reversiónGBPUSD resultado de la prueba de reversión

Para consolidar el resultado obtenido, hagamos la misma simulación para el instrumento USDJPY. Los parámetros de la prueba se muestran en las capturas de pantalla a continuación.

USDJPY prueba de reversiónUSDJPY prueba de reversión

La segunda prueba de la función de reversión también salió con éxito. De 108 transacciones realizadas, 66 (61,11%) se cerraron con ganancias (factor de beneficio - 2,45). Los resultados completos de la prueba se muestran en las capturas de pantalla a continuación.

USDJPY resultado de la prueba de reversiónUSDJPY resultado de la prueba de reversión

La prueba demostró que las posiciones de las estrategias no rentables pueden convertirse en rentables gracias a la reversión de posiciones. Pero no se debe olvidar que no siempre una estrategia rentable en un instrumento trae beneficios en otro.


Conclusiones

El trabajo realizado en el presente artículo demuestra la viabilidad de las estrategias basadas en el uso de los patrones clásicos del análisis gráfico. Como resultado, hemos obtenido un EA capaz de generar beneficios en un intervalo temporal prolongado, en diferentes instrumentos financieros. No obstante, al usar las estrategias comerciales, es necesario tomar en cuenta las particularidades del instrumento, porque a menudo una estrategia rentable en unas condiciones puede causar pérdidas en otras condiciones. A veces, la reversión de las transacciones ayuda a solucionar la situación. Pero hay que tomar en cuenta las particularidades de las condiciones comerciales.

Está claro que éstos son sólo los primeros pasos en el desarrollo de un sistema comercial rentable. Adicionalmente, se puede optimizar otros parámetros del EA y completar su funcionalidad (paso del Stop Loss al «punto muerto» (breakeven), Trailing Stop, etc.) Obviamente, no recomendaría usar el EA en los instrumentos que ya se sabe que no son rentables.

Espero que mi experiencia ayude al lector a desarrollar su propia estrategia comercial.

Es obvio que el EA presentado en el artículo ejecuta solamente las funciones de demostración de la estrategia, y habrá que mejorarlo para usar en los mercados reales.

Referencias

  1. Patrones de viraje: poniendo a prueba el patrón Pico/Valle doble
  2. Implementación de Take Profit en forma de órdenes limitadas sin cambiar el código fuente del EA

Los programas usados en el artículo:

# Nombre Tipo Descripción
1 ZigZag.mqh Librería de la clase Clase del indicador Zig Zag
2 Trends.mqh Librería de la clase Clase de la búsqueda de la tendencia
3 Pattern.mqh Librería de la clase Clase para trabajar con el patrón «Pico/valle doble"
4 HS_Pattern Librería de la clase Clase para trabajar con el patrón «Cabeza-Hombros"
5 LimitTakeProfit.mqh Librería de la clase Clase del reemplazo del TP de la orden por las órdenes limitadas
6 Header.mqh Librería Archivo de encabezados del EA
7 Head-Shoulders.mq5 Asesor Experto Asesor Experto a base de la estrategia «Cabeza-Hombros»
8 TwoPatterns.mq5 Asesor Experto Asesor Experto con el desarrollo de dos patrones