Trabalhando com séries temporais na biblioteca DoEasy (Parte 48): indicadores multissímbolos multiperíodos num buffer de uma subjanela

Artyom Trishkin | 10 dezembro, 2020

Sumário


Ideia

No último artigo, com ajuda de um exemplo de como trabalhar com o indicador padrão Accelerator Oscillator, vimos os princípios e métodos de exibição de dados de indicadores padrão calculados com base em qualquer símbolo/período para o gráfico atual. Agora precisamos coletar métodos que nos permitem criar outros indicadores padrão. Para isso, temos quase tudo pronto, e o que faremos hoje é, antes de tudo, um teste para determinar as mesmas construções de código em métodos, para posteriormente transferir os blocos de código repetidos em métodos separados. Esta é a coisa mais simples que precisamos fazer para trabalhar com indicadores padrão de buffer único (com apenas um buffer para exibir dados).
Depois, em artigos futuros, determinaremos o que pode ser melhorado no código para reduzi-lo e melhorá-lo com base nos métodos modificados hoje e nos métodos de trabalho com indicadores padrão multibuffer desenvolvidos no próximo artigo.

Hoje criaremos um exemplo de indicador personalizado que exibirá numa subjanela do gráfico atual um dos indicadores padrão selecionados nas configurações, que possuem apenas um buffer desenhado e que exibem seus dados na subjanela do gráfico principal. Para fazer isso, teremos uma etapa preparatória em que precisaremos modificar um pouco as classes da biblioteca e que será importante para a criação de métodos para trabalhar com o resto dos indicadores padrão.


Aprimorando as classes da biblioteca

Primeiro, adicionamos uma nova mensagem da biblioteca ao arquivo \MQL5\Include\DoEasy\Datas.mqh.

Adicionamos o índice da nova mensagem:

   MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF,    // Invalid number of indicator buffers (#property indicator_buffers)
   MSG_LIB_TEXT_BUFFER_TEXT_MAX_BUFFERS_REACHED,      // Reached maximum possible number of indicator buffers
   MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ,            // No buffer object for standard indicator

   MSG_LIB_TEXT_BUFFER_TEXT_STATUS_NONE,              // No drawing

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

   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},
   {"Нет ни одного объекта-буфера для стандартного индикатора","There is no buffer object for the standard indicator"},
   
   {"Нет отрисовки","No drawing"},


Todas as melhorias hoje afetarão a classe-coleção de buffers de indicador no arquivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh, e, naturalmente, a classe CEngine.

No arquivo BuffersCollection.mqh, precisamos adicionar um método para preparar os dados de buffer calculados para todos os indicadores padrão criados, para que a biblioteca possa fazer isso por si mesma por meio do comando a partir do programa. Isso simplificará o código do programa final, não precisaremos pesquisar e obter os objetos necessários.

Na seção pública da classe declaramos este método:

//--- Prepare calculated buffer data of (1) the specified standard indicator and (2) all created standard indicators
   int                     PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy);
   bool                    PreparingDataAllBuffersStdInd(void);
//--- Clear buffer data of the specified standard indicator by the timeseries index

e fora do corpo da classe, escrevemos sua implementação:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of all created standard indicators                               |
//+------------------------------------------------------------------+
bool CBuffersCollection::PreparingDataAllBuffersStdInd(void)
  {
   CArrayObj *list=this.GetListBuffersWithID();
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
   bool res=true;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
         continue;
      CSeriesDE *series=this.m_timeseries.GetSeries(buff.Symbol(),buff.Timeframe());
      if(series==NULL)
         continue;
      int used_data=(int)series.AvailableUsedData();
      int copied=this.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),used_data);
      if(copied<used_data)
         res &=false;
     }
   return res;
  }
//+------------------------------------------------------------------+

Cada objeto-buffer usado para calcular o indicador padrão deve ter um identificador atribuído quer automaticamente de acordo com o tipo do indicador padrão (ENUM_INDICATOR) quer manualmente a partir do programa ao criar os objetos-buffer necessários.

Aqui, em primeiro lugar, obtemos uma lista de todos os objetos-buffer cujo identificador não é igual a -1.
Se a lista estiver vazia, exibimos uma mensagem de que não há objetos-buffers criados para indicadores padrão e retornamos false
.
Em seguida, num loop percorrendo o número total de objetos-buffers que têm um identificador de indicador padrão, obtemos o próximo objeto-buffer. Temos pelo menos dois desses objetos-buffers para cada indicador padrão, um para o calculado e outro para o desenhado. Precisamos apenas dos buffers calculados, mas neste loop não os selecionamos porque no método PreparingDataBufferStdInd(), que consideramos no último artigo, e que refinaremos hoje um pouco mais tarde, apenas é selecionado o buffer calculado para continuar trabalhando com ele. Por isso, não vamos repetir isso.
Agora precisamos determinar quantas barras temos disponíveis com base no símbolo/período para o qual o objeto buffer resultante é criado. Precisamos desse valor para determinar o quantos dados do indicador padrão copiar para o objeto-buffer pretendido para ele. Para isso, obtemos a série temporal que precisamos de acordo com o valor do símbolo e do timeframe do objeto-buffer, e tiramos dele a quantidade de dados disponíveis.
Depois, chamamos o método PreparingDataBufferStdInd() para copiar a quantidade necessária de dados do indicador para o objeto-buffer calculado.
Se forem copiados menos dados do que o necessário, ao valor da variável res adicionamos o valor false. Se pelo menos um dos objetos-buffers existentes não for copiado na quantidade necessária, na nossa variável res será registrado o valor false. Desde o método, retornamos o valor desta variável. O método retorna true se todos os dados de todos os objetos-buffers calculados são copiados com sucesso.

Gostaria de ressaltar que por enquanto este método copia todos os dados disponíveis do identificador do indicador para o buffer calculado. Isso é um luxo inadmissível, a cada tick copiar grandes quantidades de dados e até mesmo para mais de um indicador. Mas será assim porque no momento estamos criando uma funcionalidade para trabalhar com indicadores padrão. Aqui a velocidade não é tão importante como uma lógica correta. Mais para frente, vamos garantir que todos os dados sejam copiados apenas sob as condições necessárias (primeiro inicialização, alterações nos dados históricos) e, em outros casos, apenas a quantidade necessária de dados, uma ou duas barras.

No último artigo também declaramos todos os métodos para a criação de indicadores padrão e buffers destinados a eles. Mas não realizamos a implementação para eles (exceto dois métodos para criar o indicador AC e AD). Hoje vamos adicionar implementações de métodos para criar identificadores de indicadores padrão e seus objetos-buffers, para aqueles que têm apenas um buffer de indicador, sendo que o próprio indicador desenhará os dados na subjanela do gráfico principal.

Além disso, faremos com que a cor dos indicadores criados coincida com a cor dos indicadores padrão correspondentes por padrão. Ao mesmo tempo, ainda poderemos definir nossas próprias cores para esses objetos-buffers, bastará apenas passar o índice da cor desejada ao calcular os buffers no loop principal.

Vamos considerar as mudanças feitas no método de criação de buffers para o indicador Accelerator Oscillator padrão:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period AC                              |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iAC(symbol,timeframe);
   int identifier=(id==WRONG_VALUE ? IND_AC : id);
   color array_colors[3]={clrGreen,clrRed,clrGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the histogram buffer from the zero line
      this.CreateHistogram();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetShowData(true);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
      buff.SetColors(array_colors);
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Uma vez que o indicador Accelerator Oscillator padrão tem duas cores para exibir seus valores, declaramos uma variedade de cores de tamanho 3, que inicializamos imediatamente com três cores. Por que três? Bem, porque a cor com o índice 0 exibe os valores crescentes da linha do indicador, já a cor com o índice 1, os valores decrescentes. Mas precisamos de mais uma cor que exiba valores iguais de duas barras adjacentes da linha do indicador. No Accelerator Oscillator padrão, elas são exibidas na cor dos valores crescentes, por isso, são necessárias três cores.
Depois de criar um objeto-buffer desenhado, definimos suas cores de renderização a partir desta matriz.

Ao obter um ponteiro para o último objeto-buffer criado, pode acontecer que esse ponteiro seja recebido de maneira incorreta. Anteriormente, não considerávamos esta situação, o que era potencialmente perigoso, uma vez que acessar um ponteiro inválido leva ao encerramento anormal do programa.
Por isso, em tal situação, precisamos sair do método com retorno do valor INVALID_HANDLE.

Para indicadores padrão que têm apenas uma cor de desenho, definimos uma matriz de cores de apenas um elemento. Isso é tudo o que diferencia os métodos de criação de identificadores de indicadores padrão coloridos e seus objetos-buffers dos métodos de criação de indicadores de padrão monocromáticos, quer dizer, é apenas o tamanho da matriz de cores.

Por exemplo, o método para criar o indicador Average True Range:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period ATR                             |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iATR(symbol,timeframe,ma_period);
   int identifier=(id==WRONG_VALUE ? IND_ATR : id);
   color array_colors[1]={clrLightSeaGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ATR);
      buff.SetShowData(true);
      buff.SetLabel("ATR("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Average True Range");
      buff.SetColors(array_colors);
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ATR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("ATR("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Average True Range");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Hoje vamos escrever métodos apenas para indicadores padrão de buffer único desenhados numa subjanela, isto é:

Os métodos para criar os indicadores multissímbolos multiperíodos listados acima já foram criados e não os consideraremos aqui. Eles são idênticos aos dois métodos que consideramos, e você pode se familiarizar com eles nos arquivos anexados ao artigo.

O leitor atento provavelmente já notou na lista acima a ausência de um indicador que é desenhado numa subjanela e tem um buffer desenhado, isto é, o indicador Market Facilitation Index. Sim, ele tem um buffer e é desenhado numa subjanela. Mas para calcular a cor das colunas do histograma, é necessário outro buffer calculado, um buffer do indicador de volume. Portanto, iremos referir este indicador à seção de indicadores de multibuffer na subjanela.

O método PreparingDataBufferStdInd() preenche a matriz do objeto-buffer calculado com dados a partir do identificador do indicador. Já discutimos esse método no último artigo, sendo que apenas realizamos o preenchimento do buffer do indicador AC:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of the specified standard indicator                              |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return 0;
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      case IND_AC :
        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

O mais interessante é que para outros indicadores de buffer único, precisamos fazer exatamente as mesmas ações. Aqui aparece para nós ajudar o próprio operador switch, que compara os valores das expressões com as constantes em todas as variações case e o controle é transferido para o operador que corresponde ao valor da expressão. Cada case é concluído quer pelo operador de conclusão break quer pelo operador de retorno return. Se não colocarmos nenhum desses operadores finais dentro de case, após processar o valor da expressão no case desejado, o controle passará para o próximo.
Portanto, em vez de clonar operações necessárias do mesmo tipo em todos os cases em que exatamente as mesmas operações devem ser realizadas, basta combinar todos esses casos num só, sem operador de retorno ou conclusão:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of the specified standard indicator                              |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      //--- Single-buffer standard indicators in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
 
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

Vamos fazer o mesmo no método para limpar os dados do buffer do indicador padrão especificado:

//+------------------------------------------------------------------+
//| Clear buffer data of the specified standard indicator            |
//| by the timeseries index                                          |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
//--- Get the list of buffer objects by type and ID
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   if(list==NULL || list.Total()==0)
      return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
      //--- Single-buffer standard indicators in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

e no método para definir o valor para os dados do buffer do indicador padrão especificado:

//+------------------------------------------------------------------+
//| Set values for the current chart to the specified buffer         |
//| standard indicator by the timeseries index according to          |
//| the buffer object symbol/period                                  |
//+------------------------------------------------------------------+
bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE)
  {
//--- Get the list of buffer objects by type and ID
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
//--- Get the list of drawn objects with ID
   CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Get the list of calculated buffers with ID
   CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Exit if any of the lists is empty
   if(list_data.Total()==0 || list_calc.Total()==0)
      return false;
//--- Declare the necessary objects and variables
   CBuffer *buffer_data=NULL;
   CBuffer *buffer_calc=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   datetime time_period=0;
   double value0=EMPTY_VALUE, value1=EMPTY_VALUE;

//--- Depending on the standard indicator type
   switch((int)ind_type)
     {
      //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        //--- Get drawn and calculated buffer objects
        buffer_data=list_data.At(0);
        buffer_calc=list_calc.At(0);
        if(buffer_calc==NULL || buffer_data==NULL || buffer_calc.GetDataTotal(0)==0) return false;
        
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc.Symbol(),buffer_calc.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc.GetDataTotal()-1) return false;
        //--- Get the value by the index from the indicator buffer
        value0=buffer_calc.GetDataBufferValue(0,index_period);
        if(buffer_calc.Symbol()==::Symbol() && buffer_calc.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
           time_period=::iTime(buffer_calc.Symbol(),buffer_calc.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Get the appropriate current chart bar
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
           num_bars=::PeriodSeconds(buffer_calc.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value1=(series_index_start+num_bars>buffer_data.GetDataTotal()-1 ? value0 : buffer_data.GetDataBufferValue(0,series_index_start+num_bars));
        //--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index
        //--- and set the color of the drawn buffer depending on the value0 and value1 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data.SetBufferValue(0,index,value0);
           buffer_data.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value0>value1 ? 0 : value0<value1 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Nós consideramos todos os três métodos no último artigo, e hoje apenas combinamos os cases em que precisamos fazer o mesmo manipulador.
No final do manipulador, é necessária uma instrução de retorno ou interrupção, para que o processamento do valor switch não passe para outros cases, nos quais haverá outro manipulador.

Agora precisamos complementar a classe do objeto principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh.

Os métodos para criação de indicadores padrão e objetos-buffers para eles já foram escritos na classe-coleção de buffers (por enquanto alguns métodos que ainda não foram considerados retornam INVALID_HANDLE), e precisamos especificar o acesso a todos esses métodos para o programa na classe CEngine.

Na seção pública da classe, escreveremos a chamada de todos esses métodos:
//--- The methods of creating standard indicators and buffer objects for them
//--- Create the standard Accelerator Oscillator indicator
   bool                 BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE);                    }
//--- Create the standard Accumulation/Distribution indicator
   bool                 BufferCreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateAD(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);     }
//--- Create the standard ADX indicator
   bool                 BufferCreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADX(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);        }
//--- Create the standard ADX Wilder indicator
   bool                 BufferCreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADXWilder(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Alligator indicator
   bool                 BufferCreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                              const int jaw_period,const int jaw_shift,
                                              const int teeth_period,const int teeth_shift,
                                              const int lips_period,const int lips_shift,
                                              const ENUM_MA_METHOD ma_method,const ENUM_APPLIED_PRICE applied_price,const int id)
                          {
                           return(this.m_buffers.CreateAlligator(symbol,timeframe,
                                                                 jaw_period,jaw_shift,
                                                                 teeth_period,teeth_shift,
                                                                 lips_period,lips_shift,
                                                                 ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Adaprive Moving Average indicator
   bool                 BufferCreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                        const int ama_period,
                                        const int fast_ema_period,
                                        const int slow_ema_period,
                                        const int ama_shift,
                                        const ENUM_APPLIED_PRICE applied_price,
                                        const int id)
                          { 
                           return(this.m_buffers.CreateAMA(symbol,timeframe,
                                                           ama_period,
                                                           fast_ema_period,slow_ema_period,
                                                           ama_shift,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Awesome Oscillator indicator
   bool                 BufferCreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAO(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Create the standard Average True Range indicator
   bool                 BufferCreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateATR(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Bollinger Bands indicator
   bool                 BufferCreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                          const int bands_period,
                                          const int bands_shift,
                                          const double deviation,
                                          const ENUM_APPLIED_PRICE applied_price,
                                          const int id)
                          {
                           return(this.m_buffers.CreateBands(symbol,timeframe,
                                                             bands_period,bands_shift,
                                                             deviation,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Bears Power indicator
   bool                 BufferCreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBearsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Bulls Power indicator
   bool                 BufferCreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBullsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Chaikin Oscillator indicator
   bool                 BufferCreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          {
                           return(this.m_buffers.CreateChaikin(symbol,timeframe,
                                                                fast_ma_period,slow_ma_period,
                                                                ma_method,applied_volume,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Commodity Channel Index indicator
   bool                 BufferCreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateCCI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard DEMA indicator
   bool                 BufferCreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateDEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard DeMarker indicator
   bool                 BufferCreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateDeMarker(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Envelopes indicator
   bool                 BufferCreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const double deviation,
                                       const int id)
                          {
                           return(this.m_buffers.CreateEnvelopes(symbol,timeframe,
                                                                  ma_period,ma_shift,ma_method,
                                                                  applied_price,deviation,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Force Index indicator
   bool                 BufferCreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateForce(symbol,timeframe,ma_period,ma_method,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Fractals indicator
   bool                 BufferCreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateFractals(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Create the standard FrAMA indicator
   bool                 BufferCreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateFrAMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Gator indicator
   bool                 BufferCreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          {
                           return(this.m_buffers.CreateGator(symbol,timeframe,
                                                              jaw_period,jaw_shift,
                                                              teeth_period,teeth_shift,
                                                              lips_period,lips_shift,
                                                              ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Ichimoku indicator
   bool                 BufferCreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int tenkan_sen,
                                       const int kijun_sen,
                                       const int senkou_span_b,
                                       const int id)
                          { return(this.m_buffers.CreateIchimoku(symbol,timeframe,tenkan_sen,kijun_sen,senkou_span_b,id)!=INVALID_HANDLE);  }
//--- Create the standard BW MFI indicator
   bool                 BufferCreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateBWMFI(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Momentum indicator
   bool                 BufferCreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int mom_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMomentum(symbol,timeframe,mom_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Money Flow Index indicator
   bool                 BufferCreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateMFI(symbol,timeframe,ma_period,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Moving Average indicator
   bool                 BufferCreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Moving Average of Oscillator indicator
   bool                 BufferCreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateOsMA(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard MACD indicator
   bool                 BufferCreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMACD(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard On Balance Volume indicator
   bool                 BufferCreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateOBV(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Parabolic SAR indicator
   bool                 BufferCreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const double step,
                                       const double maximum,
                                       const int id)
                          { return(this.m_buffers.CreateSAR(symbol,timeframe,step,maximum,id)!=INVALID_HANDLE);  }
//--- Create the standard Relative Strength Index indicator
   bool                 BufferCreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateRSI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard RVI indicator
   bool                 BufferCreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateRVI(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the Standard Deviation indicator
   bool                 BufferCreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateStdDev(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Stochastic indicator
   bool                 BufferCreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int Kperiod,
                                       const int Dperiod,
                                       const int slowing,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_STO_PRICE price_field,
                                       const int id)
                          { return(this.m_buffers.CreateStochastic(symbol,timeframe,Kperiod,Dperiod,slowing,ma_method,price_field,id)!=INVALID_HANDLE);  }
//--- Create the standard TEMA indicator
   bool                 BufferCreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Triple Exponential Average indicator
   bool                 BufferCreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTriX(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard William's Percent Range indicator
   bool                 BufferCreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id)
                          { return(this.m_buffers.CreateWPR(symbol,timeframe,calc_period,id)!=INVALID_HANDLE);  }
//--- Create the standard VIDYA indicator
   bool                 BufferCreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int cmo_period,
                                       const int ema_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateVIDYA(symbol,timeframe,cmo_period,ema_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Volumes indicator
   bool                 BufferCreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateVolumes(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
                          
//--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object

Os métodos na classe-coleção retornam o tipo int, em particular o identificador do indicador padrão. Aqui, retornaremos um valor de tipo bool, pois é mais fácil trabalhar com métodos do programa final desta forma. Por isso, esses métodos retornam o resultado da comparação do valor (que é devolvido pelo método correspondente da classe-coleção de buffer) com o valorINVALID_HANDLE.

Na seção pública da classe, escreveremos dois métodos, um que permite preparar os dados do buffer calculado para todos os indicadores padrão criados, bem como retorna o resultado do trabalho do método de mesmo nome da classe-coleção de objetos de buffer, considerado por nós acima, edeclaramos outro método que retorna uma descrição do objeto-buffer pelo tipo de um indicador padrão e seu identificador:

//--- Prepare data of the calculated buffer of all created standard indicators
   bool                 BufferPreparingDataAllBuffersStdInd(void)                         { return this.m_buffers.PreparingDataAllBuffersStdInd();    }

//--- Return the standard indicator buffer description by type and ID
   string               BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id);

//--- Display short description of all indicator buffers of the buffer collection
   void                 BuffersPrintShort(void);

Fora do corpo da classe, vamos escrever a implementação do método:

//+------------------------------------------------------------------+
//| Return the standard indicator buffer description                 |
//| by type and ID                                                   |
//+------------------------------------------------------------------+
string CEngine::BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id)
  {
   CArrayObj *list=m_buffers.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.Label();
  }
//+------------------------------------------------------------------+

Aqui simplesmente recebemos a lista de objetos-buffers com o tipo de indicador padrão passado para o método correspondente e seu identificador.
Teremos dois desses objetos-buffers na lista, o desenhado e o calculado. Não importa qual desses objetos recebe os dados, pois ambos os objetos os têm, e são os mesmos. Por isso, obtemos um ponteiro para o primeiro objeto-buffer na lista e retornakos sua descrição.

No bloco de processamento do evento "Nova barra" localizado na implementação do método OnDoEasyEvent() vamos corrigir um pouco o recebimento da quantidade da dados copiados:

//--- 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(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
         CArrayObj *list=this.m_buffers.GetListBuffersWithID();
         if(list!=NULL)
           {
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               CBuffer *buff=list.At(i);
               if(buff==NULL)
                  continue;
               string symbol=sparam;
               ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
               if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
                  continue;
               if(buff.Symbol()==symbol && buff.Timeframe()==timeframe )
                 {
                  CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
                  if(series==NULL)
                     continue;
                  int count=::fmin((int)series.AvailableUsedData(),::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated()));
                  this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count);
                 }
              }
           }
        }
      //--- "Bars skipped" event
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
     }
     
//--- Handling trading events

Anteriormente, na linha destacada obtivemos o valor mínimo de dados totais do objeto-buffer e dos dados calculados do indicador padrão:

int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated());

Mas, uma vez que na biblioteca e, quer dizer, para o programa, o número de dados da série temporal usados é definido por padrão como 1000 barras, agora iremos copiar o valor mínimo de três fontes de dados: a quantidade total de dados do objeto-buffer, os dados calculados do indicador padrão e a quantidade padrão de dados (1 000). Normalmente, 1 000 é sempre menor que os outros dois e isso é mais benéfico ao copiar dados em massa.

Essas são todas as modificações das classes da biblioteca para hoje.

Teste

Para testar a criação de indicadores padrão multiperíodos multissímbolos de buffer único desenhados numa subjanela, faremos o seguinte:
vamos criar um indicador personalizado cujas configurações terão opções:

  1. Símbolo usado para criação do indicador padrão
  2. Período gráfico usado (timeframe) para criação do indicador padrão
  3. Tipo de indicador padrão multiperíodos multissímbolos de buffer único criado

Os dados do indicador padrão selecionado serão exibidos na subjanela do gráfico principal do símbolo/período atual.

Pegamos no indicador do artigo anterior e o armazenamos na nova pasta \MQL5\Indicators\TestDoEasy\Part48\ com o novo nome TestDoEasyPart48.mq5.

Aos parâmetros de entrada do indicador adicionamos um parâmetro para escolhermos o indicador padrão exibido:

//|                                             TestDoEasyPart48.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   1

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
sinput   ENUM_INDICATOR       InpIndType        =  IND_AC;        // Type standard indicator
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers

//--- global variables
ENUM_SYMBOLS_MODE    InpModeUsedSymbols=  SYMBOLS_MODE_DEFINES;   // Mode of used symbols list
ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;   // Mode of used timeframes list
string               InpUsedTFs;                                  // List of used timeframes
CEngine              engine;                                      // CEngine library main object
string               prefix;                                      // Prefix of graphical object names
int                  min_bars;                                    // The minimum number of bars for the indicator calculation
int                  used_symbols_mode;                           // Mode of working with symbols
string               array_used_symbols[];                        // The array for passing used symbols to the library
string               array_used_periods[];                        // The array for passing used timeframes to the library
//+------------------------------------------------------------------+

Nem todo indicador padrão funcionará com nosso indicador, apenas aqueles que têm um buffer para plotagem e que são exibidos na subjanela. No manipulador OnInit(), criaremos indicadores padrão adequados por tipo na biblioteca, e para o resto dos indicadores, vamos fazer uma mensagem de erro:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable
   InpUsedTFs=TimeframeDescription(InpPeriod);
//--- Initialize DoEasy library
   OnInitDoEasy();
   
//--- Set indicator global variables
   prefix=engine.Name()+"_";
   //--- calculate the number of bars of the current period fitting in the maximum used period
   //--- Use the obtained value if it exceeds 2, otherwise use 2
   int num_bars=NumberBarsInTimeframe(InpPeriod);
   min_bars=(num_bars>2 ? num_bars : 2);

//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel

//--- Check playing a standard sound using macro substitutions
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(SND_NEWS);

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing a selected standard indicator
   bool success=false;
   switch(InpIndType)
     {
      case IND_AC       :  success=engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_AD       :  success=engine.BufferCreateAD(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                    break;
      case IND_AO       :  success=engine.BufferCreateAO(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_ATR      :  success=engine.BufferCreateATR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_BEARS    :  success=engine.BufferCreateBearsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_BULLS    :  success=engine.BufferCreateBullsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_CHAIKIN  :  success=engine.BufferCreateChaikin(InpUsedSymbols,InpPeriod,3,10,MODE_EMA,VOLUME_TICK,1); break;
      case IND_CCI      :  success=engine.BufferCreateCCI(InpUsedSymbols,InpPeriod,14,PRICE_TYPICAL,1);              break;
      case IND_DEMARKER :  success=engine.BufferCreateDeMarker(InpUsedSymbols,InpPeriod,14,1);                       break;
      case IND_FORCE    :  success=engine.BufferCreateForce(InpUsedSymbols,InpPeriod,13,MODE_SMA,VOLUME_TICK,1);     break;
      case IND_MOMENTUM :  success=engine.BufferCreateMomentum(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);           break;
      case IND_MFI      :  success=engine.BufferCreateMFI(InpUsedSymbols,InpPeriod,14,VOLUME_TICK,1);                break;
      case IND_OSMA     :  success=engine.BufferCreateOsMA(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);          break;
      case IND_OBV      :  success=engine.BufferCreateOBV(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                   break;
      case IND_RSI      :  success=engine.BufferCreateRSI(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                break;
      case IND_STDDEV   :  success=engine.BufferCreateStdDev(InpUsedSymbols,InpPeriod,20,0,MODE_SMA,PRICE_CLOSE,1);  break;
      case IND_TRIX     :  success=engine.BufferCreateTriX(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);               break;
      case IND_WPR      :  success=engine.BufferCreateWPR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_VOLUMES  :  success=engine.BufferCreateVolumes(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);               break;
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Check the number of buffers specified in the 'properties' block

Cada indicador padrão desenhado na subjanela tem seu próprio número de casas decimais e alguns também desenham linhas de nível. Para cada um dos indicadores padrão usados, faremos um conjunto no qual definiremos a largura de bits dos dados de saída e definiremos os níveis, se o indicador padrão os tiver:

//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();
//--- Set levels where they are required and define the data decimal capacity
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AC       : digits+=2;   break;
      case IND_AD       : digits=0;    break;
      case IND_AO       : digits+=1;   break;
      case IND_ATR      :              break;
      case IND_BEARS    : digits+=1;   break;
      case IND_BULLS    : digits+=1;   break;
      case IND_CHAIKIN  : digits=0;    break;
      case IND_CCI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_FORCE    : digits+=1;   break;
      case IND_MOMENTUM : digits=2;    break;
      case IND_MFI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_OSMA     : digits+=2;   break;
      case IND_OBV      : digits=0;    break;
      case IND_RSI      :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STDDEV   : digits+=1;   break;
      case IND_TRIX     :              break;
      case IND_WPR      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
      case IND_VOLUMES  : digits=0;    break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Set the short name for the indicator and bit depth
   string label=engine.BufferGetLabelByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Nosso manipulador OnCalculate() se tornou muito mais simples em comparação com o indicador de teste anterior. Isso ocorre porque criamos os métodos necessários para trabalhar com indicadores padrão, e tudo o que precisamos é preparar dados de indicador padrão e, no loop principal, exibir os dados calculados do indicador padrão no gráfico atual:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the library:             |
//+------------------------------------------------------------------+
//--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays
   CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the library timer
      engine.EventsHandling();      // Working with library events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- limit > 1 means the first launch or changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
      engine.BufferPreparingDataAllBuffersStdInd();
     }
   
//--- Prepare data 
//--- Fill in calculated buffers of all created standard indicators with data
   int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod);
   int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total));
   if(!engine.BufferPreparingDataAllBuffersStdInd())
      return 0;

//--- Calculate the indicator
//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      engine.GetBuffersCollection().SetDataBufferStdInd(InpIndType,1,i,time[i]);
     }
//--- return value of prev_calculated for the next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Isso é tudo o que precisa ser feito para criar um indicador de teste..
O código completo do indicador pode ser encontrado nos arquivos anexados ao artigo.

Vamos compilar o indicador criado, definir o símbolo nas configurações para calcular o indicador GBPUSD padrão e o timeframe M5, e iniciar o próprio indicador no gráfico EURUSD, M1:


A figura não mostra todos os indicadores possíveis, apenas o principal: o indicador desenha o indicador padrão selecionado com base em dados que diferem dos dados do gráfico atual, e as linhas de nível são desenhadas onde são necessárias.

O que vem agora?

No próximo artigo, começaremos a gerar métodos para criar indicadores padrão que exibem dados na janela do gráfico principal e indicadores com vários buffers desenhados.

Todos os arquivos da versão atual da biblioteca e arquivos do indicador de teste estão anexados abaixo. Você pode baixá-los e testar tudo sozinho.
Se você tiver dúvidas, comentários e sugestões, pode expressá-los nos comentários do artigo.
Gostaria de chamar sua atenção para o fato de que neste artigo fizemos um indicador de teste em MQL5 para MetaTrader 5.
Os arquivos anexados estão destinados apenas ao MetaTrader 5 e a versão atual da biblioteca ainda não foi testada no MetaTrader 4.
Após criar a funcionalidade para trabalhar com buffers de indicador e testá-la, tentaremos implementar algumas coisas de MQL5 para MetaTrader 4.

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
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 39): indicadores com base na biblioteca - preparação de dados e eventos das séries temporais
Trabalhando com séries temporais na biblioteca DoEasy (Parte 40): indicadores com base na biblioteca - atualização de dados em tempo real
Trabalhando com séries temporais na biblioteca DoEasy (Parte 41): exemplo de indicador multissímbolo multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 42): classe de um objeto de buffer abstrato de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 43): classes de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 44): classe-coleção de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 45): buffers de indicador multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 46): buffers de indicador multiperíodos multissímbolos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 47): indicadores padrão multiperíodos multissímbolos