English Русский 中文 Español Deutsch 日本語
Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa

Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa

MetaTrader 5Exemplos | 2 julho 2020, 15:44
2 374 0
Artyom Trishkin
Artyom Trishkin

Sumário


Ideia

Em artigos anteriores sobre a criação de séries temporais de qualquer período e de qualquer símbolo, criamos uma classe-coleção completa de séries temporais de todos os símbolos usados no programa e aprendemos a preencher uma série temporal com dados históricos para encontrar e classificar rapidamente esses dados.
Com essa ferramenta, no futuro, poderemos pesquisar e comparar várias combinações de dados de preços no histórico. Mas é necessário pensar em atualizar os dados atuais, o que deve ser feito em cada nova tick para cada símbolo usado.

Em princípio, na variação mais simples, podemos atualizar todas as séries temporais num manipulador de milissegundos OnTimer(). Mas aqui surge a pergunta de se é sempre necessário atualizar os dados da série temporal exatamente pelo contador do temporizador. Afinal, como os dados mudam no programa após a chegada de um novo tick, não é correto atualizá-los simplesmente independentemente desse fato, pois isso seria irracional em termos de desempenho.

Se considerarmos que no símbolo atual sempre conseguimos saber sobre a chegada de um novo tick nos manipuladores OnTick() do EA ou OnCalculate() do indicador, para saber se há um novo tick em qualquer símbolo diferente a ser rastreado pelo programa executado num símbolo diferente, precisaremos rastrear os eventos necessários no EA ou indicador.

Neste caso, o mais simples é fazer uma simples comparação entre o tempo do tick anterior e o do atual. Se o tempo do tick anterior for diferente do atual, é porque no símbolo terá chegado um novo tick, tick esse rastreado pelo programa, mas, não "nativo" para ele, uma vez que o programa não é executado no gráfico deste símbolo.

Antes de criarmos, primeiro, a classe “Novo Tick” e, em seguida, a atualização em tempo real de todas as séries temporais usadas no programa, corrigiremos levemente as classes já criadas.


Modificando classes de séries temporais

Primeiro de tudo, como já aconteceu, adicionamos ao arquivo Datas.mqh o índice da nova mensagem da biblioteca:

//--- CTimeSeries
   MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL,             // First, set a symbol using SetSymbol()
   MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE,                   // Timeseries is not used. Set the flag using SetAvailable()
   MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME,            // Unknown timeframe

bem como o texto da mensagem correspondente ao índice recém-adicionado:

   {"Сначала нужно установить символ при помощи SetSymbol()","First you need to set Symbol using SetSymbol()"},
   {"Таймсерия не используется. Нужно установить флаг использования при помощи SetAvailable()","Timeseries not used. Need to set usage flag using SetAvailable()"},
   {"Неизвестный таймфрейм","Unknown timeframe"},

A classe de objeto base de todos os objetos de biblioteca CBaseObj incorpora duas variáveis:

//+------------------------------------------------------------------+
//| Base object class for all library objects                        |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   ENUM_LOG_LEVEL    m_log_level;                              // Logging level
   ENUM_PROGRAM_TYPE m_program;                                // Program type
   bool              m_first_start;                            // First launch flag
   bool              m_use_sound;                              // Flag of playing the sound set for an object
   bool              m_available;                              // Flag of using a descendant object in the program
   int               m_global_error;                           // Global error code
   long              m_chart_id_main;                          // Control program chart ID
   long              m_chart_id;                               // Chart ID
   string            m_name;                                   // Object name
   string            m_folder_name;                            // Name of the folder storing CBaseObj descendant objects 
   string            m_sound_name;                             // Object sound file name
   int               m_type;                                   // Object type (corresponds to the collection IDs)

public:

A variável m_chart_id_main armazena o identificador do gráfico do programa de controle (gráfico do símbolo no qual está sendo executado o programa). A biblioteca deve enviar para esse gráfico todos os eventos registrados nas coleções e objetos da biblioteca.
A variável m_chart_id serve para armazenar o identificador do gráfico com o qual está relacionado o objeto que é descendente da classe CBaseObj. Embora essa propriedade não seja usada, no futuro será necessária.

Porém, como adicionaremos a variável m_chart_id_main um pouco mais tarde à variável m_chart_id, todas as mensagens serão enviadas para o identificador gráfico registrado na variável m_chart_id. Essa discrepância foi corrigida, agora todos os identificadores do gráfico atual são gravados na variável m_chart_id_main como deveria ser e foram corrigidas todas as classes que têm envio de mensagens a partir da biblioteca para o gráfico do programa de controle, além disso, todas as ocorrências do texto "m_chart_id" são substituídas por "m_chart_id_main "
Essas alterações foram feitas em todas as classes de eventos a partir do diretório \MQL5\Include\DoEasy\Objects\Events\ e nos arquivos de classes de coleções AccountsCollection.mqh, EventsCollection.mqh e SymbolsCollection.mqh.
Para economizar espaço no artigo, não descreveremos essas alterações aqui, uma vez que elas podem ser visualizadas nos arquivos anexados ao artigo.

Para poder gerar os dados da barra especificada a partir da coleção das séries temporais, adicionaremos uma descrição dos parâmetros da barra de classes CBar
no arquivo \MQL5\Include\DoEasy\Objects\Series\Bar.mqh.

No bloco de código que descreve as propriedades do objeto declaramos um método para criar uma descrição de texto dos parâmetros da barra:

//+------------------------------------------------------------------+
//| Descriptions of bar object properties                            |
//+------------------------------------------------------------------+
//--- Get description of a bar's (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_BAR_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_BAR_PROP_STRING property);

//--- Return the bar type description
   string            BodyTypeDescription(void)  const;
//--- Send description of bar properties to the journal (full_prop=true - all properties, false - only supported ones)
   void              Print(const bool full_prop=false);
//--- Display a short bar description in the journal
   virtual void      PrintShort(void);
//--- Return the (1) short name and (2) description of bar object parameters
   virtual string    Header(void);
   string            ParameterDescription(void);
//---
  };
//+------------------------------------------------------------------+

Fora do corpo da classe escrevemos uma implementação do método para criar uma descrição de texto dos parâmetros da barra e alteramos a implementação do método que registra uma breve descrição de barra:

//+------------------------------------------------------------------+
//| Return the description of the bar object parameters              |
//+------------------------------------------------------------------+
string CBar::ParameterDescription(void)
  {
   int dg=(this.m_digits>0 ? this.m_digits : 1);
   return
     (
      ::TimeToString(this.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+", "+
      "O: "+::DoubleToString(this.Open(),dg)+", "+
      "H: "+::DoubleToString(this.High(),dg)+", "+
      "L: "+::DoubleToString(this.Low(),dg)+", "+
      "C: "+::DoubleToString(this.Close(),dg)+", "+
      "V: "+(string)this.VolumeTick()+", "+
      (this.VolumeReal()>0 ? "R: "+(string)this.VolumeReal()+", " : "")+
      this.BodyTypeDescription()
     );
  }
//+------------------------------------------------------------------+
//| Display a short bar description in the journal                   |
//+------------------------------------------------------------------+
void CBar::PrintShort(void)
  {
   ::Print(this.Header(),": ",this.ParameterDescription());
  }
//+------------------------------------------------------------------+

Aqui, simplesmente do método que exibe no log os parâmetros da barra removemos o código para construir uma descrição dos parâmetros e o colocamos no novo método que retorna a mensagem criada, já ao exibir no log os parâmetro da barra imprimimos uma mensagem composta pelo nome abreviado do objeto-barra e seus parâmetros, cuja descrição agora é criada no novo método ParameterDescription().

Para atualizar os dados das séries temporais "alheias" (que não são aquelas em que o programa está sendo executado), decidimos criar a classe "Novo Tick" e atualizar os dados desses símbolos somente após a chegada do evento "Novo Tick" para cada símbolo usado no programa.

Classe "Novo Tick" e atualização de dados

Criaremos no diretório da biblioteca \MQL5\Include\DoEasy\Objects\ a nova pasta Ticks\, e nela geramos o novo arquivo NewTickObj.mqh da classe CNewTickObj, herdada a partir da classe do objeto base de todos os objetos da biblioteca CBaseObj, cujo arquivo é anexado ao arquivo da classe, e preenchemos imediatamente com os dados necessários:

//+------------------------------------------------------------------+
//|                                                   NewTickObj.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Objects\BaseObj.mqh"
//+------------------------------------------------------------------+
//| "New tick" class                                                 |
//+------------------------------------------------------------------+
class CNewTickObj : public CBaseObj
  {
private:
   MqlTick           m_tick;                          // Structure of the current prices
   MqlTick           m_tick_prev;                     // Structure of the current prices during the previous check
   string            m_symbol;                        // Object symbol
   bool              m_new_tick;                      // New tick flag
public:
//--- Set a symbol
   void              SetSymbol(const string symbol)   { this.m_symbol=symbol;             }
//--- Return the new tick flag
   bool              IsNewTick(void);
//--- Update price data in the tick structure and set the "New tick" event flag if necessary
   void              Refresh(void)                    { this.m_new_tick=this.IsNewTick(); }
//--- Constructors
                     CNewTickObj(void){;}
                     CNewTickObj(const string symbol);
  };
//+------------------------------------------------------------------+

Na variável m_tick armazenaremos os dados de preço do último tick recebido.

Na variável m_tick_prev armazenaremos os dados de preço do último tick.

Na variável m_symbol armazenaremos o símbolo cujo novo tick iremos rastrear.

O sinalizador de novo tick na variável m_new_tick será usado mais tarde.
Para as necessidades atuais da biblioteca, no momento vamos determinar o evento "Novo Tick" no símbolo com ajuda do método IsNewTick():

//+------------------------------------------------------------------+
//| Return the new tick flag                                         |
//+------------------------------------------------------------------+
bool CNewTickObj::IsNewTick(void)
  {
//--- If failed to get the current prices to the tick structure, return 'false'
   if(!::SymbolInfoTick(this.m_symbol,this.m_tick))
      return false;
//--- If this is the first launch, copy data of the obtained tick to the previous tick data
//--- reset the first launch flag and return 'false'
   if(this.m_first_start)
     {
      this.m_tick_prev=this.m_tick;
      this.m_first_start=false;
      return false;
     }
//--- If the time of a new tick is not equal to the time of a tick during the previous check -
//--- copy data of the obtained tick to the previous tick data and return 'true'
   if(this.m_tick.time_msc!=this.m_tick_prev.time_msc)
     {
      this.m_tick_prev=this.m_tick;
      return true;
     }
//--- In all other cases, return 'false'
   return false;
  }
//+------------------------------------------------------------------+

Na classe são definidos dois construtores:

  • um construtor padrão (sem parâmetros) que serve para definir o objeto "Novo Tick" como parte de outra classe. Neste caso, é necessário definir o símbolo para o objeto da classe CNewTickObj com ajuda do método SetSymbol(), para o qual serão determinados os eventos "Novo Tick".
  • um construtor paramétrico que serve para criar um objeto da classe através do operador new. Neste caso, ao criar um objeto, é possível especificar imediatamente o símbolo para o qual esse objeto é criado.
//+------------------------------------------------------------------+
//| Parametric constructor CNewTickObj                               |
//+------------------------------------------------------------------+
CNewTickObj::CNewTickObj(const string symbol) : m_symbol(symbol)
  {
//--- Reset the structures of the new and previous ticks
   ::ZeroMemory(this.m_tick);
   ::ZeroMemory(this.m_tick_prev);
//--- If managed to get the current prices to the tick structure,
//--- copy data of the obtained tick to the previous tick data and reset the first launch flag
  if(::SymbolInfoTick(this.m_symbol,this.m_tick))
     { 
      this.m_tick_prev=this.m_tick;
      this.m_first_start=false;
     }
  }
//+------------------------------------------------------------------+

Essa é toda a classe do objeto-tick novo. A ideia é simples: obtemos os preços na estrutura do tick e comparamos o tempo de tick entrante com o tempo tick anterior.
Se esses tempos não forem iguais, significa que um novo tick terá chegado.

Nos consultores, os ticks podem ser ignorados, mas isso não é importante aqui. É importante que, dessa maneira, no temporizador possamos rastrear o fato de um novo tick num símbolo "alheio", para atualizar os dados apenas quando um novo tick chegar, e não constantemente no temporizador.
Relativamente aos indicadores onde os ticks são rastreados e podem vir em pacotes, para o símbolo em que executado o indicador, os dados da série temporal atual devem ser atualizados no manipulador OnCalculate(), enquanto no temporizador os novos ticks são acompanhados apenas para símbolos "alheios" (é impossível obter os eventos de novo tick para símbolos "alheios" em OnOnCalculate()) — por isso, nos indicadores basta acompanharmos a diferença de tempo entre os ticks novos e os anteriores de símbolos "alheios" para atualizar os dados destas séries temporais a tempo.

Fazemos com que o objeto-série temporal CSeries possa enviar ao programa de controle seu evento "Nova Barra", pois isso permitirá obter no programa tais eventos a partir de qualquer série temporal e responder a eles.

Adicionamos ao final da listagem do arquivo Defines.mqh nova enumeração com uma lista de possíveis eventos do objeto-série temporal:

//+------------------------------------------------------------------+
//| List of possible timeseries events                               |
//+------------------------------------------------------------------+
enum ENUM_SERIES_EVENT
  {
   SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE,        // no event
   SERIES_EVENTS_NEW_BAR,                                   // "New bar" event
  };
#define SERIES_EVENTS_NEXT_CODE  (SERIES_EVENTS_NEW_BAR+1)  // Code of the next event after the "New bar" event
//+------------------------------------------------------------------+

Até o momento, existem apenas dois estados dos eventos da série temporal: "Nenhum evento" e o evento "Nova Barra". Vamos precisar das constantes dessa enumeração para procurar segundo as propriedades especificadas do objeto-barra na lista-coleção de barras (na série temporal CSeries).

Como os objetos das séries temporais serão atualizados no temporizador da biblioteca, adicionaremos à listagem do arquivo Defines.mqh os parâmetros do temporizador de atualização da coleção de objetos-séries temporais e o identificador da lista de coleção de séries temporais:

//--- Trading class timer parameters
#define COLLECTION_REQ_PAUSE           (300)                      // Trading class timer pause in milliseconds
#define COLLECTION_REQ_COUNTER_STEP    (16)                       // Trading class timer counter increment
#define COLLECTION_REQ_COUNTER_ID      (5)                        // Trading class timer counter ID
//--- Parameters of the timeseries collection timer
#define COLLECTION_TS_PAUSE            (32)                       // Timeseries collection timer pause in milliseconds
#define COLLECTION_TS_COUNTER_STEP     (16)                       // Account timer counter increment
#define COLLECTION_TS_COUNTER_ID       (6)                        // Timeseries timer counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
//--- Data parameters for file operations

Já analisamos os parâmetros do temporizador das coleções ao criar o objeto base da biblioteca CEngine, bem como o objetivo dos identificadores das coleções ao reorganizar a estrutura da biblioteca.

E imediatamente atribuímos ao objeto-barra o identificador da coleção das séries temporais, porque o objeto-série temporal é uma lista que contém ponteiros para os objetos-barras pertencentes a essa lista.
Mais uma vez, abrimos o arquivo \MQL5\Include\DoEasy\Objects\Series\Bar.mqh e especificamos o tipo do objeto nos dois construtores:

//+------------------------------------------------------------------+
//| Constructor 1                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   this.m_type=COLLECTION_SERIES_ID;
   MqlRates rates_array[1];
   this.SetSymbolPeriod(symbol,timeframe,index);
   ::ResetLastError();
//--- If failed to write bar data to the MqlRates array by index or set the time to the time structure,
//--- display an error message, create and fill the structure with zeros, and write it to the rates_array array
   if(::CopyRates(symbol,timeframe,index,1,rates_array)<1 || !::TimeToStruct(rates_array[0].time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code));
      MqlRates err={0};
      rates_array[0]=err;
     }
//--- Set the bar properties
   this.SetProperties(rates_array[0]);
  }
//+------------------------------------------------------------------+
//| Constructor 2                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates)
  {
   this.m_type=COLLECTION_SERIES_ID;
   this.SetSymbolPeriod(symbol,timeframe,index);
   ::ResetLastError();
//--- If failed to set time to the time structure, display the error message,
//--- create and fill the structure with zeros, set the bar properties from this structure and exit
   if(!::TimeToStruct(rates.time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code));
      MqlRates err={0};
      this.SetProperties(err);
      return;
     }
//--- Set the bar properties
   this.SetProperties(rates);
  }
//+------------------------------------------------------------------+


Agora vamos modificar a classe do objeto-série temporal CSeries, localizado em \MQL5\Include\DoEasy\Objects\Series\Series.mqh.

Na seção pública da classe, declaramos um novo método para enviar eventos para o gráfico do programa de controle:

//--- (1) Create and (2) update the timeseries list
   int               Create(const uint required=0);
   void              Refresh(const datetime time=0,
                             const double open=0,
                             const double high=0,
                             const double low=0,
                             const double close=0,
                             const long tick_volume=0,
                             const long volume=0,
                             const int spread=0);
                             
//--- Create and send the "New bar" event to the control program chart
   void              SendEvent(void);

//--- Return the timeseries name
   string            Header(void);
//--- Display (1) the timeseries description and (2) the brief timeseries description in the journal
   void              Print(void);
   void              PrintShort(void);


//--- Constructors
                     CSeries(void);
                     CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0);
  };
//+------------------------------------------------------------------+

No final da listagem da classe, escrevemos a implementação do método declarado:

//+------------------------------------------------------------------+
//| Create and send the "New bar" event                              |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CSeries::SendEvent(void)
  {
   ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,this.Time(0),this.Timeframe(),this.Symbol());
  }
//+------------------------------------------------------------------+

Aqui, criamos e enviamos para o gráfico do programa de controle o evento que consiste em:
ID do gráfico-destinatário do evento,
identificador do evento (Nova Barra),
como parâmetro long de evento enviamos o tempo de abertura de nova barra,
como parâmetro double de evento enviamos o período gráfico
, em que ocorre o evento, e
como parâmetro string enviamos o nome do símbolo, em cuja série temporal ocorre o evento.

Ao método de sincronização de dados da série temporal adicionamos uma verificação de sinalizador de uso de série temporal no programa:

//+------------------------------------------------------------------+
//|Synchronize symbol and timeframe data with server data            |
//+------------------------------------------------------------------+
bool CSeries::SyncData(const uint required,const uint rates_total)
  {
//--- If the timeseries is not used, notify of that and exit
   if(!this.m_available)
     {
      ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE));
      return false;
     }
//--- If managed to obtain the available number of bars in the timeseries

Ou seja, se para a série temporal não estiver definido o sinalizador indicando seu uso no programa, não será necessário sincronizá-lo. Porém, pode acontecer que seja necessária uma série temporal e, ao mesmo tempo, nos esqueçamos de definir o sinalizador para usá-la em algum lugar do nosso programa, o que fará com que uma mensagem seja exibida no log indicando que tal séries temporal não é usada.

Escrevemos a mesma verificação no método de criação das séries temporais:

//+------------------------------------------------------------------+
//| Create the timeseries list                                       |
//+------------------------------------------------------------------+
int CSeries::Create(const uint required=0)
  {
//--- If the timeseries is not used, notify of that and return zero
   if(!this.m_available)
     {
      ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE));
      return 0;
     }
//--- If the required history depth is not set for the list yet,

Além disso, à classe foi retornado um método que retorna um objeto-barra pelo índice da série temporal. Anteriormente, o método era assim:

//+------------------------------------------------------------------+
//| Return the bar object by index in the timeseries                 |
//+------------------------------------------------------------------+
CBar *CSeries::GetBarBySeriesIndex(const uint index)
  {
   CArrayObj *list=this.GetList(BAR_PROP_INDEX,index);
   return(list==NULL || list.Total()==0 ? NULL : list.At(0));
  }
//+------------------------------------------------------------------+

Ou seja, foi criada uma nova lista contendo uma cópia da barra solicitada e foi devolvida essa cópia. Isso é suficiente para um simples recebimento dos dados da barra solicitada, mas se as propriedades da barra precisarem ser alteradas, nada resultará, uma vez que as propriedades da cópia da barra são alteradas, não as propriedades do objeto original.

Como precisamos de uma atualização em tempo real da barra atual quando surgir um novo tick, o método foi modificado para que retorne um ponteiro para o objeto-barra original na lista-coleção de barras, e não a barra a partir de uma cópia desta lista:

//+------------------------------------------------------------------+
//| Return the bar object by index in the timeseries                 |
//+------------------------------------------------------------------+
CBar *CSeries::GetBarBySeriesIndex(const uint index)
  {
   CBar *tmp=new CBar(this.m_symbol,this.m_timeframe,index); 
   if(tmp==NULL)
      return NULL;
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
   int idx=this.m_list_series.Search(tmp);
   delete tmp;
   CBar *bar=this.m_list_series.At(idx);
   return(bar!=NULL ? bar : NULL);
  }
//+------------------------------------------------------------------+

Aqui criamos um objeto-barra temporário com símbolos e períodos gráficos do objeto-série atual e uma barra transferida pelo índice ao método. Estamos interessados no índice da barra na série temporal do gráfico, para procurar o mesmo objeto na lista-série temporal, classificado pelos índices das barras. Como resultado da pesquisa de uma barra com tal índice de série temporal, obtemos seu índice de lista e, com base nele, o ponteiro para o objeto-barra na lista e o retornamos.
Agora, o método retornará um ponteiro para o objeto-barra original na lista-série temporal, e poderá ser alterado durante a atualização de dados em tempo real.

Agora vamos modificar a classe de objeto-série temporal CTimeSeries para rastrear novos ticks e atualizar dados no momento em que o evento é detectado. Um objeto desta classe é uma coleção de séries temporais de todos os períodos gráficos de um símbolo. E isso significa que esse objeto é o local mais adequado para o objeto da classe "Novo Tick", uma vez ao obter o novo tick com base no símbolo do objeto-série temporal CTimeSeries, é ativado o processo de atualização de dados dos objetos-séries temporais CSeries de todos os períodos pertencentes a tal objeto.

Ao arquivo da classe do objeto-série temporal anexamos o arquivo da classe do objeto "Novo Tick", na seção privada da classe definimos o objeto da classe "Novo Tick",
à sessão pública da classe adicionamos o método que retorna o sinalizador de novo tick no símbolo do objeto-série temporal atual:

//+------------------------------------------------------------------+
//|                                                   TimeSeries.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Series.mqh"
#include "..\Ticks\NewTickObj.mqh"
//+------------------------------------------------------------------+
//| Symbol timeseries class                                          |
//+------------------------------------------------------------------+
class CTimeSeries : public CBaseObj
  {
private:
   string            m_symbol;                                             // Timeseries symbol
   CNewTickObj       m_new_tick;                                           // "New tick" object
   CArrayObj         m_list_series;                                        // List of timeseries by timeframes
   datetime          m_server_firstdate;                                   // The very first date in history by a server symbol
   datetime          m_terminal_firstdate;                                 // The very first date in history by a symbol in the client terminal
//--- Return (1) the timeframe index in the list and (2) the timeframe by the list index
   char              IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const { return IndexEnumTimeframe(timeframe)-1;                            }
   ENUM_TIMEFRAMES   TimeframeByIndex(const uchar index)             const { return TimeframeByEnumIndex(uchar(index+1));                       }
//--- Set the very first date in history by symbol on the server and in the client terminal
   void              SetTerminalServerDate(void)
                       {
                        this.m_server_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_SERVER_FIRSTDATE);
                        this.m_terminal_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_TERMINAL_FIRSTDATE);
                       }
public:
//--- Return (1) oneself, (2) the full list of timeseries, (3) specified timeseries object and (4) timeseries object by index
   CTimeSeries      *GetObject(void)                                       { return &this;                                                      }
   CArrayObj        *GetListSeries(void)                                   { return &this.m_list_series;                                        }
   CSeries          *GetSeries(const ENUM_TIMEFRAMES timeframe)            { return this.m_list_series.At(this.IndexTimeframe(timeframe));      }
   CSeries          *GetSeriesByIndex(const uchar index)                   { return this.m_list_series.At(index);                               }
//--- Set/return timeseries symbol
   void              SetSymbol(const string symbol)                        { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);  }
   string            Symbol(void)                                    const { return this.m_symbol;                                              }
//--- Set the history depth (1) of a specified timeseries and (2) of all applied symbol timeseries
   bool              SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool              SetRequiredAllUsedData(const uint required=0,const int rates_total=0);
//--- Return the flag of data synchronization with the server data of the (1) specified timeseries, (2) all timeseries
   bool              SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool              SyncAllData(const uint required=0,const int rates_total=0);
//--- Return the very first date in history by symbol (1) on the server, (2) in the client terminal and (3) the new tick flag
   datetime          ServerFirstDate(void)                           const { return this.m_server_firstdate;                                    }
   datetime          TerminalFirstDate(void)                         const { return this.m_terminal_firstdate;                                  }
   bool              IsNewTick(void)                                       { return this.m_new_tick.IsNewTick();                                }
//--- Create (1) the specified timeseries list and (2) all timeseries lists
   bool              Create(const ENUM_TIMEFRAMES timeframe,const uint required=0);
   bool              CreateAll(const uint required=0);
//--- Update (1) the specified timeseries list and (2) all timeseries lists
   void              Refresh(const ENUM_TIMEFRAMES timeframe,
                             const datetime time=0,
                             const double open=0,
                             const double high=0,
                             const double low=0,
                             const double close=0,
                             const long tick_volume=0,
                             const long volume=0,
                             const int spread=0);
   void              RefreshAll(const datetime time=0,
                             const double open=0,
                             const double high=0,
                             const double low=0,
                             const double close=0,
                             const long tick_volume=0,
                             const long volume=0,
                             const int spread=0);

//--- Compare CTimeSeries objects (by symbol)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Display (1) description and (2) short symbol timeseries description in the journal
   void              Print(const bool created=true);
   void              PrintShort(const bool created=true);
   
//--- Constructors
                     CTimeSeries(void){;}
                     CTimeSeries(const string symbol);
  };
//+------------------------------------------------------------------+

O método IsNewTick() retorna o resultado da solicitação de dados sobre o novo tick a partir do objeto "Novo Tick" m_new_tick.

Por conseguinte, para que o objeto da classe "Novo Tick" saiba qual símbolo usar para retornar dados, é necessário no construtor da classe definir o símbolo para o objeto da classe "Novo Tick" e imediatamente atualizar os dados para ler os preços do tick atual:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTimeSeries::CTimeSeries(const string symbol) : m_symbol(symbol)
  {
   this.m_list_series.Clear();
   this.m_list_series.Sort();
   for(int i=0;i<21;i++)
     {
      ENUM_TIMEFRAMES timeframe=this.TimeframeByIndex((uchar)i);
      CSeries *series_obj=new CSeries(this.m_symbol,timeframe);
      this.m_list_series.Add(series_obj);
     }
   this.SetTerminalServerDate();
   this.m_new_tick.SetSymbol(this.m_symbol);
   this.m_new_tick.Refresh();
  }
//+------------------------------------------------------------------+

Nos métodos que retornam o sinalizador de sincronização de dados, agora vamos verificar o sinalizador de uso de série temporal, e se o sinalizador estiver desmarcado, não será usada a série temporal no programa, e não será necessário processá-la:

//+------------------------------------------------------------------+
//| Return the flag of data synchronization                          |
//| with the server data                                             |
//+------------------------------------------------------------------+
bool CTimeSeries::SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   if(this.m_symbol==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL));
      return false;
     }
   CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ),this.m_symbol," ",TimeframeDescription(timeframe));
      return false;
     }
   if(!series_obj.IsAvailable())
      return false;
   return series_obj.SyncData(required,rates_total);
  }
//+------------------------------------------------------------------+
//| Return the flag of data synchronization                          |
//| of all timeseries with the server data                           |
//+------------------------------------------------------------------+
bool CTimeSeries::SyncAllData(const uint required=0,const int rates_total=0)
  {
   if(this.m_symbol==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL));
      return false;
     }
   bool res=true;
   for(int i=0;i<21;i++)
     {
      CSeries *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || !series_obj.IsAvailable())
         continue;
      res &=series_obj.SyncData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+

Nos métodos de criação de série temporal definiremos forçosamente o sinalizador de uso de série temporal, afinal, se a criamos é porque vamos usá-la:

//+------------------------------------------------------------------+
//| Create a specified timeseries list                               |
//+------------------------------------------------------------------+
bool CTimeSeries::Create(const ENUM_TIMEFRAMES timeframe,const uint required=0)
  {
   CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ),this.m_symbol," ",TimeframeDescription(timeframe));
      return false;
     }
   if(series_obj.RequiredUsedData()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA));
      return false;
     }
   series_obj.SetAvailable(true);
   return(series_obj.Create(required)>0);
  }
//+------------------------------------------------------------------+
//| Create all timeseries lists                                      |
//+------------------------------------------------------------------+
bool CTimeSeries::CreateAll(const uint required=0)
  {
   bool res=true;
   for(int i=0;i<21;i++)
     {
      CSeries *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || series_obj.RequiredUsedData()==0)
         continue;
      series_obj.SetAvailable(true);
      res &=(series_obj.Create(required)>0);
     }
   return res;
  }
//+------------------------------------------------------------------+

Nos métodos de atualização de série temporal se for detectado o evento "Nova Barra", adicionamos o envio de mensagem sobre esse evento para o gráfico do programa de controle com ajuda do método SendEvent() do objeto da série temporal CSeries, que analisamos acima:

//+------------------------------------------------------------------+
//| Update a specified timeseries list                               |
//+------------------------------------------------------------------+
void CTimeSeries::Refresh(const ENUM_TIMEFRAMES timeframe,
                          const datetime time=0,
                          const double open=0,
                          const double high=0,
                          const double low=0,
                          const double close=0,
                          const long tick_volume=0,
                          const long volume=0,
                          const int spread=0)
  {
   CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0)
      return;
   series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread);
   if(series_obj.IsNewBar(time))
     {
      series_obj.SendEvent();
      this.SetTerminalServerDate();
     }
  }
//+------------------------------------------------------------------+
//| Update all timeseries lists                                      |
//+------------------------------------------------------------------+
void CTimeSeries::RefreshAll(const datetime time=0,
                          const double open=0,
                          const double high=0,
                          const double low=0,
                          const double close=0,
                          const long tick_volume=0,
                          const long volume=0,
                          const int spread=0)
  {
   bool upd=false;
   for(int i=0;i<21;i++)
     {
      CSeries *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || series_obj.DataTotal()==0)
         continue;
      series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread);
      if(series_obj.IsNewBar(time))
        {
         series_obj.SendEvent();
         upd &=true;
        }
     }
   if(upd)
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+


Adicionamos a classe-coleção de séries temporais CTimeSeriesCollection no arquivo \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.

Fazemos com que o tipo de lista-coleção de séries temporais seja do tipo da classe CListObj.
Para fazer isso, anexamos o arquivo da classe CListObj e quanto ao tipo de lista de coleção alteramos CArrayObj para CListObj:

//+------------------------------------------------------------------+
//|                                         TimeSeriesCollection.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Series\TimeSeries.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
//+------------------------------------------------------------------+
//| Symbol timeseries collection                                     |
//+------------------------------------------------------------------+
class CTimeSeriesCollection : public CObject
  {
private:
   CListObj                m_list;                    // List of applied symbol timeseries
//--- Return the timeseries index by symbol name
   int                     IndexTimeSeries(const string symbol);
public:

Na seção pública da classe, declaramos o método para retornar a barra de série temporal especificada pelo índice de série temporal do gráfico, o método que retorna o sinalizador de abertura de nova barra de série temporal especificada e o método para atualizar séries temporais que não pertencem ao símbolo atual:

//--- Return the flag of data synchronization with the server data of the (1) specified timeseries of the specified symbol,
//--- (2) the specified timeseries of all symbols, (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                    SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SyncData(const string symbol,const uint required=0,const int rates_total=0);
   bool                    SyncData(const uint required=0,const int rates_total=0);

//--- Return the bar of the specified timeseries of the specified symbol of the specified position 
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true);
//--- Return the flag of opening a new bar of the specified timeseries of the specified symbol
   bool                    IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0);

//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols,
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols

   void                    RefreshOther(void);

//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(const bool created=true);
   void                    PrintShort(const bool created=true);
   
   
//--- Constructor
                           CTimeSeriesCollection();
  };
//+------------------------------------------------------------------+

No construtor da classe definimos o identificador da coleção de séries temporais para a lista de objetos-séries temporais:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTimeSeriesCollection::CTimeSeriesCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_SERIES_ID);
  }
//+------------------------------------------------------------------+

Implementação dos métodos para retornar o objeto-barra pelo índice de série temporal e o evento "Nova Barra" a partir da lista de série temporal especificada:

//+-------------------------------------------------------------------------+
//| Return the bar of the specified timeseries                              |
//| of the specified symbol of the specified position                       |
//| from_series=true - by the timeseries index, false - by the list index   |
//+-------------------------------------------------------------------------+
CBar *CTimeSeriesCollection::GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true)
  {
//--- Get the timeseries object index in the timeseries collection list by a symbol name
   int idx=this.IndexTimeSeries(symbol);
   if(idx==WRONG_VALUE)
      return NULL;
//--- Get the pointer to the timeseries object from the collection list of timeseries objects by the obtained index
   CTimeSeries *timeseries=this.m_list.At(idx);
   if(timeseries==NULL)
      return NULL;
//--- Get the specified timeseries from the symbol timeseries object by the specified timeframe
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return NULL;
//--- Depending on the from_series flag, return the pointer to the bar
//--- either by the chart timeseries index or by the bar index in the timeseries list
   return(from_series ? series.GetBarBySeriesIndex(index) : series.GetBarByListIndex(index));
  }
//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//| for a specified timeseries of a specified symbol                 |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0)
  {
//--- Get the timeseries object index in the timeseries collection list by a symbol name
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
//--- Get the pointer to the timeseries object from the collection list of timeseries objects by the obtained index
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
//--- Get the specified timeseries from the symbol timeseries object by the specified timeframe
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return false;
//--- Return the result of checking the new bar of the specified timeseries
   return series.IsNewBar(time);
  }
//+------------------------------------------------------------------+

Implementação do método de atualização para todas as séries temporais, exceto a séries temporal do símbolo atual:

//+------------------------------------------------------------------+
//| Update all timeseries except the current symbol                  |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::RefreshOther(void)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      if(timeseries.Symbol()==::Symbol() || !timeseries.IsNewTick())
         continue;
      timeseries.RefreshAll();
     }
  }
//+------------------------------------------------------------------+

Num ciclo percorrendo a lista de todos os objetos-séries temporais obtemos o seguinte objeto-série temporal, e se o símbolo do objeto for igual ao símbolo do gráfico no qual o programa está sendo executado, esse objeto de série temporal será ignorado.

Neste método, bem como nos métodos para atualizar as séries temporais apresentados abaixo, foi adicionada uma verificação adicional do sinalizador novo tick e, se não houver novo tick, a série temporal será ignorada e seus dados não serão atualizados:

//+------------------------------------------------------------------+
//| Update the specified timeseries of the specified symbol          |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   if(!timeseries.IsNewTick())
      return;
   timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);
  }
//+------------------------------------------------------------------+
//| Update the specified timeseries of all symbols                   |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const ENUM_TIMEFRAMES timeframe,
                                    const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      if(!timeseries.IsNewTick())
         continue;
      timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);
     }
  }
//+------------------------------------------------------------------+
//| Update all timeseries of the specified symbol                    |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,
                                    const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   if(!timeseries.IsNewTick())
      return;
   timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread);
  }
//+------------------------------------------------------------------+
//| Update all timeseries of all symbols                             |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      if(!timeseries.IsNewTick())
         continue;
      timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread);
     }
  }
//+------------------------------------------------------------------+


A etapa final é fazer as melhorias necessárias no arquivo de classe do objeto principal da biblioteca CEngine.

Abrimos o arquivo em \MQL5\Include\DoEasy\Engine.mqh.
Na seção privada da classe declaramos uma variável para armazenar o tipo de programa que funciona com base na biblioteca:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CTimeSeriesCollection m_series;                       // Timeseries collection
   CResourceCollection  m_resource;                      // Resource list
   CTradingControl      m_trading;                       // Trading management object
   CArrayObj            m_list_counters;                 // List of timer counters
   int                  m_global_error;                  // Global error code
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_tester;                     // Flag of working in the tester
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   bool                 m_is_account_event;              // Account change event flag
   bool                 m_is_symbol_event;               // Symbol change event flag
   ENUM_TRADE_EVENT     m_last_trade_event;              // Last account trading event
   int                  m_last_account_event;            // Last event in the account properties
   int                  m_last_symbol_event;             // Last event in the symbol properties
   ENUM_PROGRAM_TYPE    m_program;                       // Program type

Na seção pública da classe declaramos um método para processamento de eventos NewTick dos EAs:

//--- (1) NewTick event timer and (2) handler
   void                 OnTimer(void);
   void                 OnTick(void);

Aqui, na seção pública declaramos o método que retorna o objeto-barra da série temporal especificada do símbolo especificado de acordo com o índice da série temporal do gráfico,
e o método que retorna o sinalizador indicando abertura de nova barra da série temporal especificada do símbolo especificado:

//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols,
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                 SeriesCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0)
                          { return this.m_series.CreateSeries(symbol,timeframe,required);          }
   bool                 SeriesCreate(const ENUM_TIMEFRAMES timeframe,const uint required=0)
                          { return this.m_series.CreateSeries(timeframe,required);                 }
   bool                 SeriesCreate(const string symbol,const uint required=0)
                          { return this.m_series.CreateSeries(symbol,required);                    }
   bool                 SeriesCreate(const uint required=0)
                          { return this.m_series.CreateSeries(required);                           }

//--- Return the bar of the specified timeseries of the specified symbol of the specified position
   CBar                *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true)
                          { return this.m_series.GetBar(symbol,timeframe,index,from_series);                   }
//--- Return the flag of opening a new bar of the specified timeseries of the specified symbol
   bool                 SeriesIsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0)
                          { return this.m_series.IsNewBar(symbol,timeframe,time);                  }

//--- Update (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols,
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols

Na mesma seção da classe declaramos os métodos que retornam as propriedades padrão das barras para o símbolo especificado, série temporal e sua posição na série temporal do gráfico (índice de barra):

//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume,
//--- (7) RealVolume, (8) Spread of the specified bar of the specified symbol of the specified timeframe
   double               SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   datetime             SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   long                 SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   long                 SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   int                  SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);

//--- Set the following for the trading classes:
//--- (1) correct filling policy, (2) filling policy,
//--- (3) correct order expiration type, (4) order expiration type,
//--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date,
//--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts

No construtor da classe definimos o tipo de programa a executar e criamos o contador do temporizador da coleção das séries temporais:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+

Ao manipulador OnTimer() da biblioteca adicionamos o trabalho com o temporizador da coleção de séries temporais (o código extra é cortado):

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer of the collections of historical orders and deals, as well as of market orders and positions
//...

//--- Account collection timer
//...
     
//--- Timer 1 of the symbol collection (updating symbol quote data in the collection)
//...

//--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window)
//...

//--- Trading class timer
//...
     
//--- Timeseries collection timer
   index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- If this is not a tester
         if(!this.IsTester())
           {
            //--- If the pause is over, work with the timeseries list (except for the current symbol timeseries)
            if(counter.IsTimeDone())
               this.m_series.RefreshOther();
           }
         //--- In case of the tester, work with the timeseries list by tick (except for the current symbol timeseries)
         else
            this.m_series.RefreshOther();
        }
     }
  }
//+------------------------------------------------------------------+

O trabalho com contadores de temporizadores de coleções foi considerado ao criar o objeto principal da biblioteca \CEngine, o resto é descrito nos comentários do código.
Uma observação: no temporizador, são processadas apenas as séries temporais cujo símbolo não coincide com o símbolo do gráfico no qual o programa está sendo executado.
Como aqui, no temporizador, atualizamos as séries temporais ao registrar os eventos "Novo Tick" para símbolos não nativos, capturamos esses eventos no temporizador.
E para atualizar as séries temporais do símbolo atual, criamos o método OnTick(), que executaremos a partir do manipulador Ontick() do EA:

//+------------------------------------------------------------------+
//| NewTick event handler                                            |
//+------------------------------------------------------------------+
void CEngine::OnTick(void)
  {
//--- If this is not a EA, exit
   if(this.m_program!=PROGRAM_EXPERT)
      return;
//--- Update the current symbol timeseries
   this.SeriesRefresh(NULL,PERIOD_CURRENT);
  }
//+------------------------------------------------------------------+

Implementação dos métodos para obter as principais propriedades da barra especificada da série temporal especificada:

//+------------------------------------------------------------------+
//| Return the specified bar's Open                                  |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.Open() : 0);
  }
//+------------------------------------------------------------------+
//| Return the specified bar's High                                  |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.High() : 0);
  }
//+------------------------------------------------------------------+
//| Return the specified bar's Low                                   |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.Low() : 0);
  }
//+------------------------------------------------------------------+
//| Return the specified bar's Close                                 |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.Close() : 0);
  }
//+------------------------------------------------------------------+
//| Return the specified bar's Time                                  |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
datetime CEngine::SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.Time() : 0);
  }
//+------------------------------------------------------------------+
//| Return the specified bar's TickVolume                            |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
long CEngine::SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return the specified bar's RealVolume                            |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
long CEngine::SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return the specified bar's Spread                                |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
int CEngine::SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.Spread() : INT_MIN);
  }
//+------------------------------------------------------------------+

Aqui basta: pegarmos o objeto-barra por símbolo e período gráfico da série temporal a partir do índice especificado da série temporal do gráfico (0 é a barra atual) e retornarmos a propriedade da barra correspondente.

Essas são todas as modificações necessárias hoje para criar atualizações automáticas dos dados de preços das séries temporais usadas no programa, enviar eventos para o gráfico a do programa de controle e receber dados das séries temporais criadas nele.

Teste

Verificamos o funcionamento assim:
criamos três séries temporais para os períodos gráficos atuais de três símbolos, obtemos o objeto da barra zero (classe CBar) a partir do objeto-coleção das séries temporais (CTimeSeriesCollection) e exibimos nos comentários no gráfico os dados desta barra com ajuda dos seus métodos, que retornam o nome abreviado do objeto-barra + descrição dos parâmetros do objeto-barra. Na segunda linha do comentário exibimos os dados da barra zero num formato semelhante, mas criados com ajuda dos métodos do objeto principal da biblioteca CEngine, que retornam os dados da barra especificada do símbolo especificado do período especificado.

Estes dados deverão ser atualizados em tempo real no testador e no gráfico em que o EA está sendo executado.
Também processaremos o recebimento de eventos de objetos da classe CSeries que enviam eventos Nova Barra para o gráfico do programa de controle e observaremos que o recebimento desses eventos no programa em execução no gráfico do símbolo seja bem-sucedido.

Para fazer um teste, pegamos no EA do artigo anterior e o salvamos na nova pasta
\MQL5\Experts\TestDoEasy\Part38\ usando o novo nome TestDoEasyPart38.mq5.

Alteramos o manipulador OnTick() do EA assim:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();       // Working in the timer
      PressButtonsControl();  // Button pressing control
      EventsHandling();       // Working with events
     }
//--- Handle the NewTick event in the library
   engine.OnTick();

//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();    // Trailing positions
      TrailingOrders();       // Trailing of pending orders
     }
   
//--- Bet the zero bar of the current timeseries
   CBar *bar=engine.SeriesGetBar(NULL,PERIOD_CURRENT,0);
   if(bar==NULL)
      return;
//--- Create a string of parameters of the current bar similar to the one
//--- displayed by the bar object description:
//--- bar.Header()+": "+bar.ParameterDescription()
   string parameters=
     (TextByLanguage("Бар \"","Bar \"")+Symbol()+"\" "+TimeframeDescription((ENUM_TIMEFRAMES)Period())+"[0]: "+TimeToString(bar.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+
      ", O: "+DoubleToString(engine.SeriesOpen(NULL,PERIOD_CURRENT,0),Digits())+
      ", H: "+DoubleToString(engine.SeriesHigh(NULL,PERIOD_CURRENT,0),Digits())+
      ", L: "+DoubleToString(engine.SeriesLow(NULL,PERIOD_CURRENT,0),Digits())+
      ", C: "+DoubleToString(engine.SeriesClose(NULL,PERIOD_CURRENT,0),Digits())+
      ", V: "+(string)engine.SeriesTickVolume(NULL,PERIOD_CURRENT,0)+
      ", Real: "+(string)engine.SeriesRealVolume(NULL,PERIOD_CURRENT,0)+
      ", Spread: "+(string)engine.SeriesSpread(NULL,PERIOD_CURRENT,0)
     );
//--- Display the data received from the bar object in the first line of the chart comment,
//--- while the second line contains the methods of receiving timeseries price data
   Comment(bar.Header(),": ",bar.ParameterDescription(),"\n",parameters);
  }
//+------------------------------------------------------------------+

Aqui tudo é simples: este bloco de código é um modelo padrão usado ao trabalhar com a biblioteca DoEasy. Hoje adicionamos uma chamada do manipulador de eventos NewTick processado pela biblioteca a cada tick - atualização das séries temporais criadas. Todas as séries temporais ausentes (declaradas mas não criadas pelos métodos Create() são ignoradas, ou seja, a biblioteca não é atualizada). A chamada deste método a partir do manipulador OnTick() para EAs no futuro será necessária para atualizar os dados da série temporal atual.

Em seguida, obtemos o objeto-barra a partir das séries temporais do símbolo e período atuais, criamos uma linha de descrição de dados para a barra recebida e imprimimos duas linhas nos comentários:
a primeira linha é exibida usando os métodos do objeto-barra,
a segunda linha é uma string coletada a partir de dados obtidos através dos métodos do objeto principal da biblioteca, objetos esses que retornam os dados da barra solicitada.

Na função de inicialização da biblioteca OnInitDoEasy(), o bloco de código para criar séries temporais de todos os símbolos usados mudou um pouco:

//--- Implement displaying the list of used timeframes only for MQL5 - MQL4 has no ArrayPrint() function
#ifdef __MQL5__
   if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT)
      ArrayPrint(array_used_periods);
#endif 
//--- Create timeseries of all used symbols
   CArrayObj *list_timeseries=engine.GetListTimeSeries();
   if(list_timeseries!=NULL)
     {
      int total=list_timeseries.Total();
      for(int i=0;i<total;i++)
        {
         CTimeSeries *timeseries=list_timeseries.At(i);
         int total_periods=ArraySize(array_used_periods);
         for(int j=0;j<total_periods;j++)
           {
            ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[j]);
            timeseries.SyncData(timeframe);
            timeseries.Create(timeframe);
           }
        }
     }
//--- Check created timeseries - display descriptions of all created timeseries in the journal
//--- (true - only created ones, false - created and declared ones)
   engine.GetTimeSeriesCollection().PrintShort(true); // Short descriptions
   //engine.GetTimeSeriesCollection().Print(true);      // Full descriptions


Aqui, obtemos a lista de séries temporais e num ciclo percorrendo a lista de séries temporais obtemos o próximo objeto das séries temporais de acordo com o índice do ciclo, em seguida, num ciclo para o número de períodos gráficos usados criamos a lista-série temporal exigida, depois de sincronizar os dados da série temporal e os dados históricos.

Na função de manipulação de eventos da biblioteca OnDoEasyEvent() adicionamos um bloco de código para manipular eventos de séries temporais (o código extra é cortado):

//+------------------------------------------------------------------+
//| Handling DoEasy library events                                   |
//+------------------------------------------------------------------+
void OnDoEasyEvent(const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time
   ushort msc=engine.EventMSC(lparam);
   ushort reason=engine.EventReason(lparam);
   ushort source=engine.EventSource(lparam);
   long time=TimeCurrent()*1000+msc;
   
//--- Handling symbol events
//...  
     
//--- Handling account events
//...
     
//--- Handling market watch window events
//...
     
//--- Handling timeseries events
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- "New bar" event
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
        }
     }
     
//--- Handling trading events
//...

  }
//+------------------------------------------------------------------+

Aqui: se o identificador do evento obtido estiver dentro dos identificadores de eventos das séries temporais, e se esse evento for "Nova Barra", exibiremos uma mensagem sobre o evento no log do terminal.

Compilamos o EA e nos seus parâmetros definimos:

  • em Mode of used symbols list o uso de uma determinada lista de símbolo,
  • na lista List of used symbols (comma - separator) deixaremos apenas três símbolo, um dos quais é EURUSD e
  • em Mode of used timeframes list optaremos por trabalhar apenas com o período atual, assim:


Iniciamos o EA no gráfico. Depois de um tempo, no log serão exibidas as mensagens sobre o evento "Nova Narra" nos símbolos usados para o gráfico atual:

New bar on EURUSD M5: 2020.03.11 12:55
New bar on EURAUD M5: 2020.03.11 12:55
New bar on AUDUSD M5: 2020.03.11 12:55
New bar on EURUSD M5: 2020.03.11 13:00
New bar on AUDUSD M5: 2020.03.11 13:00
New bar on EURAUD M5: 2020.03.11 13:00

Iniciamos o EA no modo visual do testador no gráfico de um dos símbolos selecionados nas configurações, por exemplo, EURUSD, e vemos como são alterados os dados da barra zero no comentário no gráfico:


Como podemos ver, as duas linhas (cujos dados são obtidos de maneiras diferentes) têm valores idênticos quanto às propriedades obtidas da barra zero e são atualizados em tempo real a cada tick.

O que vem agora?

No próximo artigo, corrigiremos algumas das deficiências da versão atual da biblioteca que foram observadas após a preparação da publicação do artigo e continuaremos o desenvolvimento do conceito de trabalho com séries temporais; prepararemos uma biblioteca para trabalhar como parte de indicadores.

Abaixo estão anexados todos os arquivos da versão atual da biblioteca e os arquivos do EA de teste. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.

Complementos

Artigos desta série:

Trabalhando com séries temporais na biblioteca DoEasy (Parte 35): Objeto "Barra" e lista-série temporal do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 36): objeto das séries temporais de todos os períodos usados do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 37): coleção de séries temporais - banco de dados de séries temporais para símbolos e períodos


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

Arquivos anexados |
MQL5.zip (3698.04 KB)
MQL4.zip (3697.98 KB)
Monitoramento de sinais de negociação multimoeda (Parte 3): Introdução de algoritmos de busca Monitoramento de sinais de negociação multimoeda (Parte 3): Introdução de algoritmos de busca
No artigo anterior, nós desenvolvemos a parte visual do aplicativo, bem como a interação básica dos elementos da GUI. Desta vez, nós adicionaremos a lógica interna e o algoritmo de preparação dos dados do sinal de negociação, bem como a capacidade de configurar os sinais, buscá-los e visualizá-los no monitor.
Otimização Walk Forward Contínua (parte 5): Panorama do Projeto Otimizador Automático e Criação da Interface Gráfica Otimização Walk Forward Contínua (parte 5): Panorama do Projeto Otimizador Automático e Criação da Interface Gráfica
Este artigo fornece uma descrição mais detalhada da otimização walk-forward na plataforma MetaTrader 5. Nos artigos anteriores, nós consideramos os métodos para gerar e filtrar o relatório de otimização e começar a analisar a estrutura interna do aplicativo responsável pelo processo de otimização. O Otimizador Automático é implementado como uma aplicação em C# e possui sua própria interface gráfica. O quinto artigo é dedicado à criação dessa interface gráfica.
Linguagem MQL como um meio de marcação da interface gráfica de programas MQL. Parte 1 Linguagem MQL como um meio de marcação da interface gráfica de programas MQL. Parte 1
O artigo propõe uma nova ideia para descrever a interface de programas MQL com ajuda das construções da linguagem MQL. As classes especiais transformam o esquema visual MQL em elementos da GUI, permitem gerenciá-los de maneira unificada, configurar propriedades e processar eventos. Além disso, apresenta exemplos de uso de layouts para caixas de diálogo e elementos da biblioteca padrão.
Trabalhando com séries temporais na biblioteca DoEasy (Parte 37): coleção de séries temporais - banco de dados de séries temporais para símbolos e períodos Trabalhando com séries temporais na biblioteca DoEasy (Parte 37): coleção de séries temporais - banco de dados de séries temporais para símbolos e períodos
Este artigo é dedicado à criação de uma coleção de séries temporais com base nos períodos gráficos especificados para todos os símbolos usados no programa. Criaremos uma coleção de séries temporais, os métodos para definir os parâmetros dessas séries e inicialmente as preencheremos com dados históricos.