Patrón de ruptura del canal

Dmitriy Gizlyk | 16 febrero, 2018


Introducción

El mercado mundial es una lucha eterna entre los compradores y los vendedores. Los primeros quieren ganar y vender cuanto más caro, mientras que los segundos no quieren ceder sus dinero, intentando comprar cuanto más barato. La teoría de la economía dice que el precio verdadero de la mercancía se encuentra en el punto de la igualdad de la demanda y la oferta. No se puede negar eso. Pero la dinámica del mercado echa «el aceite al fuego», es decir, los volúmenes de la demanda y la oferta se encuentran en un constante movimiento.

El resultado de esta lucha se representa por las fluctuaciones de los precios Ellas forman unos canales entre los cuales los traders buscan las tendencias del mercado. En su lugar, estos movimientos forman las fluctuaciones del grado mayor. Uno de los primeros indicios de la terminación de la tendencia es la ruptura del canal formado.

1. Aspectos teóricos de la estrategia

Los canales de precios, junto con las líneas de tendencia, pertenecen a las figuras principales del análisis gráfico. Los canales de precios muestran la tendencia actual y la amlitud de las fluctuaciones dentro de ella. Por tanto, dependiendo de la tendencia actual, los canales pueden ser ascendientes, descendientes o laterales (flat).

El terminal MetaTrader 5 permite a sus usuarios construir 4 tipos de canales.

  1. Canal equidistante.
  2. Canal de desviaciones estándar.
  3. Canal de regresión.
  4. Tridente Andrews.

En la documentación del terminal se puede encontrar la información detallada sobre los modos de construirlos y sobre diferencias entre ellos. En este artículo, nos centraremos en los aspectos generales de la construcción de los canales.

Como ejemplo, analizaremos el gráfico EURUSD M30 en el se puede ver las fluctuaciones de los precios.

Gráfico EURUSD M30

Si dividimos este gráfico en las tendencias, se puede destacar 3 canales de precios. Los canales equidistantes se muestran en el gráfico de abajo. Los canales descendientes están marcados con líneas rojas, el canal ascendiente, con líneas azules. Para construir un canal descendiente, primero se construye el borde superior del canal que determina la tendencia a base de los máximos de las fluctuaciones. El borde inferior se construye en paralelo usando los extremos inferiores del precio. Se puede construir el borde inferior usando tanto la desviación máxima, como la desviación media. Para un canal ascendiente, todo lo contrario: Primero se construye el borde inferior, y luego el superior. Al construir un canal lateral, es necesario fijarse en la tendencia anterior, porque las fluctuaciones del flat con mayor frecuencia suelen ser una corrección del movimiento anterior y preceden a su continuación.

El gráfico EURUSD M30 con canales de precios.

Cuando trabajamos con los canales, normalmente usamos la estrategia de dos tipos: trading en el canal (estrategia tendencial) y trading de la ruptura del canal (estrategia contratendencial). En este artículo, vamos a analizar el patrón de la ruptura del canal que indica en el cambio de la tendencia.

El cambio de la tendencia se acompaña con la salida del precio fuera del canal en la dirección opuesta a la tendencia actual. La ruptura del canal se considera el cierre de la vela fuera de sus límites.

Al construir la estrategia, se debe tomar en cuenta que el precio vuelve a los límites del canal después de su ruptura, y sólo después va en la dirección de la nueva tendencia. Este movimiento suelen llamar el disparo (triggering) de los Stop Loss de los traders antes del movimiento del precio. Vamos a usar esta observación: entramos en el mercado después de que el precio vuelva a los límites del canal roto. 

2. Automatización de la búsqueda de los patrones

Para crear el algoritmo de la búsqueda de los patrones, usaremos el método propuesto por Dmitry Fedoseev en el artículo [1]. Del indicador propuesto en su artículo, vamos a usar sólo la definición de la formación horizontal, y añadimos su código a la clase CChannel.

Antes hemos decidido abrir la posición no inmediatamente después de la ruptura del canal, sino después de que el precio vuelva a los bordes del canal. En este caso, es muy probable que surja la situación cuando en un canal vamos a esperar el retroceso del precio hacia los bordes, y al mismo tiempo el Asesor Experto (EA) tendrá que buscar un canal nuevo. Para resolver el problema del trabajo simultáneo con varios canales, hagamos que la clase que creamos va a buscar y procesar sólo un patrón. Reunimos todas las clases de los canales en un array. Después de procesar y abrir la orden usando el patrón, la instancia de la clase se elimina. Por consiguiente, inicializando el indicador del zig-zag en la clase, necesitaremos llamar al indicador para cada clase. Para evitar eso, vamos a inicializar el indicador en el programa principal, y a la clase le pasaremos sólo su manejador (handle).

Además de eso, para evitar el duplicado de los canales, vamos a pasar la hora de la ruptura del canal anterior a la clase durante la inicialización. Así, cada siguiente instancia de la clase va a buscar el canal después de la ruptura del anterior.

De esta manera, nuestra clase tendrá el siguiente aspecto.

class CChannel : public CObject
  {
private:
   string            s_Symbol;      // Symbol
   ENUM_TIMEFRAMES   e_Timeframe;   // Timeframe
   int               i_Handle;      // Indicator's handle
   datetime          dt_LastCalc;   // Last calculated bar
   SPeackTrough      PeackTrough[]; // Array of ZigZag's peacks
   int               CurCount;      // Count of peaks
   int               PreDir;        // Previus ZigZag's leg direction
   int               CurDir;        // Current ZigZag's leg direction
   int               RequiredCount; // Minimal peacks in channel
   double            d_Diff;        
   bool              b_FoundChannel;
   bool              b_Breaked;
   datetime          dt_Breaked;
   double            d_BreakedPrice;                     

   void              RefreshLast(datetime time,double v);
   void              AddNew(datetime time,double v,int d);
   bool              CheckForm(double base);
   double            GetRessistPrice(SPeackTrough &start_peack, datetime time);
   double            GetSupportPrice(SPeackTrough &start_peack, datetime time);
   bool              DrawChannel(MqlRates &break_bar);
   bool              DrawChannel(void);
   bool              UnDrawChannel(void);

public:
                     CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe);
                    ~CChannel();
   bool              Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time,bool &breaked,datetime &breaked_time);
  };

En los parámetros de la función de la inicialización de la clase vamos a pasar el handle del indicador, la hora del inicio de la búsqueda de canales, nombre del instrumento y el marco temporal (timeframe). En el cuerpo de la función, guardamos los datos pasados en las variables correspondientes y asignamos los valores iniciales a las demás variables..

CChannel::CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe) : RequiredCount(4),
                                                                                             CurCount(0),
                                                                                             CurDir(0),
                                                                                             PreDir(0),
                                                                                             d_Diff(0.1),
                                                                                             b_Breaked(false),
                                                                                             dt_Breaked(0),
                                                                                             b_FoundChannel(false)
  {
   i_Handle=handle;
   dt_LastCalc=fmax(start_time-1,0);
   s_Symbol=symbol;
   e_Timeframe=timeframe;
  }

En las funciones de la deinicialización, llamamos a la función UnDrawChannel que eliminará todos los objetos gráficos desde el gráfico.

El trabajo principal va a realizarse en la función Calculate. Sus parámetros son las referencias a las variables para escribir la información sobre la ruptura del canal y la transacción que se abre usando el patrón. El uso de las referencias a las variables en los parámetros permite devolver los valores de varias variables desde la función.

Al principio de la función, cargamos al array las cotizaciones del instrumento a partir del último pico guardado. En caso del error de la carga de las cotizaciones necesarias, la función devolverá false.

bool CChannel::Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time, bool &breaked,datetime &breaked_time)
  {
   MqlRates rates[];
   CurCount=ArraySize(PeackTrough);
   if(CurCount>0)
     {
      dt_LastCalc=PeackTrough[CurCount-1].Bar;
      CurDir=PeackTrough[CurCount-1].Dir;
     }
   int total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc-PeriodSeconds(e_Timeframe),0),TimeCurrent(),rates);
   if(total<=0)
      return false;

 Después de eso, realizamos la inicialización de las variables a devolver.

   stop_loss=-1;
   breaked=b_Breaked;
   breaked_time=dt_Breaked;
   deal_time=0;

Luego iniciamos el ciclo del procesamiento de los datos en cada barra. Lo primero que hacemos aquí es comprobar la aparición de un nuevo pico del zig-zag. En caso de la aparición del nuevo pico o el redibujado del anterior, los datos se guardan en el array a través de la función RefreshLast и AddNew.

   for(int i=0;i<total;i++)
     {
      if(rates[i].time>dt_LastCalc)
        {
         dt_LastCalc=rates[i].time;
         PreDir=CurDir;
        }
      else
         continue;

      // new max      

      double lhb[2];
      if(CopyBuffer(i_Handle,4,total-i-1,2,lhb)<=0)
         return false;

      if(lhb[0]!=lhb[1])
        {
         if(CurDir==1)
            RefreshLast(rates[i].time,rates[i].high);
         else
            AddNew(rates[i].time,rates[i].high,1);
        }

      // new min

      double llb[2];
      if(CopyBuffer(i_Handle,5,total-i-1,2,llb)<=0)
         return false;

      if(llb[0]!=llb[1])
        {
         if(CurDir==-1)
            RefreshLast(rates[i].time,rates[i].low);
         else
            AddNew(rates[i].time,rates[i].low,-1);
        }

En el siguiente paso comprobamos si se alcanzado el número mínimo de los picos para la identificación del canal. Si es así, comprobamos la correspondencia del movimiento de precios actual de la información del canal. Esta comprobación se ejecuta en la función CheckForm.

Si la correspondencia ha sido encontrada, a la variable b_FoundChannel se le asigna el valor true. En caso contrario, el pico más antiguo se descarta de la lista de los picos, a las variables se le asignan los valores iniciales, y volvemos al principio del ciclo.

      double base=(CurCount>=2 ? MathAbs(PeackTrough[1].Val-PeackTrough[0].Val) : 0);
   
      if(CurCount>=RequiredCount && !b_FoundChannel)
        {
         if(CurDir!=PreDir)
           {
            if(CheckForm(base))
              {
               b_FoundChannel=true;
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
        }

Después de encontrar el canal, pasamos a la búsqueda de su ruptura. En caso de la ruptura, asignamos el valor true a las variables b_Breaked y breaked. En las variables dt_Breaked y breaked_time guardamos la hora de la apertura de la vela de la ruptura, y en la variable d_BreakedPrice guardamos su valor extremo. Luego llamamos a la función DrawChannel que dibujará nuestro canal y el punto de su ruptura en el gráfico. Preste atención: se busca la ruptura sólo en la dirección opuesta a la tendencia actual. Si la tendencia se hace más fuerte y la salida del canal se realiza en dirección de la tendencia actual, la clase inicializa la creación de una nueva instancia de la clase para la búsqueda del canal (la función global SearchNewChannel la consideraremos más tarde en detalle).

Después de definir la ruptura del canal, empezamos a buscar el patrón de la entrada en la posición. Como ya se ha dicho antes, después de la ruptura del canal, la clase espera la vuelta del precio a sus bordes y luego espera la señal de la entrada en el mercado. He hecho que la señal adicional de la entrada sea el cierre de la vela de turno por encima del extremo de la vela de la ruptura para Buy, y por debajo, para Sell. Este patrón se usa para la entrada en el mercado cuando el precio rompe el canal con un fuerte movimiento y se va sin la corrección.

Cuando aparece la señal, introducimos el tipo necesario de la orden en la variable type, calculamos y guardamos el valor del Stop Loss en la variable correspondiente. Introducimos la hora del inicio de la barra de la llegada de la señal en la variable deal_time.

      if(b_FoundChannel)
        {
         if(PeackTrough[0].Dir==1)
           {
            if(PeackTrough[0].Val>PeackTrough[2].Val)
              {
               if(!b_Breaked)
                 {
                  if((rates[i].close-GetRessistPrice(PeackTrough[0],rates[i].time))>=(d_Diff*base))
                    {
                     b_Breaked=breaked=true;
                     dt_Breaked=breaked_time=rates[i].time;
                     d_BreakedPrice=rates[i].high;
                     DrawChannel(rates[i]);
                     continue;
                    }
                  if(CurCount>4 && PeackTrough[CurCount-1].Dir==1 && (GetRessistPrice(PeackTrough[1],rates[i].time)-PeackTrough[CurCount-1].Val)>0)
                    {
                     int channels=ArraySize(ar_Channels);
                     if(ar_Channels[channels-1]==GetPointer(this))
                       {
                        SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe));
                       }
                    }
                 }
               else
                 {
                  if(rates[i].time<=dt_Breaked)
                     continue;
                  //---
                  double res_price=GetRessistPrice(PeackTrough[0],rates[i].time);
                  if(((rates[i].low-res_price)<=0 && (rates[i].close-res_price)>0 && (rates[i].close-res_price)<=(d_Diff*base)) || rates[i].close>d_BreakedPrice)
                    {
                     type=ORDER_TYPE_BUY;
                     stop_loss=res_price-base*(1+d_Diff);
                     deal_time=rates[i].time;
                     return true;
                    }
                 }
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
         else
           {
            if(PeackTrough[0].Val<PeackTrough[2].Val)
              {
               if(!b_Breaked)
                 {
                  if((GetSupportPrice(PeackTrough[0],rates[i].time)-rates[i].close)>=(d_Diff*base))
                    {
                     b_Breaked=breaked=true;
                     dt_Breaked=breaked_time=rates[i].time;
                     d_BreakedPrice=rates[i].low;
                     DrawChannel(rates[i]);
                     continue;
                    }
                  if(CurCount>4 && PeackTrough[CurCount-1].Dir==-1 && (PeackTrough[CurCount-1].Val-GetSupportPrice(PeackTrough[1],rates[i].time))>0)
                    {
                     int channels=ArraySize(ar_Channels);
                     if(ar_Channels[channels-1]==GetPointer(this))
                       {
                        SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe));
                       }
                    }
                 }
               else
                 {
                  if(rates[i].time<=dt_Breaked)
                     continue;
                  double sup_price=GetSupportPrice(PeackTrough[0],rates[i].time);
                  if(((sup_price-rates[i].high)<=0 && (sup_price-rates[i].close)>0 && (sup_price-rates[i].close)<=(d_Diff*base)) || rates[i].close<d_BreakedPrice)
                    {
                     type=ORDER_TYPE_SELL;
                     stop_loss=sup_price+base*(1+d_Diff);
                     deal_time=rates[i].time;
                     return true;
                    }
                 }
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
        }
     }
   return b_Breaked;
  }

Puede encontrar el código completo de la clase CChannel y de sus funciones en los archivos adjuntos.

3. Creando el Asesor Experto para probar la estrategia

Ahora, cuando ya hemos creado la clase para buscar los canales, hay que probar nuestra estrategia. Para realizar la prueba, vamos a crear un pequeño EA. Recordamos que los canales se buscan por los picos del zig-zag universal del artículo [3]. Por eso, hay que cargar y recompilar este indicador. Lo he introducido en la lista de los recursos para el uso más cómodo del EA. Este enfoque da la posibilidad de traspasar el EA entre los terminales sin traspasar el indicador. Además, he incluido nuestra clase CChannel y la clase estándar para realizar las operaciones comerciales CTrade en nuestro Asesor Experto.

#resource "\\Indicators\\ZigZags\\iUniZigZagSW.ex5"
#include <\\Break_of_channel_DNG\\Channel.mqh>
#include <Trade\\Trade.mqh>

Los parámetros del EA serán iguales que los parámetros del indicador.

input ESorce               SrcSelect      =  Src_HighLow;
input EDirection           DirSelect      =  Dir_NBars;
input int                  RSIPeriod      =  14;
input ENUM_APPLIED_PRICE   RSIPrice       =  PRICE_CLOSE;
input int                  MAPeriod       =  14;
input int                  MAShift        =  0;
input ENUM_MA_METHOD       MAMethod       =  MODE_SMA;
input ENUM_APPLIED_PRICE   MAPrice        =  PRICE_CLOSE;
input int                  CCIPeriod      =  14;
input ENUM_APPLIED_PRICE   CCIPrice       =  PRICE_TYPICAL;
input int                  ZZPeriod       =  50;

Habrá sólo cuatro variables globales. Ahí vamos a escribir lo siguiente:

  • Handle de nuestro indicador
  • Array de punteros a nuestros canales (objetos de la clase CChannel),
  • Puntero a la clase CTrade (se usan para realizar operaciones comerciales)
  • Hora de la apertura de la barra de la última ruptura
int         zz_handle;
CChannel   *ar_Channels[];
CTrade     *Trade;
datetime    dt_last_break;

Lamamos al indicador en la función OnInit del EA e inicializamos las clases necesaras. En caso del error, la función devolverá INIT_FAILED.

int OnInit()
  {
//---
   zz_handle=iCustom(Symbol(),Period(),"::Indicators\\ZigZags\\iUniZigZagSW",SrcSelect,
                                             DirSelect,
                                             RSIPeriod,
                                             RSIPrice,
                                             MAPeriod,
                                             MAShift,
                                             MAMethod,
                                             MAPrice,
                                             CCIPeriod,
                                             CCIPrice,
                                             ZZPeriod);
                                             
   if(zz_handle==INVALID_HANDLE){
      Alert("Error load indicator");
      return(INIT_FAILED);
   }  
//---
   Trade=new CTrade();
   if(CheckPointer(Trade)==POINTER_INVALID)
      return INIT_FAILED;
//---
   dt_last_break=0;
//---
   return(INIT_SUCCEEDED);
  }

Para limpiar la memoria, eliminamos todas las instancias de las clases utilizadas, usando la función OnDeinit.

void OnDeinit(const int reason)
  {
//---
   int total=ArraySize(ar_Channels);
   for(int i=0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])!=POINTER_INVALID)
         delete ar_Channels[i];
     }
   ArrayFree(ar_Channels);
   if(CheckPointer(Trade)!=POINTER_INVALID)
      delete Trade;
  }

Como siempre, el trabajo principal va a realizarse en la función OnTick.

Antes, ya hemos acordado que vamos a considerar sólo el cierre de la vela fuera del canal como la ruptura. La construcción del canal también va a realizarse por los picos formados del zig-zag. Por tanto, el EA no tiene que hacer ningunas acciones en cada tick. Por eso, lo primero que tenemos que hacer en nuestra función es comprobar la llegada del momento de la apertura de nueva barra.

void OnStart()
  {
//---
   static datetime last_bar=0;
   if(last_bar>=SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE))
      return;
   last_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);

Obsérvese que la variable last_bar se usa sólo en este bloque del código, por eso no la he declarado como global.. Pero como se sabe, la inicalización de todas las variables locales se realiza cada vez después del inicio de las funciones correspondientes. Por consiguiente, en el momento del siguiente inicio de OnTick perderemos los datos guardados en la variable. Para que eso no ocurra, la variable está declarada con el modificador static. Esta variable va a guardar sus valores durante los siguientes inicios de la función.

En el siguiente paso, determinaremos cuántos canales se guardan en nuestro array. Si no hay ninguno, iniciamos la búsqueda del canal desde la última ruptura guardada.

   int total=ArraySize(ar_Channels);
   if(total==0)
      if(SearchNewChannel(dt_last_break))
         total++;

Después de eso, realizamos cíclicamente el trabajo con cada canal guardado. Primero, comprobamos el puntero al objeto de la clase. Si el puntero no es correcto, lo eliminamos del array y pasamos al siguiente.

   for(int i=0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])==POINTER_INVALID)
        {
         DeleteChannel(i);
         i--;
         total--;
         continue;
        }

Luego, llamamos a la función Calculate de la clase. Sus parámetros son las referencias a las variables en las que la función va a devolver la información sobre los resultados de las operaciones realizadas. Por eso tenemos que declarar estas variables antes de llamar a la función. Aparte de eso, la función devuelve el valor lógico. Por tanto, podemos llamarla como una expresión lógica del operador if, y las futuras operaciones van a realizarse sólo en caso de la ejecución de la función con éxito.

      ENUM_ORDER_TYPE type;
      double stop_loss=-1;
      bool breaked=false;
      datetime breaked_time=0;
      datetime deal_time=0;
      if(ar_Channels[i].Calculate(type,stop_loss,deal_time,breaked,breaked_time))
        {

Después de ejecutar la función con éxito, volvemos a guardar la hora de la barra de la última ruptura del canal.

         dt_last_break=fmax(dt_last_break,breaked_time);

Si el último canal ha sido roto, inicializamos la búsqueda de nuevo canal desde la última ruptura.

         if(breaked && i==(total-1))
            if(SearchNewChannel(breaked_time))
              { 
               if(total>=5)
                  i--;
               else
                  total++;
              }

Obsérvese que la función SearchNewChannel está construida en el guardado de 5 últimos canales. Por tanto, el valor de la variable total crece sólo si en el array había menos de 5 canales. De lo contrario, reducimos la variable i que indica en el número del canal procesado.

Luego, comprobamos si ha llegado la señal de apertura de la posición, y si es necesario, enviamos la orden correspondiente. Puesto que el EA está construido sólo para el testeo de la estrategia, no incluye el bloque de la Gestión de capital, y todas las operaciones van a abrirse con el lote mínimo. Después del envío de la orden, eliminamos el canal procesado.

         if(deal_time>=0 && stop_loss>=0)
           {
            int bars=Bars(_Symbol,PERIOD_CURRENT,deal_time,TimeCurrent());
            double lot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
            switch(type)
              {
               case ORDER_TYPE_BUY:
                 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
                    Trade.PositionClose(_Symbol);
                 if(bars<=2)
                    Trade.Buy(lot,_Symbol,0,fmax(stop_loss,0));
                 break;
               case ORDER_TYPE_SELL:
                 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
                    Trade.PositionClose(_Symbol);
                 if(bars<=2)
                    Trade.Sell(lot,_Symbol,0,fmax(stop_loss,0));
                 break;
              }
            DeleteChannel(i);
            i--;
            total--;
           }
        }
     }
  }

Por favor, preste atención en dos momentos bastante importantes en este bloque del código del programa.

Primero: Las órdenes se cierran sólo si la señal ha llegado no antes que en la vela anterior. Eso se debe al hecho de que el EA puede procesar los datos históricos (por ejemplo, en caso de la inicialización o después del fallo de la conexión del terminal con el servidor). En este caso, la señal llegará con retardo, y la transacción abierta puede provocar unas pérdidas no controladas.

Segundo: El EA abre las órdenes especificando el Stop Loss, pero no indica el Take Profit. Por eso, cuando llega una señal para abrir la posición, si es necesario se cierra las posición opuesta.

Como ha podido notar, en el código se usan dos funciones adicionales: SearchNewChannel y DeleteChannel.

La primera función, SearchNewChannel, inicializa la nueva instancia de la clase CChannel en nuestro array de canales. Comprobamos la presencia del handle del indicador al principio de la función. Si el handle es incorrecto, salimos de la función con el resultado false.

bool SearchNewChannel(datetime time)
  {
   if(zz_handle==INVALID_HANDLE)
      return false;

Desde el principio, cuando construía el canal, decidí trabajar sólo con cinco últimos canales. Por eso, en el siguiente paso comprobamos el número de los canales guardados en el arrray, y si es necesario, eliminamos el más antiguo. Los cuatro restantes se trasladan al principio del array.

   int total=ArraySize(ar_Channels);
   if(total>4)
     {
      for(int i=0;i<total-4;i++)
        {
         if(CheckPointer(ar_Channels[i])!=POINTER_INVALID)
            delete ar_Channels[i];
        }
      for(int i=0;i<4;i++)
         ar_Channels[i]=ar_Channels[total-4+i];
      if(total>5)
        {
         if(ArrayResize(ar_Channels,5)>0)
            total=5;
         else
            return false;
        }
     }

Si el array contiene menos de 5 canales, simplemente lo aumentamos.

   else
     {
      if(ArrayResize(ar_Channels,total+1)>0)
         total++;
      else
         return false;
     }

Al final de la función inicializamos la nueva instancia de la clase CChannel en la última celda del array.

   ar_Channels[total-1]=new CChannel(zz_handle,time,_Symbol,PERIOD_CURRENT);
   return (CheckPointer(ar_Channels[total-1])!=POINTER_INVALID);
  }

La función DeleteChannel elimina la instancia de la clase CChannel desde el array según el número especificado. Al principio de la función, comprobamos si este número se encuentra dentro del array existente. Si no es así, salimos de la función con el resultado false.

bool DeleteChannel(int pos)
  {
   int total=ArraySize(ar_Channels);
   if(pos<0 || pos>=total)
      return false;

Luego, eliminamos el objeto especificado y desplazamos los siguientes a 1 celda hacia abajo.

   delete ar_Channels[pos];
   for(int i=pos;i<total-1;i++)
      ar_Channels[i]=ar_Channels[i+1];

Si el array contenía sólo un objeto antes del inicio de la función, liberamos el array. De lo contrario, lo reducimos a 1 elemento.

   if(total==1)
     {
      ArrayFree(ar_Channels);
      return true;
     }
   return (ArrayResize(ar_Channels,total-1)>0);
  }

El código completo se encuentra en los archivos adjuntos.

4. Simulación del Asesor Experto

4.1. Timeframe H1

Es costumbre considerar que estas estrategias trabajan mejor en los timeframes mayores ya que son más estáticos y están menos sujetos al ruido accidental. Por eso, he realizado la primera prueba de la estrategia en el timeframe H1. La prueba se realizaba en EURUSD durante 2017 sin la optimización previa de los parámetros.

Simulación del EA en H1Parámetros del EA para la simulación.

La primera prueba ha demostrado la capacidad de la estrategia de traer beneficios. Durante el período de prueba, el EA realizó sólo 26 transacciones, y como resultado fueron abiertas 10 posiciones. 80% de las posiciones abiertas se cerraron con beneficios, Esto dio un crecimiento bastante estable del balance. Según los resultados de la simulación, el Profit Factor llegó a ser de 4,06. No es un resultado malo.

Resultados de la simulación en H1.

Pero 10 posiciones durante un año no es mucho. Para aumentar el número de transacciones, decidí realizar la simulación usando un timeframe menor sin cambiar los parámetros.

4.1. Timeframe M15

La segunda prueba de la estrategia fue hecha en el timeframeM15 sin cambiar otros parámetros.

Simulación del EA en M15.Parámetros del EA para la simulación.

Como se esperaba, el EA aumentó el número de transacciones. Durante el período de prueba, el EA abrió 63 posiciones. Pero este aumento no produjo un resultado cualitativo. El beneficio total de las operaciones realizadas fue de $130.60 contra $133.46 en Н1, la parte de posiciones rentables se redujo casi en 2 veces, cayendo hasta 41,27%. Como resultado, obtuvimos un gráfico del balance más quebrado y el factor de beneficio fue de 1,44, casi dos veces menos que en la prueba anterior.

Resultados de la simulación en M15.

4.3. Simulación en otros instrumentos

Los resultados de las pruebas de arriba han demostrado unívocamente que la eficiencia de la estrategia es mayor en H1. Para estimar las perspectivas de su uso en diferentes instrumentos, realice tres pruebas más. Se usaba el timeframe H1 sin cambiar los parámetros y el período de la prueba. Puede estudiar los resultados completos de la simulación en el archivo adjunto, los indicadores principales están agrupados en la tabla de abajo.

SímboloNúmero de tradesNúmero de transaccionesTrades rentables, %Factor de beneficioFactor de recuperaciónTiempo medio de mantenimiento del trade, horas 
EURUSD1026804.061.78552 
GBPUSD28501.470.232072 
EURGBP51400.0-0.71976 
USDJPY617830.72-0.19875 

Como se puede observar, el peor resultado ha sido en el par EURGBP. Ninguno de 5 trades ha sido cerrado con beneficio. Pero si analizamos el gráfico, se puede observar un potencial del beneficio perdido en las entradas según la estrategia. Como se puede ver en la captura de pantalla de abajo, la estrategia de la ruptura del canal genera buenas señales de entrada, pero para un trabajo estable hay que mejorar la estrategia de la salida de la posición. Eso se confirma también con el la duración del mantenimiento de la posición. Según los resultados de las pruebas realizadas, dependiendo del instrumento, el tiempo medio del mantenimiento de la posición oscila entre 550 y 2100 horas. Durante este período, el mercado cambia sus tendencias más de una vez.

Ejemplo de realización de transacciones por el EA en el gráfico EURGBP.

Conclusión

En este artículo, ha sido descrito el ejemplo del EA para tradear usando el patrón de ruptura del canal. Los resultados de la prueba han demostrado que esta estrategia puede ser usada como generador de las señales de entrada en el mercado. La prueba ha confirmado que la estrategia trabaja mejor en los timeframes mayores. Pero para un trading eficaz, hay que añadirle las señales de salida de la posición. La estrategia genera las señales de entrada bastante precisas pero raras, aunque sus señales no son suficientes para fijar el beneficio a tiempo. Eso a menudo lleva a las pérdidas del beneficio flotante y del depósito.

El EA desarrollado no incluye el bloque de la Gestión de capital ni las comprobaciones de la presencia de los errores que puede ocurrir durante la ejecución de los cálculos y operaciones comerciales. Por eso no se recomienda usarlo en las cuentas reales. Sin embargo, quien lo desea, puede insertar las funciones necesarias en él.

Vínculos

  1. Patrón Bandera
  2. Gráficos y diagramas en formato HTML
  3. ZigZag universal

Programas utilizados en el artículo:

#
 Nombre
Tipo 
Descripción 
1Break_of_channel_DNG.mq5 Asesor Experto Asesor Experto para probar la estrategia
 2Channel.mqh Librería de la clase Clase para buscar los canales de precios y señales de apertura de las posiciones
 3Break_of_channel_DNG.mqproj  Archivo de la descripción del proyecto
 4iUniZigZagSW.ex5 Indicador ZigZag universal
 5Reports.zip Archivo comprimido Informes del testeo del EA