Receitas MQL5 - sinais de negociação de pivô

Denis Kirichenko | 18 abril, 2017


Introdução

Este artigo continua uma série de artigos que descreve indicadores e configurações que geram sinais de negociação. Desta vez consideraremos os pivôs, isto é, pontos de reversão (pontos). Mais uma vez estará envolvida a Biblioteca padrão. Primeiro trabalharemos com o indicador de níveis de reversão e, em seguida, criaremos com base nele uma estratégia base que nós modificaremos.

É desejável que o leitor conheça a classe base para criar geradores de sinais de negociação CExpertSignal.


1. Indicador de pivôs ou níveis de reversão

Para a estratégia será utilizado um indicador que plota níveis de potencial reversão. Ao fazer isto, a plotagem ocorre só através de construção gráfica, sem o uso de objetos gráficos. A principal vantagem dessa abordagem consiste na possibilidade de acessar o indicador durante o modo de otimização. As desvantagens, por sua vez, abrangem o fato das construções gráficas não poderem ficar fora dos limites dos buffers de indicador, o que acarreta que mais tarde nas barras não haverá linhas.

Os níveis podem ser contados de várias maneiras diferentes. É possível aprofundar sobre isto no artigo Estratégia de trading com base na análise de pontos de pivô.

Por enquanto, consideraremos a abordagem clássica, onde os níveis são definidos de acordo com estas formulas:



RES é o nível i de resistência, enquanto SUP é o nível i de suporte. No total haverá: 1 nível base de reversão (PP), 6 níveis de resistência (RES) e 6 níveis de suporte (SUP).

Assim, o indicador aparece como um conjunto de níveis horizontais construídos segundo diferentes preços. A primeira execução do indicador no gráfico plotará os níveis apenas para o dia atual (Fig.1).


Fig.1 Indicador de pivôs: plotagem para o dia atual

Fig. 1 Indicador de pivôs: plotagem para o dia atual


Examinemos bloco por bloco o código do indicador, comecemos com a parte prevista.

Assim, quando se inicia um novo dia, é preciso contar novamente todos os níveis de reversão.

//--- se houver um novo dia
   if(gNewDay.isNewBar(today))
     {
      PrintFormat("Novo dia: %s",TimeToString(today));
      //--- normalização dos preços
      double d_high=NormalizeDouble(daily_rates[0].high,_Digits);
      double d_low=NormalizeDouble(daily_rates[0].low,_Digits);
      double d_close=NormalizeDouble(daily_rates[0].close,_Digits);
      //--- memorizamos os preços
      gYesterdayHigh=d_high;
      gYesterdayLow=d_low;
      gYesterdayClose=d_close;
      //--- 1) pivô: PP = (HIGH + LOW + CLOSE) / 3        
      gPivotVal=NormalizeDouble((gYesterdayHigh+gYesterdayLow+gYesterdayClose)/3.,_Digits);
      //--- 4) RES1.0 = 2*PP - LOW
      gResVal_1_0=NormalizeDouble(2.*gPivotVal-gYesterdayLow,_Digits);
      //--- 5) SUP1.0 = 2*PP – HIGH
      gSupVal_1_0=NormalizeDouble(2.*gPivotVal-gYesterdayHigh,_Digits);
      //--- 8) RES2.0 = PP + (HIGH -LOW)
      gResVal_2_0=NormalizeDouble(gPivotVal+(gYesterdayHigh-gYesterdayLow),_Digits);
      //--- 9) SUP2.0 = PP - (HIGH – LOW)
      gSupVal_2_0=NormalizeDouble(gPivotVal-(gYesterdayHigh-gYesterdayLow),_Digits);
      //--- 12) RES3.0 = 2*PP + (HIGH – 2*LOW)
      gResVal_3_0=NormalizeDouble(2.*gPivotVal+(gYesterdayHigh-2.*gYesterdayLow),_Digits);
      //--- 13) SUP3.0 = 2*PP - (2*HIGH – LOW)
      gSupVal_3_0=NormalizeDouble(2.*gPivotVal-(2.*gYesterdayHigh-gYesterdayLow),_Digits);
      //--- 2) RES0.5 = (PP + RES1.0) / 2
      gResVal_0_5=NormalizeDouble((gPivotVal+gResVal_1_0)/2.,_Digits);
      //--- 3) SUP0.5 = (PP + SUP1.0) / 2
      gSupVal_0_5=NormalizeDouble((gPivotVal+gSupVal_1_0)/2.,_Digits);
      //--- 6) RES1.5 = (RES1.0 + RES2.0) / 2
      gResVal_1_5=NormalizeDouble((gResVal_1_0+gResVal_2_0)/2.,_Digits);
      //--- 7) SUP1.5 = (SUP1.0 + SUP2.0) / 2
      gSupVal_1_5=NormalizeDouble((gSupVal_1_0+gSupVal_2_0)/2.,_Digits);
      //--- 10) RES2.5 = (RES2.0 + RES3.0) / 2
      gResVal_2_5=NormalizeDouble((gResVal_2_0+gResVal_3_0)/2.,_Digits);
      //--- 11) SUP2.5 = (SUP2.0 + SUP3.0) / 2
      gSupVal_2_5=NormalizeDouble((gSupVal_2_0+gSupVal_3_0)/2.,_Digits);
      //--- barra do início do dia atual
      gDayStart=today;
      //--- encontrar a barra inicial no timeframe ativo
      //--- como uma série temporal
      for(int bar=0;bar<rates_total;bar++)
        {
         //--- tempo da barra selecionada
         datetime curr_bar_time=time[bar];
         user_date.DateTime(curr_bar_time);
         //--- dia da barra selecionada
         datetime curr_bar_time_of_day=user_date.DateOfDay();
         //--- se a barra atual tiver acontecido de dia anteriormente
         if(curr_bar_time_of_day<gDayStart)
           {
            //--- fixar a barra inicial
            gBarStart=bar-1;
            break;
           }
        }
      //--- zerar o contador local
      prev_calc=0;
         }

Eu destaquei a vermelho as cadeias de caracteres onde os níveis são recalculados. Em seguida, para o timeframe atual, é preciso encontrar o número da barra que será o início para plotar os níveis. A variável gBarStart está encarregada pelo seu valor. Durante a pesquisa a estrutura personalizada SUserDateTime é ativada para trabalhar com as datas e o tempo, isto é, com o descendente da estrutura CDateTime.

Agora algumas palavras sobre o bloco em que são preenchidos os valores de buffer para as barras do timeframe atual.

//--- se houver uma barra no timeframe ativo
   if(gNewMinute.isNewBar(time[0]))
     {
      //--- até qual barra contar 
      int bar_limit=gBarStart;
      //--- se não for a primeira execução
      if(prev_calc>0)
         bar_limit=rates_total-prev_calc;
      //--- contagem de buffers 
      for(int bar=0;bar<=bar_limit;bar++)
        {
         //--- 1) pivô
         gBuffers[0].data[bar]=gPivotVal;
         //--- 2) RES0.5
         if(gToPlotBuffer[1])
            gBuffers[1].data[bar]=gResVal_0_5;
         //--- 3) SUP0.5
         if(gToPlotBuffer[2])
            gBuffers[2].data[bar]=gSupVal_0_5;
         //--- 4) RES1.0
         if(gToPlotBuffer[3])
            gBuffers[3].data[bar]=gResVal_1_0;
         //--- 5) SUP1.0
         if(gToPlotBuffer[4])
            gBuffers[4].data[bar]=gSupVal_1_0;
         //--- 6) RES1.5
         if(gToPlotBuffer[5])
            gBuffers[5].data[bar]=gResVal_1_5;
         //--- 7) SUP1.5
         if(gToPlotBuffer[6])
            gBuffers[6].data[bar]=gSupVal_1_5;
         //--- 8) RES2.0
         if(gToPlotBuffer[7])
            gBuffers[7].data[bar]=gResVal_2_0;
         //--- 9) SUP2.0
         if(gToPlotBuffer[8])
            gBuffers[8].data[bar]=gSupVal_2_0;
         //--- 10) RES2.5
         if(gToPlotBuffer[9])
            gBuffers[9].data[bar]=gResVal_2_5;
         //--- 11) SUP2.5
         if(gToPlotBuffer[10])
            gBuffers[10].data[bar]=gSupVal_2_5;
         //--- 12) RES3.0
         if(gToPlotBuffer[11])
            gBuffers[11].data[bar]=gResVal_3_0;
         //--- 13) SUP3.0
         if(gToPlotBuffer[12])
            gBuffers[12].data[bar]=gSupVal_3_0;
        }
         }

A contagem de buffers começa após atualizar a nova barra no gráfico onde foi executado o indicador. A amarelo selecionada a definição do número da barra sobre o qual contaremos os buffers. Para esses fins, é utilizado o contador local de barras contadas. Nós precisávamos dele porque ao iniciar o novo dia não é zerado o valor da constante prev_calculated, enquanto a reinicialização é necessária.

O código completo do indicador de pivôs está no arquivo Pivots.mq5.


2. Estratégia básica

Com base no indicador descrito tentaremos criar uma estratégia básica simples. Vamos supor que o sinal de abertura depende da localização do preço de abertura do dia em relação ao pivô central. Quando o preço atinge o nível de pivô, o sinal é confirmado.

Assim, no gráfico EURUSD, M15 (Fig.2) podemos ver que o dia 14 de janeiro de 2015 é aberto abaixo do pivô central e, logo depois, o preço intradia atinge seu nível de baixo para cima. Assim, surge um sinal de venda. Se não se ativar nem o Stop-Loss nem o Take-Profit, ao começar o novo dia fecharemos.

Fig.2 Estratégia básica: sinal de venda

Fig. 2 Estratégia básica: sinal de venda


Ligaremos os níveis de stop e lucro aos níveis de reversão do indicador de pivô. O nível intermediário de resistência Res0.5 localizado em $1.18153 será o stop para venda. O nível base de suporte Sup1.0 em $1.17301 será o stop para lucro. Mais tarde retornaremos para o dia 14 de janeiro. Entretanto, falaremos sobre o código que constituirá a base algorítmica de uma estratégia básica.


2.1 Classe de sinal CSignalPivots

Criaremos a classe de sinal que gerará os sinais a partir de vários modelos formados com base na dinâmica de preço e no indicador de níveis de reversão.

//+------------------------------------------------------------------+
//| Class CSignalPivots                                              |
//| Purpose: Classe de sinais de negociação com base em pivôs.       |
//| Descendente da classe CExpertSignal.                             |
//+------------------------------------------------------------------+
class CSignalPivots : public CExpertSignal
  {
   //--- === Data members === ---  
protected:
   CiCustom          m_pivots;            // objeto-indicador "Pivots"   
   //--- parâmetros personalizados
   bool              m_to_plot_minor;     // plotagem de níveis secundários
   double            m_pnt_near;          // tolerância 
   //--- de cálculo
   double            m_pivot_val;         // valor de pivô
   double            m_daily_open_pr;     // preço de abertura do dia atual   
   CisNewBar         m_day_new_bar;       // nova barra do timeframe diário
   //--- modelos de mercado  
   //--- 1) Modelo 0 "primeiro atingimento do nível PP" (de cima - buy, de baixo - sell)
   int               m_pattern_0;         // peso
   bool              m_pattern_0_done;    // sinal de modelo modificado
   //--- === Methods === ---  
public:
   //--- construtor/destrutor
   void              CSignalPivots(void);
   void             ~CSignalPivots(void){};
   //--- métodos de instalação de parâmetros personalizados
   void              ToPlotMinor(const bool _to_plot) {m_to_plot_minor=_to_plot;}
   void              PointsNear(const uint _near_pips);
   //--- métodos de configuração de "pesos" de modelos de mercado
   void              Pattern_0(int _val) {m_pattern_0=_val;m_pattern_0_done=false;}
   //--- método de verificação de configurações
   virtual bool      ValidationSettings(void);
   //--- método de criação de indicador e de TimeSeries
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- método para verificar se o modelo de mercado foi formado
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);
   virtual double    Direction(void);
   //--- método de definição de níveis de entrada no mercado
   virtual bool      OpenLongParams(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      OpenShortParams(double &price,double &sl,double &tp,datetime &expiration);
   //---
protected:
   //--- método de inicialização do indicador
   bool              InitCustomIndicator(CIndicators *indicators);
   //--- obtenção do valor do nível de pivô
   double            Pivot(void) {return(m_pivots.GetData(0,0));}
   //--- obtenção de valor do nível básico de resistência
   double            MajorResistance(uint _ind);
   //--- obtenção do valor do nível secundário de resistência
   double            MinorResistance(uint _ind);
   //--- obtenção do valor do nível básico de suporte
   double            MajorSupport(uint _ind);
   //--- obtenção do valor do nível secundário de suporte
   double            MinorSupport(uint _ind);
  };
  //+------------------------------------------------------------------+


No artigo "Guia Prático MQL5 - sinais de negociação de canais móveis", já usei uma abordagem em que se o preço ficar nos arredores da linha, seu toque feito pelo preço será fixado. O membro de dados m_pnt_near definirá a tolerância para o nível de reversão.

Talvez seja o modelo de sinal - atendido pela classe - que deve realizar o papel mais importante na classe. Na classe base, haverá um modelo. Além do peso (m_pattern_0), ele terá ainda um sinal de processamento durante o dia de negociação (m_pattern_0_done).

A classe de sinal base CExpertSignal é rica em métodos virtuais. E esta riqueza permite implementar uma configuração sutil da classe derivada.

Geralmente, para calcular os níveis de negociação, eu tenho redefinido 2 métodos, nomeadamente, OpenLongParams() e OpenShortParams().

Examinemos o código do primeiro método, isto é, a determinação dos valores, para os níveis de negociação, durante a compra.

//+------------------------------------------------------------------+
//| Determinação de níveis de negociação para compra                 |
//+------------------------------------------------------------------+
bool CSignalPivots::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration)
  {
   bool params_set=false;
   sl=tp=WRONG_VALUE;
//--- se Modelo 0 é levado em conta
   if(IS_PATTERN_USAGE(0))
      //--- se Modelo 0 não é modificado
      if(!m_pattern_0_done)
        {
         //--- preço de abertura - segundo o mercado
         double base_price=m_symbol.Ask();
         price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit());
         //--- preço Stop-Loss - nível Sup0.5
         sl=this.MinorSupport(0);
         if(sl==DBL_MAX)
            return false;
         //--- se definido preço Stop-Loss
         sl=m_symbol.NormalizePrice(sl);
         //--- preço Take-Profit - nível Res1.0         
         tp=this.MajorResistance(0);
         if(tp==DBL_MAX)
            return false;
         //--- se definido o preço Take-Profit
         tp=m_symbol.NormalizePrice(tp);
         expiration+=m_expiration*PeriodSeconds(m_period);
         //--- se definido o preço
         params_set=true;
         //--- modelo modificado
         m_pattern_0_done=true;
        }
//---
   return params_set;
  }
  //+------------------------------------------------------------------+


O preço Stop-Loss é calculado como o valor do primeiro nível de suporte secundário usando o método MinorSupport(). Definimos o lucro de acordo com o preço do primeiro nível base de resistência com ajuda do método MajorResistance(). Para venda, estes métodos são colocados, respetivamente, em MinorResistance() e MajorSupport().

Para que os métodos de definição de níveis de negociação na classe derivada "joguem", é necessário tornar o sinal personalizado o principal. O método de definição de níveis de negociação da classe principal tem a seguinte aparência:

//+------------------------------------------------------------------+
//| Detecting the levels for buying                                  |
//+------------------------------------------------------------------+
bool CExpertSignal::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration)
  {
   CExpertSignal *general=(m_general!=-1) ? m_filters.At(m_general) : NULL;
//---
   if(general==NULL)
     {
      //--- if a base price is not specified explicitly, take the current market price
      double base_price=(m_base_price==0.0) ? m_symbol.Ask() : m_base_price;
      price      =m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit());
      sl         =(m_stop_level==0.0) ? 0.0 : m_symbol.NormalizePrice(price-m_stop_level*PriceLevelUnit());
      tp         =(m_take_level==0.0) ? 0.0 : m_symbol.NormalizePrice(price+m_take_level*PriceLevelUnit());
      expiration+=m_expiration*PeriodSeconds(m_period);
      return(true);
     }
//---
   return(general.OpenLongParams(price,sl,tp,expiration));
  }
  //+------------------------------------------------------------------+

Se não for especificado o índice do sinal principal, os níveis terão os valores por padrão. Para evitar esta situação, no código do EA, ao inicializar o sinal, é possível escrever o seguinte:

//--- filtro CSignalPivots
   CSignalPivots *filter0=new CSignalPivots;
   if(filter0==NULL)
     {
      //--- erro
      PrintFormat(__FUNCTION__+": error creating filter0");
      return INIT_FAILED;
     }
   signal.AddFilter(filter0);
     signal.General(0);  


O método de verificação de condições de compra é apresentado como segue:

//+------------------------------------------------------------------+
//| Verificação de condição de compra                                |
//+------------------------------------------------------------------+
int CSignalPivots::LongCondition(void)
  {
   int result=0;
//--- se Modelo 0 é levado em conta
   if(IS_PATTERN_USAGE(0))
      //--- se Modelo 0 não é modificado
      if(!m_pattern_0_done)
         //--- se o dia abir acima do pivô
         if(m_daily_open_pr>m_pivot_val)
           {
            //--- preço mínimo na barra atual
            double last_low=m_low.GetData(1);
            //--- se preço obtido
            if((last_low>WRONG_VALUE) && (last_low<DBL_MAX))
               //--- se o atingimento for de cima (tendo em conta a tolerância)
               if(last_low<=(m_pivot_val+m_pnt_near))
                 {
                  result=m_pattern_0;
                  //--- no Diário
                  Print("\n---== O preço atingiu de cima o nível de pivô ==---");
                  PrintFormat("Preço: %0."+IntegerToString(m_symbol.Digits())+"f",last_low);
                  PrintFormat("Pivô: %0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val);
                  PrintFormat("Tolerância: %0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near);
                 }
           }
//---
   return result;
  }
  //+------------------------------------------------------------------+

É fácil perceber que houve atingimento de cima é verificado tendo em conta a tolerância last_low<=(m_pivot_val+m_pnt_near).

Método de definição da direção "ponderada" Direction(), entre outras coisas, verifica o processamento do modelo base.

//+------------------------------------------------------------------+
//| Definição da direção "ponderada"                                 |
//+------------------------------------------------------------------+
double CSignalPivots::Direction(void)
  {
   double result=0.;
//--- obter dados históricos do diário
   MqlRates daily_rates[];
   if(CopyRates(_Symbol,PERIOD_D1,0,1,daily_rates)<0)
      return 0.;
//--- se o Modelo 0 é modificado
   if(m_pattern_0_done)
     {
      //--- verificar a existência de um novo dia
      if(m_day_new_bar.isNewBar(daily_rates[0].time))
        {
         //--- zerar o sinalizador de processamento de modelo
         m_pattern_0_done=false;
         return 0.;
        }
     }
//--- se Modelo 0 não é modificado
   else
     {
      //--- preço de abertura do dia
      if(m_daily_open_pr!=daily_rates[0].open)
         m_daily_open_pr=daily_rates[0].open;
      //--- pivô
      double curr_pivot_val=this.Pivot();
      if(curr_pivot_val<DBL_MAX)
         if(m_pivot_val!=curr_pivot_val)
            m_pivot_val=curr_pivot_val;
     }
//--- resultado
   result=m_weight*(this.LongCondition()-this.ShortCondition());
//---
   return result;
  }
  //+------------------------------------------------------------------+


No que diz respeito aos sinais para fechamento substituímos os métodos da classe principal CloseLongParams() e CloseShortParams(). Exemplo de código para o bloco de compra:

//+------------------------------------------------------------------+
//| Determinação do nível de negociação para compra                  |
//+------------------------------------------------------------------+
bool CSignalPivots::CloseLongParams(double &price)
  {
   price=0.;
//--- se Modelo 0 é levado em conta
   if(IS_PATTERN_USAGE(0))
      //--- se Modelo 0 não é modificado
      if(!m_pattern_0_done)
        {
         price=m_symbol.Bid();
         //--- no Diário
         Print("\n---== Sinal para fechamento da compra ==---");
         PrintFormat("Preço de mercado: %0."+IntegerToString(m_symbol.Digits())+"f",price);
         return true;
        }
//--- return the result
   return false;
  }
  //+------------------------------------------------------------------+

O limiar do sinal para fechamento deve ser zerado no código do próprio Expert Advisor.

signal.ThresholdClose(0);

Assim, na classe principal não haverá verificação de acordo com direção.

//+------------------------------------------------------------------+
//| Generating a signal for closing of a long position               |
//+------------------------------------------------------------------+
bool CExpertSignal::CheckCloseLong(double &price)
  {
   bool   result   =false;
//--- the "prohibition" signal
   if(m_direction==EMPTY_VALUE)
      return(false);
//--- check of exceeding the threshold value
   if(-m_direction>=m_threshold_close)
     {
      //--- there's a signal
      result=true;
      //--- try to get the level of closing
      if(!CloseLongParams(price))
         result=false;
     }
//--- zeroize the base price
   m_base_price=0.0;
//--- return the result
   return(result);
  }
  //+------------------------------------------------------------------+

Surge a pergunta: como é que neste caso será verificado se o sinal será fechado? Ele será verificado, em primeiro lugar, graças à presença da posição (no método Processing() ), e, em segundo lugar, graças à caraterística m_pattern_0_done (nos métodos substituídos CloseLongParams() e CloseShortParams() ). Uma vez que o Expert Advisor detecta a presença da posição - ao mesmo tempo que o Modelo 0 não está modificado -, ele imediatamente tentará fechar a posição. Isto acontece no início do dia de negociação.

Nós examinamos as noções básicas da classe de sinal personalizada CSignalPivots, agora falaremos sobe a classe da estratégia.


2.2 Classe de estratégia de negociação CPivotsExpert

A classe derivada de estratégia é semelhante com o análogo para os canais móveis. A diferença é que, em primeiro lugar, não é utilizado o modo baseado em ticks, mas sim um com base em minutos. Isto permite testar rápida e profundamente o histórico para a estratégia. Em segundo lugar, há uma verificação para o fechamento da posição. Acima foi determinada a razão pela qual o EA pode fechar a posição.

O principal método-manipulador tem a seguinte aparência:

//+------------------------------------------------------------------+
//| Módulo principal                                                 |
//+------------------------------------------------------------------+
bool CPivotsExpert::Processing(void)
  {
//--- nova barra em minutos
   if(!m_minute_new_bar.isNewBar())
      return false;
//--- cálculo de direção
   m_signal.SetDirection();
//--- se não há posição
   if(!this.SelectPosition())
     {
      //--- módulo de abertura de posição
      if(this.CheckOpen())
         return true;
     }
//--- se há posição 
   else
     {
      //--- módulo de fechamento de posição
      if(this.CheckClose())
         return true;
     }
//--- se não há operações de negociação
   return false;
  }
  //+------------------------------------------------------------------+

Isso é tudo. Agora podemos executar a estratégia básica. Seu código está disponível no arquivo BasePivotsTrader.mq5.


Fig.3 Estratégia básica: venda

Fig. 3 Estratégia básica: venda


Voltamos para o exemplo do dia 14 de janeiro de 2015. Neste caso, o modelo foi idealmente elaborado. Abrimos para baixo, sobre pivô, e fechamos no nível de suporte base Sup1.0.

Foi executado no Testador de Estratégias de 07.01.2013 a 07.01.2017, no timeframe de 15 minutos, EURUSD, com os seguintes parâmetros:

  • Limiar do sinal para abertura, [0...100] = 10;
  • Peso, [0...1.0] = 1,0;
  • Volume fixado = 0,1;
  • Tolerância, pp = 15.

Afinal, a estratégia opera com resultados sustentáveis. No entanto, é um resultado negativo (Fig.4).

Fig.4 EURUSD: resultados da primeira estratégia básica para 2013-2016.

Fig. 4  EURUSD: resultados da primeira estratégia básica para 2013-2016.


A jugar pelo gráfico de desempenho, fizemos tudo errado. Tínhamos de comprar imediatamente após chegar o sinal para venda, e vender imediatamente depois de surgir o sinal de compra. É não é? Tentemos verificar. Para fazer isso, criamos uma segunda estratégia básica.

Alteraremos a parte de sinais. Então, por exemplo, a condição de compra será como segue:

//+------------------------------------------------------------------+
//| Verificação de condição de venda                                 |
//+------------------------------------------------------------------+
int CSignalPivots::LongCondition(void)
  {
   int result=0;
//--- se Modelo 0 é levado em conta
   if(IS_PATTERN_USAGE(0))
      //--- se Modelo 0 não é modificado
      if(!m_pattern_0_done)
         //--- se o dia aberto abaixo do pivô
         if(m_daily_open_pr<m_pivot_val)
           {
            //--- preço máximo na barra atual
            double last_high=m_high.GetData(1);
            //--- se preço obtido
            if((last_high>WRONG_VALUE) && (last_high<DBL_MAX))
               //--- se o atingimento for de baixo (tendo em conta a tolerância)
               if(last_high>=(m_pivot_val-m_pnt_near))
                 {
                  result=m_pattern_0;
                  //--- no Diário
                  Print("\n---== O preço atingiu de baixo o nível de pivô ==---");
                  PrintFormat("Preço: %0."+IntegerToString(m_symbol.Digits())+"f",last_high);
                  PrintFormat("Pivô: %0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val);
                  PrintFormat("Tolerância: %0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near);
                 }
           }
//---
   return result;
  }
  //+------------------------------------------------------------------+

Novamente executamos a estratégia no Testador e obtemos o seguinte resultado:

Fig.5 EURUSD: resultados da segunda estratégia básica para 2013-2016.

Fig. 5  EURUSD: resultado da segunda estratégia básica para 2013-2016.

É evidente que não houve um "espelhamento" da primeira versão. Isso é atribuído, obviamente, ao tamanho do Stop-Loss e Take-Profit. Além disso, imediatamente após iniciar o novo dia, foram fechadas as posições que, durante o dia de negociação, não ativaram nem o Profit nem o Stop.

Tentemos novamente modificar a estratégia básica, nomeadamente, sua segunda versão. Faremos com que, para compra, o nível Stop-Loss seja colocado mais adiante, isto é, antes do nível de suporte de Sup1.0, enquanto, o tamanho do Take-Profit seja limitado pelo nível de resistência de Res0.5. Para venda, colocaremos o Stop-Loss no nível de Res1.0, enquanto o Take-Profit, em Sup0.5.

Dessa maneira, por exemplo, os níveis de negociação para a compra serão determinados como segue:

//+------------------------------------------------------------------+
//| Definição dos níveis de negociação para compra                   |
//+------------------------------------------------------------------+
bool CSignalPivots::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration)
  {
   bool params_set=false;
   sl=tp=WRONG_VALUE;
//--- se Modelo 0 é levado em conta
   if(IS_PATTERN_USAGE(0))
      //--- se Modelo 0 não é modificado
      if(!m_pattern_0_done)
        {
         //--- preço de abertura — segundo o mercado
         double base_price=m_symbol.Ask();
         price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit());
         //--- preço Stop-Loss - nível Sup1.0
         sl=this.MajorSupport(0);
         if(sl==DBL_MAX)
            return false;
         //--- se definido preço Stop-Loss
         sl=m_symbol.NormalizePrice(sl);
         //--- preço Take-Profit - nível Res0.5         
         tp=this.MinorResistance(0);
         if(tp==DBL_MAX)
            return false;
         //--- se definido o preço Take-Profit
         tp=m_symbol.NormalizePrice(tp);
         expiration+=m_expiration*PeriodSeconds(m_period);
         //--- se definido o preço
         params_set=true;
         //--- modelo modificado
         m_pattern_0_done=true;
        }
//---
   return params_set;
  }
  //+------------------------------------------------------------------+


No Testador, obtemos este resultado para a terceira versão:

Fig.6  EURUSD: resultados da terceira estratégia básica para 2013-2016.

Fig. 6 EURUSD: resultados da terceira estratégia básica para 2013-2016


A imagem ficou mais o menos parecida com a imagem "espelhada" para a primeira versão. À primeira vista parece que foi encontrada a estratégia de negociação sem perdas. Mas há armadilhas, que serão discutidas na próxima seção.


3. Sobre a questão da eficiência

Se olharmos atentamente para a Fig.6, podemos reparar facilmente em que a curva de balanço cresceu de forma desigual. Houve áreas onde o balanço acumulou lucros de maneira constante. Houve áreas de abaixamento, bem como áreas onde a curva de balanço mudava para a direita.

A eficiência (robustness) é a mesma a estabilidade - do sistema de negociação - que reflete a relativa constância e competência do sistema durante um longo período.

Em geral podemos dizer que a estratégia carece de eficiência. Será que é possível aumentá-la? Tentemos.


3.1 Indicador de tendência

Na minha opinião, as regras de negociação descritas acima funcionam melhor quando, no mercado, uma tendência, isto é, um movimento dirigido. Assim, a estratégia mostrou o melhor resultado em EURUSD, entre 2014 e o início de 2015, quando o par estava a diminuir progressivamente.

Em certo sentido, então, é necessário algum tipo de filtro que permita evitar um movimento lateral. Quanto ao tema sobre a definição de uma tendência estável há muitos materiais, inclusive na seção "Artigos" no mql5.com. Pessoalmente, gostei muito do artigo "Diversas maneiras de encontrar uma tendência no MQL5". Nele, o autor oferece uma maneira confortável e, mais importante, universal para procurar a tendência.

Eu criei um indicador semelhante MaTrendCatcher. Nele, são usadas duas MA, quer dizer, a rápida e a lenta. Se a diferença entre os valores é positivo, julgaremos que a tendência é de alta. Em seguida, no indicador, as colunas do histograma serão iguais a 1. Se essa diferença for negativa, no mercado prevalecerá a tendência de baixa. As colunas serão igual a menos 1 (Fig.7.).


Fig.7  Indicador de tendência MaTrendCatcher

Fig. 7  Indicador de tendência MaTrendCatcher


Além disso, se, em relação à barra anterior, a diferença entre as MA aumentar (se tendência se fortalecer), a coluna ficará verde, se cair, vermelha.

Existe mais um recurso adicionado ao indicador: onde a diferença entre a MA seja pequena, não serão exibidas colunas. É o parâmetro personalizado do indicador "Corte, pp" (Fig.8) que define qual a diferença necessária para ocultar sua visualização.


Fig.8  Indicador de tendência MaTrendCatcher, corte de pequenas diferenças

Fig. 8 Indicador de tendência MaTrendCatcher, corte de pequenas diferenças


Assim, para efeitos de filtragem, usaremos o indicador MaTrendCatcher.

Para ativar o indicador, é necessário fazer algumas alterações no código dos arquivos do projeto. Observem que a última versão do Expert Advisor será armazenada na pasta Model.

Para efeitos da estratégia, era necessário obter a dimensão calculada da direção "ponderada". Então foi necessário criar uma classe descendente personalizada a partir da classe de sinal base.

class CExpertUserSignal : public CExpertSignal

Em seguida, na classe de sinal atualizada de níveis de reversão, apareceu um novo modelo — Modelo 1 "tendência-fase de correção-contra-tendência".

Na verdade, ela complementa o Modelo 0. Portanto, ela pode ser chamada de sub-modelo. Um pouco mais tarde, no código, citaremos este ponto.

O método de verificação de condição de compra é apresentado como segue:

//+------------------------------------------------------------------+
//| Verificação de condição de compra                                |
//+------------------------------------------------------------------+
int CSignalPivots::LongCondition(void)
  {
   int result=0;
//--- se Modelo 0 é levado em conta
   if(IS_PATTERN_USAGE(0))
      //--- se Modelo 0 não é modificado
      if(!m_pattern_0_done)
        {
         m_is_signal=false;
         //--- se o dia aberto abaixo do pivô
         if(m_daily_open_pr<m_pivot_val)
           {
            //--- preço máximo na barra anterior
            double last_high=m_high.GetData(1);
            //--- se preço obtido
            if(last_high>WRONG_VALUE && last_high<DBL_MAX)
               //--- se o atingimento for de baixo (tendo em conta a tolerância)
               if(last_high>=(m_pivot_val-m_pnt_near))
                 {
                  result=m_pattern_0;
                  m_is_signal=true;
                  //--- no Diário
                  this.Print(last_high,ORDER_TYPE_BUY);
                 }
           }
         //--- se o Modelo1 é levado em conta
         if(IS_PATTERN_USAGE(1))
           {
            //--- se na barra anterior houve tendência de alta
            if(m_trend_val>0. && m_trend_val!=EMPTY_VALUE)
              {
               //--- se houver aceleração
               if(m_trend_color==0. && m_trend_color!=EMPTY_VALUE)
                  result+=(m_pattern_1+m_speedup_allowance);
               //--- se não houver aceleração
               else
                  result+=(m_pattern_1-m_speedup_allowance);
              }
           }
        }
//---
   return result;
      }

Destacado em verde o bloco onde entra em jogo o sub-modelo.

A finalidade do cálculo é a seguinte: se entrada no mercado for realizada excluindo o sub-modelo, o resultado do sinal será igual ao peso do Modelo 0. Se o sub-modelo for levado em conta, serão possíveis as seguintes opções:

  1. entrada segundo a tendência com aceleração (prêmio pela tendência e prêmio pela aceleração);
  2. entrada segundo a tendência sem aceleração (prêmio pela tendência e multa pela aceleração);
  3. entrada contra a tendência com aceleração (penalidade pela contra-tendência e a penalidade pela aceleração);
  4. entrada contra a tendência sem aceleração (penalidade pela contra-tendência e prêmio pela aceleração).

Tal abordagem permite não reagir a um sinal fraco. Se o peso do sinal superar o valor limite, isso afetará o tamanho do volume de negociação. Na classe do EA, quanto a pivôs, exite o método CPivotsExpert::LotCoefficient():

//+------------------------------------------------------------------+
//| Coeficiente do lote                                              |
//+------------------------------------------------------------------+
double CPivotsExpert::LotCoefficient(void)
  {
   double lot_coeff=1.;
//--- sinal comum
   CExpertUserSignal *ptr_signal=this.Signal();
   if(CheckPointer(ptr_signal)==POINTER_DYNAMIC)
     {
      double dir_val=ptr_signal.GetDirection();
      lot_coeff=NormalizeDouble(MathAbs(dir_val/100.),2);
     }
//---
   return lot_coeff;
  }
  //+------------------------------------------------------------------+

Assim, se o sinal reunir 120 pontos, o volume inicial será corrigido para 1,2, e se 70 — para 0,7.

Para aplicar o coeficiente, é necessário substituir os métodos OpenLong() e OpenShort(). Em particular, o método para compra é apresentado como segue:

//+------------------------------------------------------------------+
//| Long position open or limit/stop order set                       |
//+------------------------------------------------------------------+
bool CPivotsExpert::OpenLong(double price,double sl,double tp)
  {
   if(price==EMPTY_VALUE)
      return(false);
//--- get lot for open
   double lot_coeff=this.LotCoefficient();
   double lot=LotOpenLong(price,sl);
   lot=this.NormalLot(lot_coeff*lot);
//--- check lot for open
   lot=LotCheck(lot,price,ORDER_TYPE_BUY);
   if(lot==0.0)
      return(false);
//---
   return(m_trade.Buy(lot,price,sl,tp));
  }
  //+------------------------------------------------------------------+

A ideia da formação dinâmica do tamanho do lote é bastante simples: quanto mais forte o sinal, maior o risco.


3.2 Tamanho do intervalo

Também é fácil reparar em que quando os níveis de reversão (pivôs) estão próximos uns dos outros há uma baixa volatilidade no mercado. Para se abster de operar nesses dias, temos o parâmetro "Limite de largura, pp". O Modelo 0 (e com ele o sub-modelo) será considerado aperfeiçoado, se o limiar do limite não for atingido. Isto é verificado no corpo do método Direction(). Este é um trecho de código:

//--- se defiido o limite
   if(m_wid_limit>0.)
     {
      //--- limite superior de cálculo 
      double norm_upper_limit=m_symbol.NormalizePrice(m_wid_limit+m_pivot_val);
      //--- limite superior real
      double res1_val=this.MajorResistance(0);
      if(res1_val>WRONG_VALUE && res1_val<DBL_MAX)
        {
         //--- se o limite não for atingido 
         if(res1_val<norm_upper_limit)
           {
            //--- Modelo 0 aperfeiçoado
            m_pattern_0_done=true;
            //--- no Diário
            Print("\n---== Não foi atingido o limite superior ==---");
            PrintFormat("De cálculo: %0."+IntegerToString(m_symbol.Digits())+"f",norm_upper_limit);
            PrintFormat("Real: %0."+IntegerToString(m_symbol.Digits())+"f",res1_val);
            //---
            return 0.;
           }
        }
      //--- limite inferior de cálculo 
      double norm_lower_limit=m_symbol.NormalizePrice(m_pivot_val-m_wid_limit);
      //--- limite inferior real
      double sup1_val=this.MajorSupport(0);
      if(sup1_val>WRONG_VALUE && sup1_val<DBL_MAX)
        {
         //--- se o limite não for atingido 
         if(norm_lower_limit<sup1_val)
           {
            //--- Modelo 0 aperfeiçoado
            m_pattern_0_done=true;
            //--- no Diário
            Print("\n---== Não foi atingido o limite inferior ==---");
            PrintFormat("De cálculo: %0."+IntegerToString(m_symbol.Digits())+"f",norm_lower_limit);
            PrintFormat("Real: %0."+IntegerToString(m_symbol.Digits())+"f",sup1_val);
            //---
            return 0.;
           }
        }
         }

Se a verificação de largura de intervalo não for aprovada, no diário, por exemplo, aparecerá esta entrada:

2015.08.19 00:01:00   ---== Limite superior não atingido ==---
2015.08.19 00:01:00   De cálculo: 1.10745
2015.08.19 00:01:00   Real: 1.10719
Ou seja, neste caso, para ficar completo, ao sinal lhe faltou 26 pp.


Executamos estratégia no Testador no modo de otimização. Eu usei os seguintes parâmetros de otimização:

  1. "Limite de largura, pp";
  2. "Tolerância, pp";
  3. "MA rápida";
  4. "MA lenta";
  5. "Corte, pp".

Em termos de rentabilidade, a execução melhor sucedida tem a seguinte aparência:

Fig.9 EURUSD: resultados de estratégia com uso de filtros para 2013-2016.

Fig. 9EURUSD: resultados de estrategia com uso de filtros para 2013-2016.

De um modo geral, como esperado, foram selecionados alguns sinais. A curva do balanço tornou-se mais suave.

Não vale a pena falar do que não foi possível. A estratégia, como pode ser visto no gráfico, desde 2015, gera áreas em que a curva de balanço flutua numa faixa estreita sem um crescimento explícito do lucro. Os resultados de otimização encontram-se no arquivo EURUSD_model.xml.

Vejamos os resultados noutros instrumentos.

Assim, no par USDJPY, a melhor otimização é exibida na Fig.10.

Fig.10 USDJPY: resultados de estratégia com uso de filtros para 2013-2016.

Fig. 10USDJPY: resultados de estrategia com uso de filtros de 2013 a 2016.

Agora olhemos para o ouro "spot". O melhor resultado é mostrado na Fig.11.

Fig.11 XAUUSD: resultados de estratégia com uso de filtros de 2013 a 2016.

Fig. 11XAUUSD: resultados de estratégia com uso de filtros de 2013 a 2016.

Neste período, o metal precioso foi negociado numa faixa estreita, de modo que a estratégia não teve um resultado positivo.

Quanto à libra esterlina, o melhor dos resultados é apresentado na Fig.12.

Fig.12 GBPUSD: resultados de estratégia com uso de filtros para 2013-2016.

Fig. 12GBPUSD: resultados de estrategia com uso de filtros para 2013-2016.

A libra foi negociada bem de acordo com a tendência. Mas a correção no início de 2015 estragou um pouco o resultado final.

Em geral, a estratégia trabalha melhor quando existe uma tendência de mercado.

Conclusão

Em conclusão, gostaria de salientar que a criação de uma estratégia de negociação consiste em várias etapas. Na fase inicial, é formulada a ideia de negociação. Normalmente, esta é uma hipótese que deve ser formalizada como código e, em seguida, verificada no Testador. Muitas vezes é preciso corrigir ou refinar algo durante o processo de teste. Nisso consiste o trabalho do desenvolvedor. Neste caso, para correção da estratégia de pivô, foi usada esta abordagem. Ao fazer isto, na minha opinião, o uso da POO facilita muito a tarefa do desenvolvedor.

Observe que todo o teste foi realizado no modo de otimização na MQL5 Cloud Network. Usando tecnologias baseadas em nuvem foi possível avaliar de maneira rápida e barata a eficiência das estratégias.


Localização de arquivos

Localização de arquivos

O mais conveniente é colocar os arquivos das estratégias na pasta Pivots. É preciso colocar os arquivos dos indicadores (Pivots.ex5 e MaTrendCatcher.ex5) na pasta \MQL5\Indicators\.