Handler de evento "nova barra"

6 fevereiro 2014, 16:08
Konstantin Gruzdev
9
6 008

Introdução

Os autores dos indicadores e especialistas sempre estiveram interessados em escrever o código compacto em termos de tempo de execução. Você pode resolver este problema a partir de diferentes ângulos. A partir deste amplo tópico neste artigo, cobriremos o problema, que aparentemente foi solucionado: verifique por uma nova barra. Este é um meio bastante comum para limitar os loops de cálculo, já que todos os cálculos e operações de negócio são realizados uma vez durante a geração de uma nova barra no gráfico. Então, o que será discutido:

  • Meios para detectar novas barras.
  • Atalhos de algorítimos existentes da detecção de nova barra.
  • Criação do método universal de detecção de nova barra.
  • Nuances e formas de aplicação deste método.
  • Evento NewBar e handler deste evento - OnNewBar().

Meios para detectar novas barras

Agora, existe uma solução aceitável de como detectar uma nova barra. Por exemplo, elas podem ser encontradas nas Limitações e verificações no Expert Advisor, nos artigos Os princípios do cálculo econômico dos indicadores ou aqui. A propósito, eu recomendo o estudo destes materiais. Será mais fácil entender do que estou falando.

Estes materiais usam o princípio de rastreio de tempo de abertura da barra atualmente não finalizada. Esta é uma forma muito fácil e confiável. Existem outros métodos para detectar uma nova barra.

Por exemplo, em indicadores personalizados para este propósito, você pode usar dois parâmetros de entrada da função OnCalculate(): rates_total e prev_calculated. Limitação deste método - é basicamente o fato de que pode ser usado apenas para detectar uma nova barra no gráfico atual e apenas em indicadores. Se você quiser encontrar uma nova barra em outro período ou símbolo, é necessário usar técnicas adicionais.

Ou, por exemplo, você pode tentar obter uma nova barra em seu primeiro tick, quando Volume de tick = 1 ou quando todos os preços de barra forem iguais a: Aberto = Alto = Baixo = Fechado. Estes métodos podem ser bem usados para teste, mas na negociação real eles geralmente falham. Isto devido ao momento entre o primeiro e segundo tick algumas vezes não ser suficiente para obter a barra gerada. Isso é percebido especialmente em um forte movimento de mercado ou quando a qualidade da conexão de internet for ruim.

Existe um método para detectar uma nova barra com base na função TimeCurrent(). A propósito, é um bom meio se você precisar detectar uma nova barra para o gráfico atual. Usaremos no final deste artigo.

Bem, você pode até perguntar para um vizinho: "Ei, existe uma barra nova?". Imagino o que ele vai responder: Bem, ok, vamos parar sua escolha no princípio de tempo de rastreio de abertura da barra atual não terminada para detectar uma nova barra. Sua simplicidade e confiabilidade são verdadeiramente testadas e verdadeiras.

Ponto de partida

Nos materiais mencionados acima, as coisas não estão ruins para a detecção de uma nova barra. Mas...

Para entender o que é este "mas", como ponto de partida (ou um protótipo) consideraremos uma boa e simples função de trabalho para detectar uma nova barra do artigo Limitações e verificações em Expert Advisors. Aqui está:

//+------------------------------------------------------------------+
//| Returns true if a new bar has appeared for a symbol/period pair  |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;
//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }
//--- if we passed to this line, then the bar is not new; return false
   return(false);
  }

A função deste protótipo está, na verdade, funcionando e tem pleno direito de existir. Mas...

Análise da função do protótipo

Eu copie esta função no código fonte do meu (é claro) maior e melhor do melhor Expert Advisor. Não funcionou. Comecei a investigar. Abaixo, minhas considerações sobre esta função.

Cabeçalho da função. Vamos dar uma olhada em tudo consequentemente. Vamos começar com o cabeçalho da função:

bool isNewBar()

Eu gosto do cabeçalho da função, é muito simples, intuitivo e não há necessidade de lidar com o passado nos parâmetros. Seria interessante usá-lo nesta forma no futuro.

Número de restrição de chamadas. Após o cabeçalho, é a primeira frase que inicia a variável estática:

//---  memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;

Tudo parece muito bem. Mas...

O problema é que usamos a variável estática. Os tópicos de ajuda nos dizem: Os tópicos de ajuda nos dizem:

Variáveis estáticas existem a partir do momento da execução do programa e são iniciadas apenas uma vez antes da função OnInit() ser chamada. Se os valores iniciais não forem especificados, as variáveis da classe de armazenamento estáticas estão considerando valores iniciais zero.

Variáveis locais, declaradas com a palavra-chave estática retém seus valores através da função lifetime. Com cada próxima chamada de função, tais variáveis locais contém os valores que tinham durante a chamada anterior.

Se você chamar esta função de protótipo de um lugar, então, temos o que precisamos. Mas, se quisermos usar esta função, por exemplo, novamente em outro lugar no mesmo loop de cálculo, ele sempre retornará falso, o que significa que não há uma barra. E isso não será sempre verdadeiro. A variável estática neste caso, impõe um limite artificial no número de chamadas da função de protótipo.

Questão da universalidade. A seguinte frase na função de protótipo parece com o seguinte:

//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

é lógico, que para obter o tempo de abertura da última barra não finalizada, a função SeriesInfoInteger() é usada com o modificador SERIES_LASTBAR_DATE.

Nossa função de protótipo isNewBar() foi originalmente concebida como simples e por predefinição usa o instrumento de negócio e período do gráfico atual. Isso é aceitável se você quiser rastrear a nova barra apenas no gráfico atual. Mas, o que fazer se eu uso o período e instrumento não apenas para o gráfico atual? Além disso, e se eu tiver um gráfico intrincado? Por exemplo, e se eu decidir plotar Renko ou Kagi?

A falta de possibilidade pode nos limitar severamente. Mais adiante, descobriremos como resolver isso.

Manuseio errôneo. Vamos dar uma olhada detalhada na função SeriesInfoInteger(). O que você acha que retornaria, se a função executasse quando o gráfico não tiver sido formado ainda? Tal situação pode surgir, por exemplo, se você tiver anexado seu Expert Advisor ou indicador a um gráfico e decidiu mudar o período ou símbolo, ou quando reiniciar o terminal. E o que acontecerá durante estas atualizações de séries de tempo? Incidentalmente, existe um aviso no tópico de Ajuda:

Disponibilidade de dados

A presença de dados no formato HCC ou mesmo no formato HC pronto para uso nem sempre denota a disponibilidade absoluta destes dados a serem exibidos no gráfico ou para usar nos programas MQL5.

Ao acessar os dados de preço ou valores do indicador a partir do programa MQL5, lembre-se que sua disponibilidade em determinado momento do tempo ou começando a partir de um determinado momento do tempo não é garantida. Isso deve-se ao fato de que, para economizar recursos do sistema, a cópia completa dos dados necessários para um programa mql5 não é armazenada no MetaTrader 5; é fornecido apenas o acesso direto ao banco de dados do terminal.

O histórico de preço para todos os períodos de tempo é construído a partir de dados comuns do formato HCC, e qualquer atualização de dados a partir de um servidor, leva à atualização dos dados para todos os períodos de tempo e ao recálculo dos indicadores. Devido a isso, o acesso aos dados pode ser negado, mesmo se estes dados estivessem disponíveis instantes atrás.

Então, o que esse função retornará? Para evitar essa incerteza, você precisa de alguma forma começar a obter erros de consulta do tempo de abertura da última barra inacabada.

Possibilidade de inicialização. Vamos seguir em frente. Considere as seguintes frases da nossa função protótipo:

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

Aqui tudo está definitivamente bem. Porém, existe uma nuance. Você percebeu a frase acima de Ajuda: "Variáveis estáticas existem a partir do momento da execução do programa e são iniciadas apenas uma vez antes da função OnInit() ser chamada"? E se nós precisarmos mais tempo para iniciar a variável last_time? Mais precisamente, o que fazer se você quiser criar artificialmente uma situação da primeira chamada? Ou alguma outra situação? é fácil fazer perguntas quando você sabe as respostas. Mas, veremos mais sobre isso depois.

Número de barras. A seguir, nossa função protótipo terá o seguinte código:

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }

Como você pode ver, um programador como eu pode fazer de tal modo que o operador if "surpreenda" o terminal do cliente e Strategy Tester. O fato é que, logicamente, o tempo passado é sempre menor que o presente. Ou seja, last_time < lastbar_time. Devido ao erro acidental do programa, tenho a máquina do tempo, ou mais exatamente - o oposto aconteceu: lastbar_time < last_time. Que surpresa! No geral, tal paradoxo do tempo é fácil detectar e exibir uma mensagem de erro.

Mas, toda nuvem tem um lado bom. Enquanto assistia minha "máquina do tempo", descobri que entre as chamadas isNewBar(), não apenas uma nova barra pode aparecer. Quanto menor o período de tempo, maior a probabilidade de ocorrência de diversas barras entre as chamadas de função. Pode haver muitos motivos para isso: começando a partir do longo tempo de computação e finalização de uma ausência temporária de conexão com o servidor. A oportunidade de não apenas receber o sinal de uma nova barra, mas também o número de barras, certamente será útil.

Nossa função protótipo acaba desta forma:

//--- if we passed to this line, then the bar is not new; return false
   return(false);

Sim, se passarmos para esta linha - a barra não é nova.

Criando uma nova função isNewBar()

Aqui começa uma coisa interessante. Resolveremos o ponto fraco detectado. Você sabe, fui um pouco modesto demais, chamando a seção como "Criando uma nova função isNewBar(). Faremos algo mais sólido.

Começaremos nos livrando das restrições sobre o número de chamadas de função.

A primeira coisa que vem em mente é que você pode usar funções com o mesmo nome como isNewBar() a partir do artigo Os princípios do cálculo econômico dos indicadores ou a aqui isNewBar. Ou seja, para incluir arrays armazenando múltiplos valores last_time no corpo da função, coloque contadores das chamadas da função isNewBar() a partir de diferentes locais, e assim em diante. é claro, estas são todas versões funcionais e podem ser implementadas. Mas, imagine se estivéssemos escrevendo um Expert Advisor de múltiplas moedas para trabalhar com 12 pares de moeda. Haveria tantas nuances necessárias a considerar e não ficar confuso?

O que devemos fazer? A resposta está aqui!

A beleza da Programação orientada a objeto é que um objeto ou uma instância de alguma classe pode "viver sua própria vida", independente de outras instâncias da mesma classe. Então, vamos começar a criar uma classe CisNewBar, então poderemos produzir instâncias desta classe em qualquer lugar de nosso Expert Advisor ou Indicador quantas vezes forem necessárias. E deixe cada instância "viver sua vida".

Isso é o que devemos começar:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar();       // First type of request for new bar
  };  

bool CisNewBar::isNewBar()
  {
   //--- here is the definition of static variable

   //--- here will be the rest of method's code   
   ...

   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

O que era a função isNewBar(), agora é o método. Observe que agora não existe a variável estática last_time - ao invés dela, temos agora a variável de classe protegida m_lastbar_time. Se tivéssemos deixado a variável estática no método isNewBar(), então todos nossos esforços dariam errado, já que enfrentaríamos os mesmos problemas que antes com a função isNewBar() - estes são os recursos das variáveis estáticas.

E agora, o tempo da última barra será armazenado na variável de classe protegida m_lastbar_time, e em cada instância de classe a memória será localizada para esta variável. Assim, pudemos remover a restrição sobre o número de chamadas que estava na função protótipo. Podemos chamar o método isNewBar() em diferentes locais em nosso programa MQL quantas vezes quisermos, criando a instância de classe para cada local.

Isso é algo em que fomos bem sucedidos. Agora, vamos trabalhar com a universalidade. Antes de adicionar algo em nossa nova classe, gostaria de apresentá-lo a uma ideia incrível.

Vamos pensar. O que queremos? Queremos obter o sinal sobre a nova barra. Como fazemos isso? Então, se o tempo de abertura da barra não finalizada atual no último tick (ou no último momento) for maior que o tempo de abertura da barra não finalizada atual no tick anterior (ou no momento anterior), a nova barra está formada. Frase complicada, porém, correta. O ponto principal é que precisamos comparar o tempo. Portanto, decidi que seria lógico passar o tempo de abertura da barra não finalizada atual newbar_time para o método isNewBar(). Então, o cabeçalho do método será o seguinte:

bool isNewBar(datetime newbar_time)

Não pergunte ainda onde teremos a newbar_time - suponha que já sabemos. Veremos isso mais tarde.

A propósito, passando o tempo para o método isNewBar(), obtemos uma ferramenta muito flexível para detectar uma nova barra. Poderemos cobrir todos os períodos de gráfico padrão com todos os tipos de ferramentas de negócio. Aconteceu de modo que agora não dependemos do nome do símbolo e o tamanho do período.

Podemos usar também gráficos não padrão. Por exemplo, se você estiver plotando candlesticks de tick, ou gráficos Renko ou Kagi, seus tempos de abertura de barra praticamente nunca coincidem com o tempo dos períodos do gráfico padrão. Neste caso, nossa função será indispensável.

Bem, agora está tudo OK com a versatilidade. Vamos suplementar nossa classe CisNewBar de acordo com nossa ideia.

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      uint              m_retcode;        // Result code of detecting new bar
      int               m_new_bars;       // Number of new bars
      string            m_comment;        // Comment of execution
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar(datetime new_Time); // First type of request for new bar
  };
   
//+------------------------------------------------------------------+
//| First type of request for new bar                     |
//| INPUT:  newbar_time - time of opening (hypothetically) new bar   |
//| OUTPUT: true   - if new bar(s) has(ve) appeared                  |
//|         false  - if there is no new bar or in case of error      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CisNewBar::isNewBar(datetime newbar_time)
  {
   //--- Initialization of protected variables
   m_new_bars = 0;      // Number of new bars
   m_retcode  = 0;      // Result code of detecting new bar: 0 - no error
   m_comment  =__FUNCTION__+" Successful check for new bar";
   //---
   
   //--- Just to be sure, check: is the time of (hypothetically) new bar m_newbar_time less than time of last bar m_lastbar_time? 
   if(m_lastbar_time>newbar_time)
     { // If new bar is older than last bar, print error message
      m_comment=__FUNCTION__+" Synchronization error: time of previous bar "+TimeToString(m_lastbar_time)+
                                                  ", time of new bar request "+TimeToString(newbar_time);
      m_retcode=-1;     // Result code of detecting new bar: return -1 - synchronization error
      return(false);
     }
   //---
        
   //--- if it's the first call
   if(m_lastbar_time==0)
     {  
      m_lastbar_time=newbar_time; //--- set time of last bar and exit
      m_comment   =__FUNCTION__+" Initialization of lastbar_time = "+TimeToString(m_lastbar_time);
      return(false);
     }   
   //---

   //--- Check for new bar:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // Number of new bars
      m_lastbar_time=newbar_time; // remember time of last bar
      return(true);
     }
   //---
   
   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

Olhando o código fonte para nossa classe, você provavelmente percebeu que levamos em consideração o rastreio de erros de runtime, e introduzimos a variável armazenando o número de barras novas.

Tudo está bem, mas nosso método universal isNewBar (data e hora newbar_time) contém uma inconveniência maior. Esta inconveniência é que sempre nos preocupamos em calcular o tempo do (hipoteticamente) newbar_time no código fonte de nosso expert ou indicador.

Felizmente, em alguns casos, podemos simplificar sua vida, confiando esta função a um novo método adicional de nossa classe. Para períodos padrão e símbolos em nossa função protótipo, isso pode ser feito usando a segunda versão da função SeriesInfoInteger() com o modificador SERIES_LASTBAR_DATE, e em todos os outros casos - usando o método genérico. Então, aqui está o que tenho:

//+------------------------------------------------------------------+
//| Second type of request for new bar                     |
//| INPUT:  no.                                                      |
//| OUTPUT: m_new_bars - Number of new bars                          |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CisNewBar::isNewBar()
  {
   datetime newbar_time;
   datetime lastbar_time=m_lastbar_time;
      
   //--- Request time of opening last bar:
   ResetLastError(); // Set value of predefined variable _LastError as 0
   if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time))
     { // If request has failed, print error message:
      m_retcode=GetLastError();  // Result code of detecting new bar: write value of variable _LastError
      m_comment=__FUNCTION__+" Error when getting time of last bar opening: "+IntegerToString(m_retcode);
      return(0);
     }
   //---
   
   //---Next use first type of request for new bar, to complete analysis:
   if(!isNewBar(newbar_time)) return(0);
   
   //---Correct number of new bars:
   m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1;
   
   //--- If we've reached this line - then there is(are) new bar(s), return their number:
   return(m_new_bars);
  }

Então, o que temos nesse momento? Agora, para períodos padrão, não precisamos nos preocupar em determinar o tempo de abertura da última barra não finalizada. Nos aproximamos de nossa função protótipo com esta simples chamada e sem os atalhos que ela tem. E até obtemos vantagens adicionais, incluindo códigos de erro, comentários runtime e número de barras novas.

Falta alguma coisa? Sim. Tem o momento final - a inicialização. Para isso, usaremos o construtor de classe e diversos métodos de ajuste. Nosso construtor de classe parece com o seguinte:

//+------------------------------------------------------------------+
//| CisNewBar constructor.                                           |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CisNewBar::CisNewBar()
  {
   m_retcode=0;         // Result code of detecting new bar
   m_lastbar_time=0;    // Time of opening last bar
   m_new_bars=0;        // Number of new bars
   m_comment="";        // Comment of execution
   m_symbol=Symbol();   // Symbol name, by default - symbol of current chart
   m_period=Period();   // Chart period, by default - period of current chart
  }

E os métodos de ajuste assim:

//--- Methods of initializing protected data:
void              SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time;                            }
void              SetSymbol(string symbol)             {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol;  }
void              SetPeriod(ENUM_TIMEFRAMES period)    {m_period=(period==PERIOD_CURRENT)?Period():period;      }

Graças ao nosso construtor de classe, não precisamos prestar atenção à inicialização do símbolo e período do gráfico atual. Como na função protótipo, eles serão usados por padrão. Mas, se precisarmos usar outro símbolo ou período de gráfico, então podemos usá-los para nossos métodos de ajuste criados. Além disso, usando SetLastBarTime(datetime lastbar_time) você pode recriar a situação da "primeira chamada".

Concluindo, vamos criar diversos métodos de obtenção para obter dados a partir de nossa classe no Expert Advisor e Indicadores:

      //--- Methods of access to protected data:
uint              GetRetCode()     const  {return(m_retcode);     }  // Result code of detecting new bar 
datetime          GetLastBarTime() const  {return(m_lastbar_time);}  // Time of opening last bar
int               GetNewBars()     const  {return(m_new_bars);    }  // Number of new bars
string            GetComment()     const  {return(m_comment);     }  // Comment of execution
string            GetSymbol()      const  {return(m_symbol);      }  // Symbol name
ENUM_TIMEFRAMES   GetPeriod()      const  {return(m_period);      }  // Chart period

Agora podemos obter todas as informações necessárias em nossos programas mql5. Por hora, podemos colocar uma parada completa na criação da classe CisNewBar.

O código fonte completo para nossa classe está no arquivo anexo Lib CisNewBar.mqh.

Exemplos de utilização da classe CisNewBar

Proponho que você considere os exemplos de nossa utilização de classe para entender todas as nuances do que criamos. Talvez, possa haver não apenas vantagens, mas desvantagens.

Exemplo 1. Para começar, vamos criar um Expert Advisor completamente idêntico para a função isNewBar() a partir do artigo Limitações e verificações nos Expert Advisors:

//+------------------------------------------------------------------+
//|                                               Example1NewBar.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               Lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(current_chart.isNewBar()>0)
     {     
      PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Vamos executar ambos Expert Advisors nos gráficos com o mesmo par e período. Vamos ver o que temos:


Primeiro, ambos Expert Advisors reportam sincronizadamente sobre a nova barra. Depois, todos silenciam e apenas quatro minutos depois, eles informam que existe uma nova barra (desta vez marcada com 1). Está OK - Desconectei a internet por alguns minutos e decidi ver o que acontecerá. Apesar do fato que algumas novas barras foram formadas, não recebemos esta informação. Em nosso novo Expert Advisor, podemos corrigir esta desvantagem já que nosso método isNewBar() permite fazer tal coisa.

A seguir, mudei o período do gráfico para M2. A reação dos Expert Advisors foi diferente. CheckLastBar começou a reportar em uma nova barra a cada 2 minutos, e Example1NewBar fala sobre as novas barras a todo minuto, como se o período não tivesse mudado (marcado como 2).

O fato de que nossa instância current_chart foi iniciada pelo construtor de classe quando o Expert Advisor foi anexado ao gráfico. Quando você muda o período do Expert Advisor, já anexo ao gráfico, o construtor da classe não começa, e o Expert Advisor continua a trabalhar com o período M1. Ele nos diz que nossa instância de classe vive sua própria vida e não é afetada por mudanças no ambiente. Pode ser tanto um pró como contra - tudo depende das tarefas.

Para que nosso Expert Advisor atue como CheckLastBar, precisamos iniciar as variáveis de classe protegidas m_symbol e m_period na função OnInit(). Vamos fazer isso.

Exemplo 2. Vamos introduzir algumas adições ao nosso Expert Advisor e comparar novamente seu desempenho com relação ao CheckLastBar. O código fonte do Expert Advisor é anexo como o arquivo Example2NewBar.mq5. Execute os Expert Advisors nos gráficos com o mesmo par e período. Vamos criar os mesmos obstáculos para eles, como da última vez. Vamos ver o que temos:


Como na última vez, os Expert Advisors primeiro reportam sincronizadamente sobre a nova barra. Depois, eu desconecto a internet por alguns minutos... Depois eu ligo. Nosso novo Expert Advisor não reportou apenas sobre uma nova barra, mas também quantas apareceram (marcado como 1). Para a maioria dos indicadores e experts, este número representará o número das barras calculadas. Assim, temos uma boa base para algoritmos de recálculo de baixo custo.

A seguir, mudei o período dos gráficos para M2. Diferente do Exemplo 1, os Expert Advisors trabalham sincronizadamente (marcado com 2). A inicialização das variáveis de classe protegidas m_symbol e m_period na função OnInit() ajudou! Ao mudar o símbolo (marcado com 3), os Expert Advisors também trabalham da mesma forma.

Exemplo 3. Em nossa classe CisNewBar colocamos a possibilidade de rastrear erros. Pode acontecer que o Expert Advisor seja projetado de modo que não haja a necessidade de rastrear erros. Bem, apenas não use essa possibilidade. Bem, tentaremos criar artificialmente uma situação onde o erro é possível e tentaremos obtê-lo. Para isso, suplementaremos ligeiramente o código fonte do Expert Advisor (o arquivo Example3NewBar.mq5).

O que farei? Como de costume, vou executar Example3NewBar em gráficos de minuto. Depois, vou começar a mudar os instrumentos do gráfico na esperança de que uma situação surja e que o terminal não tenha tempo para acumular séries de tempo antes da solicitação do Expert Advisor. Em geral, vou tormentar o terminal do cliente e ver o que acontece...

Depois de diversas tentativas, nosso Expert Advisor obtém um erro:

Agora podemos dizer com segurança que somos capazes de obter erros de runtime. Como manipulá-los - é uma questão de gosto. Observe que rastreamos este erro quatro vezes. Quando o download estiver concluído e o gráfico formado, o Expert Advisor sugere que perdemos apenas 1 barra.

A propósito, aqueles que viram o código fonte do Expert Advisor podem ter notado que faz sentido verificar erros apenas quando o método isNewBar() retorna um valor menor ou igual a zero.

Aviso: Se durante este experimento, você começar a mudar o período do gráfico, depois quando você mudar o período de gráfico de pequeno para grande, você terá um erro de sincronização. Isso porque o tempo de abertura de barra (por exemplo) de H1 é antes de M1 em 59 casos. Para evitar este erro, ao mudar o período do gráfico, você precisa iniciar adequadamente a variável m_lastbar_time na função OnInit() com o método SetLastBarTime(datetime lastbar_time).

Exemplo 4. Neste exemplo, vamos complicar a tarefa do Expert Advisor. Pegue três pares de moeda: EURUSD em M1, GBPUSD em M1 e USDJPY em M2. O gráfico com o primeiro par será atual, e nele apenas esperaremos por uma nova barra. No segundo par, calcularemos o número de barras formadas após o início do Expert Advisor. Contaremos até o primeiro par sinalizar que existe uma nova barra. E no terceiro par realizaremos constantemente (quando a barra aparecer em EURUSD) a inicialização da variável da classe protegida m_lastbar_time. O código fonte do Expert Advisor está anexo como o arquivo Example4NewBar.mq5.

Criando este exemplo, quero descobrir como nossa classe CisNewBar trabalhará no modo de moeda múltipla. Bem, eu iniciei... Eis o que obtive:


Os resultados levantaram dúvidas. Vou colocar lenha na fogueira e executar esse mesmo intervalo de tempo no Strategy Tester. Resultados do Strategy Tester:


Então, você pode jogar o jogo "encontre as dez diferenças". Além das estranhezas do trabalho do Expert Advisor em uma conta-demo, é óbvio que existem diferenças entre a conta-demo e o Strategy Tester - e elas são claramente visíveis. Uma comparação similar com a abordagem correta não revelará apenas as desvantagens do Expert Advisor, mas também permite eliminá-las. Talvez, eu não vá analisar o porquê disso ter acontecido, como aconteceu e o que precisa ser consertado no Expert Advisor.

Exemplo 5. Nunca usamos explicitamente o método de detecção de barra nova mais universal nos exemplos - isNewBar(data e hora newbar_time). Para isso, pegarei o candlestick do tick do artigo Criando indicadores de tick no MQL5 e adicionarei um buffer para armazenar o tempo de abertura de barra (arquivo TickColorCandles v2.00.mq5). Vou escrever um Expert Advisor muito pequeno que dirá sobre o tempo do novo candlestick do tick (arquivo Example5NewBar.mq5):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar newbar_ind; // instance of the CisNewBar class: detect new tick candlestick
int HandleIndicator;  // indicator handle
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Get indicator handle:
   HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); 
   if(HandleIndicator==INVALID_HANDLE)
     {
      Alert(" Error when creating indicator handle, error code: ",GetLastError());
      Print(" Incorrect initialization of Expert Advisor. Trade is not allowed.");
      return(1);
     }

//--- Attach indicator to chart:  
   if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator))
     {
      Alert(" Error when attaching indicator to chart, error code: ",GetLastError());
      return(1);
     }
//--- If you passed until here, initialization was successful
   Print(" Successful initialization of Expert Advisor. Trade is allowed.");
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double iTime[1];

//--- Get time of opening last unfinished tick candlestick:
   if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0)
     {
      Print(" Failed to get time value of indicator. "+
            "\nNext attempt to get indicator values will be made on the next tick.",GetLastError());
      return;
     }
//--- Detect the next tick candlestick:
   if(newbar_ind.isNewBar((datetime)iTime[0]))
     {
      PrintFormat("New bar. Opening time: %s  Time of last tick: %s",
                  TimeToString((datetime)iTime[0],TIME_SECONDS),
                  TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Certamente você notou como obtivemos o tempo de abertura do candlestick do tick. Muito fácil, não? Coloco o indicador e o Expert Advisor em suas pastas, compilo e executo o Expert Advisor. Funciona, eis os resultados:

Handler de evento "nova barra"


Nos aproximando do final deste artigo, gostaria de compartilhar uma outra ideia. No Forum (em Russo) tinha uma ideia que seria interessante ter um handler de evento "nova barra" padrão. Talvez os desenvolvedores cheguem a isso, talvez não. Mas, a beleza do MQL5 é que é possível implementar as mais incríveis ideias elegantemente e de forma simples.

Se você quiser ter um handler de evento "nova barra" (ou NewBar) - vamos criá-lo! Especialmente agora que podemos obter este evento facilmente utilizando nossa classe. Nosso expert (com o handler de evento NewBar OnNewBar()) será assim:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include "OnNewBar.mqh" // Here is the secret of launching the "new bar" event handler

//+------------------------------------------------------------------+
//| New bar event handler function                                   |
//+------------------------------------------------------------------+
void OnNewBar()
  {
   PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
  }

Parece muito bom. Nosso Expert Advisor parece bem simples. Este handler imprime a string sobre a nova barra. E é tudo que faz. Para entender como rastrear o evento NewBar e como executar o handler, você precisa ver o arquivo OnNewBar.mqh.

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar@mail.ru"

#include <Lib CisNewBar.mqh>
CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int period_seconds=PeriodSeconds(_Period);                     // Number of seconds in current chart period
   datetime new_time=TimeCurrent()/period_seconds*period_seconds; // Time of bar opening on current chart
   if(current_chart.isNewBar(new_time)) OnNewBar();               // When new bar appears - launch the NewBar event handler
  }

Como você pode ver, não há nada de complicado aqui também. Mas há alguns momentos nos quais gostaria de chamar sua atenção:

Primeiro. Como você percebeu, usei a função TimeCurrent() para calcular o tempo de abertura de barra e usei o primeiro método para verificar o evento NewBar de nossa classe. Isso é o melhor. Ela se baseia no fato de que tal método não requer nenhum processamento de erro, quando usando SeriesInfoInteger() com o modificador SERIES_LASTBAR_DATE. Para nós, isso é importante, já que nosso handler OnNewBar() deve ser o mais confiável possível.

Segundo. O uso da função TimeCurrent() para calcular o tempo de abertura de barra é o método mais rápido. O uso da função SeriesInfoInteger(), mesmo sem o controle de erro, para o mesmo propósito como um método mais lento.

O resultado de nosso handler.

Conclusão

No curso da apresentação de material, fizemos uma boa análise de meios para detectar uma nova barra. Expusemos os prós e contras dos métodos existentes de detecção da nova barra. Baseado no que tivemos, criamos a classe CisNewBar, possibilitando sem custos adicionais na programação obter o evento "nova barra" em quase qualquer tarefa. Ao mesmo tempo, nos livramos da maioria das inconveniências das soluções anteriores.

Estes exemplos nos ajudam a entender os prós e contras dos métodos inventados por nós. O método de moeda múltipla requer atenção especial em termos do trabalho correto. Você tem que fazer uma análise completa das ineficiências identificadas e desenvolver meios para solucioná-las.

O handler do evento "nova barra" criado é adequado apenas para Expert Advisors de moeda única. Mas, aprendemos a usar o meio mais confiável e rápido para este propósito. Agora você pode prosseguir e fazer um handler de evento NewBar de moeda múltipla. Mas este é o tópico de outro artigo.

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/159

Arquivos anexados |
onnewbar.mqh (1.12 KB)
example1newbar.mq5 (0.94 KB)
example2newbar.mq5 (2.61 KB)
example3newbar.mq5 (2.98 KB)
example4newbar.mq5 (6.41 KB)
example5newbar.mq5 (2.39 KB)
onnewbar.mq5 (0.8 KB)
Últimos Comentários | Ir para discussão (9)
Felipe Miguel dos Santos
Felipe Miguel dos Santos | 29 mai 2018 em 21:56
Muito obrigado, me ajudou muito no meu EA.
evelyneds
evelyneds | 28 dez 2018 em 13:27
Ótimo material! Precisei voltar aqui depois de sentir na prática algumas dificuldades com o isNewBar...
Parabéns!
Kl_ Urt
Kl_ Urt | 6 mai 2019 em 09:02
Excelente solução! Parabéns! Muito obrigado!
tito.vinicius
tito.vinicius | 21 ago 2019 em 18:33

Atualemnte me EA abre vários ordens no mesmo candle. Creio que o material divulgado neste artigo me ajudaria.

Como faço para implementá-lo?

Charles Magno
Charles Magno | 9 nov 2019 em 18:51
tito.vinicius:

Atualemnte me EA abre vários ordens no mesmo candle. Creio que o material divulgado neste artigo me ajudaria.

Como faço para implementá-lo?


Você pode tirar a lógica de entrada no trade do evento OnTick() e colocar dentro da função OnNewBar(). Assim, só será executado quando houver uma nova barra, não entrando mais várias vezes no mesmo candle, pois uma vez executado, só vai executar novamente no próximo candle.

Simulink: um guia para os desenvolvedores de Expert Advisors Simulink: um guia para os desenvolvedores de Expert Advisors

Não sou um programador profissional. E assim, o princípio de "ir do simples para o complexo" é de suma importância para mim quando trabalho com o desenvolvimento de um sistema de negócio. O que exatamente é simples para mim? Primeiramente, esta é a visualização do processo de criação do sistema e a lógica de seu funcionamento. Também, é um mínimo de código escrito à mão. Neste artigo, tentarei criar e testar o sistema de negócio, com base no pacote Matlab e, depois, escrever um Expert Advisor para o MetaTrader 5. Os dados do histórico do MetaTrader 5 serão usados para o processo de teste.

Encontrando erros e registrando Encontrando erros e registrando

O MetaEditor 5 possui o recurso de depuração Mas, quando você escreve seus programas MQL5, geralmente você deseja exibir não apenas valores individuais, mas todas as mensagens que aparecem durante o trabalho online e teste. Quando o conteúdo do arquivo de registro tem um tamanho grande, é óbvio automatizar a recuperação rápida e fácil da mensagem requerida. Neste artigo consideraremos várias maneiras de encontrar erros em programas do MQL5 e métodos de registro. Além disso, simplificaremos o logging em arquivos e conheceremos um programa simples LogMon para visualizações confortáveis dos registros.

Gás neural em desenvolvimento: Implementação em MQL5 Gás neural em desenvolvimento: Implementação em MQL5

Este artigo mostra um exemplo de como desenvolver um programa MQL5 implementando o algorítimo adaptável de fazer o cluster chamado gás neural em desenvolvimento (GNG). O artigo é destinado para usuários que tenham estudado a documentação de linguagem e têm determinadas habilidades de programação e conhecimento básico na área de neuroinformática.

Assistente MQL5: criar Expert Advisors sem programação Assistente MQL5: criar Expert Advisors sem programação

Você quer experimentar uma estratégia de negócio enquanto não gasta tempo em programação? No Assistente MQL5 você pode simplesmente selecionar o tipo de sinais de negócio, adicionar módulos de posições de rastreio e gerenciamento de dinheiro - e seu trabalho está feito! Crie suas próprias implementações dos módulos ou encomende através do atendimento Jobs - e combine seus novos módulos com os já existentes.