O padrão Rompimento de Canal

Dmitriy Gizlyk | 5 março, 2018


Introdução

O mercado global é uma luta antiga entre vendedores e compradores. Os vendedores querem ganhar mais vendendo a um preço mais alto, enquanto que os compradores não estão dispostos a dar o seu dinheiro ganho, querendo pagar um preço mais barato. De acordo com a teoria econômica, o verdadeiro preço é encontrado no ponto de equilíbrio da oferta e demanda. Isso parece ser verdade. No entanto, o problema está na dinâmica do mercado, porque os volumes de oferta e demanda estão mudando constantemente.

A luta resulta em flutuações de preços. Essas flutuações formam canais, que os traders analisam para encontrar as tendências do mercado. Por sua vez, esses movimentos formam flutuações de uma ordem superior. Um dos primeiros sinais de reversão de tendência é o rompimento do canal de preço formado.

1. Aspectos Teóricos da Estratégia

Os canais de preços, juntamente com as linhas de tendência, referem-se às principais formas de análise gráfica. Os canais de preços mostram a tendência atual e a amplitude das flutuações de preços dentro dessa tendência. Dependendo da tendência atual, os canais podem ser ascendentes, descendentes ou lateralizados (sem tendência).

A plataforma MetaTrader 5 suporta quatro tipos de canais.

  1. Canal Equidistante
  2. Canal de Desvio Padrão
  3. Canal de Regressão
  4. Andrews Pitchfork
Mais detalhes sobre os princípios de construção dos canais e suas diferenças podem ser encontrados na Ajuda do terminal. Neste artigo, nós vamos considerar os aspectos gerais da construção do canal.

Por exemplo, vamos analisar o gráfico EURUSD M30 e as flutuações de preços.

Gráfico EURUSD M30

Ao dividir o gráfico acima em tendências, nós podemos marcar três canais de preços. Os canais equidistantes são mostrados no gráfico abaixo. Os canais descendentes são marcados com linhas vermelhas, um canal ascendente é exibido em azul. O desenho de um canal descendente começa com a borda do canal superior, que determina a tendência com base nas máximas das flutuações de preços. A borda inferior é construída nas mínimas do preço paralela a superior. A borda inferior pode ser desenhada com o desvio máximo ou médio. A construção de canais ascendentes é oposta: a borda inferior é desenhada primeiro e, em seguida, a superior. Ao desenhar um canal lateral, devemos prestar atenção à tendência anterior, porque as flutuações lateralizadas dos preços geralmente agem como uma correção para o movimento anterior, o que pode continuar após um período lateralizado.

O gráfico EURUSD M30 com os canais de preços.

Dois tipos de estratégias geralmente são usados ​​para a negociação de canais: negociação dentro do canal (uma estratégia de tendências) e negociação de rompimento do canal (uma estratégia contra tendência). Neste artigo, nós lidamos com a estratégia de rompimento do canal, que indica uma mudança de tendência.

Quando a tendência se altera, o preço sai do canal na direção oposta à tendência atual. Um canal é considerado como rompido se uma vela fecha além de seus limites.

Tenha em mente que, após o rompimento do canal, o preço retorna às suas bordas e só depois se move em uma nova direção de tendência. Este movimento geralmente leva ao desencadeamento dos stop loss dos traders antes do movimento dos preços. Para evitar isso, nós entraremos no mercado depois que o preço retornar às bordas do canal rompido. 

2. Automatizando a Busca de Padrões

Para criar um algoritmo para encontrar padrões, nós usaremos o método proposto por Dmitry Fedoseev em seu artigo [1]. Vamos usar a definição de uma formação horizontal a partir do indicador descrito nesse artigo. Seu código deve ser adicionado à classe CChannel.

Então, nós decidimos abrir uma posição após o preço retornar às bordas do canal ao invés de abrir imediatamente após o rompimento. Neste caso, pode ocorrer uma situação, quando nós esperamos o preço retornar para o canal, enquanto o EA já está procurando por um novo canal. Para habilitar a operação paralela com vários canais, a classe criada encontrará e processará apenas um padrão. Vamos unir todas as classes em um array. Assim que o padrão é processado e uma ordem adequada é aberta, a classe será excluída. Portanto, ao inicializar o indicador ZigZag em uma classe, nós precisamos chamar o indicador para cada classe. Para evitar isso, nós inicializaremos o indicador no programa principal, e somente o identificador do indicador será passado para a classe.

Além disso, para evitar a duplicação de canais, nós passaremos o horário de rompimento do canal anterior para a classe durante a inicialização. Isso assegurará que a próxima instância da classe busque um canal após a quebra da anterior.

Esta classe é exibida abaixo.

class CChannel : public CObject
  {
private:
   string            s_Symbol;      // Símbolo
   ENUM_TIMEFRAMES   e_Timeframe;   // Tempo gráfico
   int               i_Handle;      // Manipulador do indicador
   datetime          dt_LastCalc;   // Última barra calculada
   SPeackTrough      PeackTrough[]; // Picos do array de ZigZag
   int               CurCount;      // Contagem dos picos
   int               PreDir;        // Direção da perna anterior do ZigZag
   int               CurDir;        // Direção da perna atual do ZigZag
   int               RequiredCount; // Picos mínimos no canal
   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);
  };

As seguintes informações serão passadas nos parâmetros da função de inicialização da classe: o identificador do indicador, a hora de início da busca do canal, o nome do símbolo e o tempo gráfico de trabalho. No corpo da função, os dados passados ​​são salvos em variáveis ​​apropriadas e os valores iniciais são atribuídos a outras variáveis.

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

A função UnDrawChannel é chamada na função de desinicialização da classe. Ela remove do gráfico os objetos gráficos adicionados anteriormente.

As principais operações são realizadas na função Calculate. Seus parâmetros incluem referências a variáveis ​​para escrever informações sobre o rompimento de canais e da negociação aberta pelo padrão. O uso de referências nos parâmetros permite retornar da função os valores de múltiplas variáveis.

As cotações dos símbolos que começam com o último pico salvo são carregadas no array no início da função. Se o carregamento das cotações necessárias falhar, a função retorna 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;

 Depois disso, nós inicializamos as variáveis ​​de retorno.

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

Depois disso, o ciclo de processamento de dados em cada barra começa. Em primeiro lugar, verifica-se o surgimento de um novo pico do ZigZag. Se um novo pico aparecer ou o anterior é pintado, os dados são salvos no array usando as funções RefreshLast e AddNew.

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

      // nova máxima      

      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);
        }

      // novo 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);
        }

O próximo passo é verificar se a quantidade mínima de picos necessários para identificar um canal foi formada. Se sim, nós verificamos se o movimento do preço atual corresponde à formação do canal. Esta verificação é realizada na função CheckForm.

Se eles correspondem, é atribuído true à variável b_FoundChannel. Caso contrário, o pico mais antigo é descartado da lista de picos, os valores iniciais são atribuídos as variáveis ​​e a operação retorna ao início do loop.

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

Depois que o canal for encontrado, busca-se um rompimento. Se o canal estiver rompido, o valor de true é atribuído às variáveis ​​b_Breaked e breaked. O horário de abertura da vela de rompimento é salvo nas variáveis ​​dt_Breaked e breaked_time e o valor extremo da vela é salvo em d_BreakedPrice. Então, a função DrawChannel é chamada para desenhar o canal e o ponto de rompimento no gráfico. Observe que a função busca por um rompimento na direção oposta à tendência atual. Se a tendência se intensificar e o preço sair do canal na direção atual da tendência, a classe inicializa a criação de uma nova instância de classe para procurar o canal (veja a função Global SearchNewChannel abaixo).

Uma vez que a descoberta é encontrada, nós procedemos à procura de um padrão de entrada no mercado. Um sinal de entrada é gerado se o preço quebrar o canal e depois retornar às suas bordas. Um sinal de entrada adicional é o fechamento de uma vela acima do extremo do rompimento da vela para uma negociação de Compra ou abaixo dela para negociação de Venda. Este padrão é usado para entrar no mercado se o preço romper o canal em um movimento forte e se mover ainda mais sem qualquer correção.

Quando um sinal é gerado, nós escrevemos o tipo de ordem desejada para a variável "type" e também calculamos o valor de Stop Loss e salvamos para a variável apropriada. A hora de início da barra, na qual o sinal emergiu, é escrita na variável 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;
  }

O código completo da classe CChannel e suas funções estão anexadas abaixo.

3. Criação do Expert Advisor para o Testador de Estratégia

Agora que nós criamos uma classe de busca de canais, nós precisamos testar nossa estratégia. Vamos criar um Expert Advisor para testar a estratégia. Nossos canais são buscados usando o indicador Universal ZigZag, que foi descrito no artigo relacionado [3]. É por isso que nós precisamos baixar e recompilar esse indicador. Eu adicionei-o à lista de recursos por conveniência. Esta abordagem permite transferir o Expert Advisor entre os terminais sem ter que transferir o indicador. Eu também incluí no EA nossa classe CChannel e uma classe padrão para operações de negociação - CTrade.
#resource "\\Indicators\\ZigZags\\iUniZigZagSW.ex5"
#include <\\Break_of_channel_DNG\\Channel.mqh>
#include <Trade\\Trade.mqh>

Os parâmetros do Expert Advisor serão idênticos aos parâmetros do 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;

O EA possui quatro variáveis ​​globais. O seguinte está escrito nessas variáveis:

  • o manipulador do indicador
  • uma série de ponteiros para os canais (objetos da classe CChannel),
  • um ponteiro para a classe CTrade (é usado para executar as operações de negociação),
  • o horário de abertura da barra, no qual ocorreu o último rompimento.
int         zz_handle;
CChannel   *ar_Channels[];
CTrade     *Trade;
datetime    dt_last_break;

Na função OnInit do EA, nós chamamos o indicador e inicializamos as classes necessárias. A função deve retornar INIT_FAILED em caso de erro.

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 limpar a memória, nós excluímos todas as instâncias de classe usadas na função 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;
  }

O trabalho principal é realizado na função OnTick.

Nós decidimos que um canal deveria ser considerado como rompido se uma vela se fechar além dos limites. O canal será desenhado com base nos picos completamente formados do ZigZag. Assim, o EA não precisa realizar ações em todos os ticks. Portanto, a primeira coisa a fazer nesta função é verificar a abertura de uma nova barra.

void OnTick()
  {
//---
   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);

Observe que a última variável da barra é usada somente neste bloco de código, é por isso que ela não está declarada globalmente. Como você sabe, a inicialização de todas as variáveis ​​locais é realizada toda vez após o início da função correspondente. É por isso que os dados salvos na variável são perdidos no próximo início da OnTick. Para evitar a perda de dados, a variável é declarada com o modificador estático. Essa variável manterá seus valores durante as próximas chamadas da função.

O próximo passo é determinar quantos canais são armazenados no array. Se não houver canais, inicie a pesquisa a partir do último rompimento salvo.

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

Depois disso, nós trabalhamos com cada canal salvo em um loop. Primeiro, o ponteiro para o objeto da classe é verificado. Se o ponteiro não estiver correto, nós excluímos ele do array e passamos para o próximo.

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

Em seguida, a função Calculate da classe é chamada. Seus parâmetros são referências das variáveis, às quais a função retornará informações sobre os resultados das operações realizadas. Nós precisamos declarar essas variáveis ​​antes da chamada da função. Além disso, a função retorna um valor bool. Portanto, nós podemos chamar a função como uma expressão lógica para a instrução "if", e as operações futuras só serão realizadas se a função for bem-sucedida.

      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))
        {

Após a execução bem sucedida da função, salve novamente a hora da barra, na qual ocorreu o último rompimento do canal.

         dt_last_break=fmax(dt_last_break,breaked_time);

Se o último canal salvo foi rompido, inicialize a busca de um novo canal que foi formado após o último desdobramento.

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

Observe que a função SearchNewChannel armazena os últimos cinco canais. Portanto, o valor da variável "total" só cresce se houver menos de 5 canais no array. Caso contrário, reduza a variável i, que indica o índice do canal que está sendo processado.

Em seguida, nós verificamos o surgimento de um sinal para abrir uma posição e enviar uma ordem correspondente, se necessário. O Expert Advisor apenas foi projetado para fins de teste. É por isso que não tem um bloco de gerenciamento de dinheiro, então todas as negociações são abertas com o lote mínimo. Depois de enviar uma ordem, o canal processado deve ser excluído.

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


Observe dois pontos importantes neste bloco de código do programa.

1. As ordens só são abertas se o sinal surgiu não antes do que em uma vela anterior. Esta limitação é adicionada devido ao fato de que o Expert Advisor pode processar os dados históricos (por exemplo, durante a inicialização ou depois que o terminal é desconectado do servidor). Neste caso, um sinal pode aparecer com um atraso, e um nova operação pode levar a perdas incontroláveis.

2. O Expert Advisor abre ordens com um Stop Loss, enquanto que o Take Profit não está especificado. Então, quando um sinal emerge, uma posição oposta é fechada, se necessário.

Duas funções auxiliares SearchNewChannel e DeleteChannel são adicionalmente usadas no código.

A função SearchNewChannel inicializa uma nova instância da classe CChannel no array de canais. No início da função, nós verificamos o identificador do indicador. Se o identificador estiver incorreto, saia da função com o resultado "false".

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

Ao criar o Expert Advisor, eu decidi trabalhar com os últimos cinco canais. É por isso que o próximo passo é verificar o número de canais armazenados no array e excluir o mais antigo, se necessário. Os quatro canais restantes são movidos para o início do 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;
        }
     }

Se houver menos de cinco canais, o array é aumentado.

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

No final da função nós inicializamos uma nova instância da classe CChannel na última célula do array.

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

A função DeleteChannel exclui do array uma instância da classe CChannel com o índice especificado. No início da função, nós verificamos se o índice está dentro do array existente. Se não estiver, sai da função com o resultado "false".

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

Em seguida, o objeto especificado é excluído e os objetos restantes são movidos para uma célula abaixo.

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

Se o array tivesse apenas um objeto antes do início da função, o array é liberado. Caso contrário, é reduzido por um elemento.

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

O código completo do Expert Advisor está abaixo.

4. Testando o Expert Advisor

4.1. O tempo gráfico H1

Acredita-se que essas estratégias funcionem melhor em tempos gráficos maiores, uma vez que esses tempos gráficos são mais estáticos e menos sujeitos a ruídos acidentais. Portanto, o primeiro teste foi realizado no período H1. O teste foi realizado nos dados do EURUSD para 2017 sem a otimização preliminar do parâmetros.

Testes do Expert Advisor no tempo gráfico H1.Parâmetros do Expert Advisor para testes.

O primeiro teste mostrou que a estratégia é capaz de obter lucro. O EA realizou apenas 26 operações que resultaram em 10 posições abertas durante o período avaliado. 80% das posições abertas foram encerradas com lucro. Isso deu um bom crescimento do saldo. O fator de lucro de acordo com os resultados dos testes foi de 4.06. É um bom resultado.

Testando os resultados no tempo gráfico Н1.

Mas 10 posições por ano não são suficientes. Para aumentar o número de negociações, eu decidi testar o EA em um tempo gráfico menor sem alterar os parâmetros.

4.2. O tempo gráfico M1

O segundo teste foi realizado no tempo gráfico de M15 com os mesmos parâmetros.

Testes do Expert Advisor no tempo gráfico M15.Parâmetros do Expert Advisor para testes.

O número de negócios aumentou. O EA abriu 63 operações durante o período testado. Mas esse aumento não produziu um resultado qualitativo. O lucro total de todas as operações foi de $130.60 em comparação com os $133,46 em Н1. A participação dos negócios lucrativos diminuiu quase que duas vezes, para 41.27%. O gráfico de saldo resultante é mais quebrado e o fator de lucro é de 1.44, que é quase três vezes menor do que no teste anterior.

Testando os resultados no tempo gráfico M15.

4.3. Testando em Outros Símbolos

Os resultados dos testes mostraram que a estratégia melhorou no tempo gráfico H1. Para avaliar o possível uso da estratégia em outros tempos gráficos, eu realizei mais três testes adicionais. Eu usei o tempo gráfico H1, os mesmos parâmetros e o período de teste. Os resultados completos do testes estão disponíveis em anexo, as figuras principais são mostradas na tabela abaixo.

Símbolo Total de negociações Número de Negócios Negociações com lucro, % Fator de lucro Fator de recuperação; Tempo médio de duração da posição, horas 
EURUSD 10 26 80 4.06 1.78 552 
GBPUSD 2 8 50 1.47 0.23 2072 
EURGBP 5 14 0 0.0 -0.71 976 
USDJPY 6 17 83 0.72 -0.19 875 

Os melhores resultados foram obtidos no par EURGBP. Nenhuma das 5 negociações foram encerradas com lucro. Mas se analisarmos o gráfico de preços, nós podemos ver o potencial de lucro perdido para entradas de acordo com a estratégia. Como pode ser visto na imagem abaixo, a estratégia de rompimento do canal gera bons sinais de entrada. Mas precisa de uma estratégia de saída apropriada para uma operação mais estável. Isso é confirmado pelo tempo de duração de uma posição. Os testes mostraram que o tempo médio de duração de uma posição é de 550 a 2100 horas, dependendo do símbolo. As tendências do mercado podem mudar várias vezes durante um período tão longo.

Exemplo de operações realizadas pelo EA no gráfico EURGBP.

Conclusões

Um exemplo de um Expert Advisor que opera o padrão de rompimento do canal é descrito neste artigo. Os resultados dos testes mostraram que esta estratégia pode ser usada como gerador de sinais de entrada no mercado. Além disso, o teste confirmou que a estratégia funciona melhor em tempos gráficos maiores. No entanto, os sinais de saída da posição devem ser adicionados para tornar a estratégia bem sucedida. A estratégia gera sinais de entrada no mercado precisos e raros, e esses sinais não são suficientes para a obtenção de lucros. Isso muitas vezes leva a perdas de lucro flutuante e até do depósito.

O Expert Advisor não possui um módulo de gerenciamento de dinheiro ou verificação de erros, o que pode ocorrer em cálculos e operações de negociação. Portanto, o EA não é recomendado para uso em contas reais. No entanto, qualquer pessoa pode adicionar as funções necessárias a ele.

Referências

  1. O Padrão de Bandeira
  2. Gráficos e diagramas no formato HTML
  3. ZigZag Universal

Programas usados ​​no artigo:

#
 Nome
Tipo 
Descrição 
1 Break_of_channel_DNG.mq5  Expert Advisor  Um Expert Advisor para testar a estratégia
 2 Channel.mqh  Biblioteca da classe  Classe buscando por canais de preços e sinais de abertura de posição
 3 Break_of_channel_DNG.mqproj    Project description file
 4 iUniZigZagSW.ex5  Indicador  ZigZag Universal
 5 Reports.zip Zip  Relatórios de testes do Expert Advisor