English Русский 中文 Español Deutsch 日本語
Receitas MQL5 - sinais de negociação de pivô

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

MetaTrader 5Exemplos | 18 abril 2017, 10:50
2 853 0
Denis Kirichenko
Denis Kirichenko

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\.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2853

Arquivos anexados |
Pivots.zip (43.18 KB)
Optimization.zip (40.72 KB)
Os Expert Advisors prontos a partir do Assistente MQL5 funcionam no MetaTrader 4 Os Expert Advisors prontos a partir do Assistente MQL5 funcionam no MetaTrader 4
No artigo, propõe-se um emulador simples do ambiente de negociação MetaTrader 5 para o MetaTrader 4. Com sua ajuda é possível transferir e adaptar as classes de negociação da biblioteca padrão. Como resultado, os EAs gerados no Assistente do MetaTrader 5 podem ser compilados e executados sem alterações no MetaTrader 4.
Tendência universal com GUI Tendência universal com GUI
No artigo, criaremos um indicador de tendência universal com base numa série de indicadores padrão. Será desenvolvida uma interface gráfica do usuário para selecionar o tipo de indicador e seus parâmetros. Exibiremos o indicador numa janela separada com fileiras de ícones coloridos.
Análise comparativa de 10 estratégias de tendência Análise comparativa de 10 estratégias de tendência
No artigo, além de una breve visão geral de 10 estratégias de tendência, são levados a cabo seu teste e análise comparativa. Com base nos resultados obtidos, é feita uma conclusão geral sobre a viabilidade, vantagens e desvantagens da negociação de tendência.
Interfaces Gráficas X: Atualizações para a tabela Renderizada e otimização do código (build 10) Interfaces Gráficas X: Atualizações para a tabela Renderizada e otimização do código (build 10)
Nós continuamos a complementar a tabela renderizada (CCanvasTable) com novas funcionalidades. A tabela terá agora: o realce das linhas quando o mouse estiver em cima; possibilidade de adicionar um array de ícones para cada célula e um método para trocá-los; possibilidade de definir ou modificar o texto da célula durante a execução do programa, e muito mais.