Trabalhando com séries temporais na biblioteca DoEasy (Parte 50): indicadores padrão multiperíodos multissímbolos com deslocamento

Artyom Trishkin | 18 dezembro, 2020

Sumário


Ideia

Ao longo de vários artigos, criamos, passo a passo, uma funcionalidade para exibir indicadores padrão no gráfico do símbolo/período atual no modo indicador multissímbolo multiperíodo. Nem todos os indicadores padrão podem ser exibidos neste modo ainda, mas hoje vamos nos desviar do tópico de conversão de indicadores padrão em multimodos.
Não, não faremos outra coisa diferente, tudo estará dentro do mesmo tema, mas... se tentarmos criar um indicador multiperíodo cuja linha seja deslocada no gráfico do símbolo atual, não teremos sucesso. O motivo é que até agora temos usado o valor padrão definido no construtor da classe do objeto-buffer como o deslocamento da linha do indicador, e ele é zero. Já se apenas pegarmos e definirmos o deslocamento da linha do indicador multiperíodo como igual ao valor do deslocamento da linha do indicador padrão, nada acontecerá, pois a linha mudará, mas apenas dentro dessas barras em quanto a linha indicadora padrão é deslocada.
Motivo: para a exibição correta da linha do indicador multiperíodo, é necessário calcular, por um lado, quantas barras do gráfico atual cabem numa barra do período gráfico no qual é calculado o indicador padrão e, por outro lado, quantas barras deslocar a linha. Em outras palavras, se o indicador padrão for calculado no gráfico H4 e o exibirmos no gráfico H1, devemos desenhar uma barra do gráfico H4 em quatro barras do gráfico H1. O mesmo se aplica ao deslocamento: se a linha do indicador padrão for deslocada uma barra no gráfico H4, devemos deslocá-la quatro barras no gráfico H1.

E isso não é tudo. Da mesma forma, ao preencher a matriz-buffer calculada no objeto-buffer, devemos levar em conta o deslocamento da linha do indicador padrão, e essa mudança deve nos indicar o ponto de partida para contar o número de barras que copiamos da matriz de origem do indicador padrão para a matriz receptor do objeto-buffer que criamos.

Após a implementação e cumprir as regras acima, seremos capazes de exibir corretamente o indicador padrão no modo multiperíodo com deslocamento de linha.

Quero ressaltar que nem todos os indicadores padrão se podem converter de maneira tão simples em indicadores com modo multiperíodo e deslocamento de linha. Por agora, os indicadores, cujas linhas, segundo a intenção dos autores, são inicialmente traçadas com um deslocamento (Gator Oscillator e Ichimoku Kinko Hyo) não podem ser exibidos no modo multiperíodo. Por outra parte, o Alligator se pode converter facilmente. Acho que isso se deve ao fato de que no indicador Alligator para cada linha desenhada é definido seu próprio deslocamento, nós as exibimos com o deslocamento calculado, enquanto os indicadores Gator e Ichimoku usam linhas inicialmente deslocadas para seus cálculos. Em qualquer caso, encontraremos o motivo e criaremos esses indicadores como sendo multiperíodos e multissímbolos. Por enquanto, para não demorar muito na busca de motivos (faremos isso em paralelo com o desenvolvimento), criaremos outras funcionalidades de biblioteca. Além disso, muito em breve será mais interessante, uma vez que a inclusão desses indicadores padrão em objetos-barras da classe da série temporal permitirá pesquisar rapidamente qualquer combinação de indicadores com base em qualquer símbolo/período gráfico.

Além de implementar a exibição de indicadores padrão com deslocamento de linha, hoje iremos gerar métodos gerais para preparar e exibir indicadores padrão em multimodos. No momento, implementamos nossos próprios métodos de exibição para cada indicador padrão. A criação gradual de indicadores multissímbolo e multiperíodo nos deu uma imagem clara do que é exatamente o mesmo em todos os métodos individuais. Hoje faremos o que pode ser feito num método universal que funciona com cada um dos indicadores padrão. Isso simplificará e reduzirá muito o tamanho do código da classe-coleção de buffers de indicador.

E, finalmente, hoje removeremos do indicador final o cálculo e a configuração dos níveis do indicador padrão na subjanela e a largura de bits dessas linhas dos indicadores padrão exibidos na janela de dados do terminal. Removeremos esse cálculo para o arquivo das funções do serviço de biblioteca e o chamaremos a partir do indicador de teste. Isso simplificará o código final e o deixará mais claro, librando o usuário final de tarefas rotineiras.


Aprimorando as classes da biblioteca

Para poder processar diferentes indicadores com o mesmo método, precisamos que na listagem do nome das linhas de cada indicador os valores numéricos de tais linhas coincidam. Afinal, cada linha de qualquer indicador pode ser atribuída a três tipos

  1. Linha superior, também conhecida como principal ou como Jaws,
  2. Linha nferior, também conhecida como sinal ou Teeth e +DI,
  3. Linha do meio, também conhecida como Lips e -DI

Ainda não consideramos neste artigo as linhas do indicador Ichimoku, mas isso será mais tarde quando faremos isso multiperíodo também.

Se agora todas as linhas corresponderão umas às outras desta maneira, faremos facilmente os manipuladores de cada linha do indicador no mesmo método.

Abrimos o arquivo \MQL5\Include\DoEasy\Defines.mqh e fazemos as alterações necessárias na enumeração dos tipos de linha dos indicadores padrão:

//+------------------------------------------------------------------+
//| Indicator lines                                                  |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_LINE_MODE
  {
   INDICATOR_LINE_MODE_MAIN      =  0,                      // Main line
   INDICATOR_LINE_MODE_SIGNAL    =  1,                      // Signal line
   INDICATOR_LINE_MODE_UPPER     =  0,                      // Upper line
   INDICATOR_LINE_MODE_LOWER     =  1,                      // Lower line
   INDICATOR_LINE_MODE_MIDDLE    =  2,                      // Middle line
   INDICATOR_LINE_MODE_JAWS      =  0,                      // Jaws line
   INDICATOR_LINE_MODE_TEETH     =  1,                      // Teeth line
   INDICATOR_LINE_MODE_LIPS      =  2,                      // Lips line
   INDICATOR_LINE_MODE_DI_PLUS   =  1,                      // +DI line
   INDICATOR_LINE_MODE_DI_MINUS  =  2,                      // -DI line
  };
//+------------------------------------------------------------------+

Como precisamos copiar os dados da matriz de origem do indicador padrão para a matriz de destino do objeto de buffer calculado com o deslocamento definido para a linha do indicador, no arquivo \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh da classe de objeto buffer abstrato, vamos mudar o método que define o deslocamento da plotagem do indicador - agora ele contém uma verificação de que é um buffer calculado e, caso seja assim, o método retorna que o valor não está definido. Simplesmente removemos esta verificação do método:

//+------------------------------------------------------------------+
//| Set the indicator graphical construction shift                   |
//+------------------------------------------------------------------+
void CBuffer::SetShift(const int shift)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   this.SetProperty(BUFFER_PROP_SHIFT,shift);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+

O método agora fica assim:

//+------------------------------------------------------------------+
//| Set the indicator graphical construction shift                   |
//+------------------------------------------------------------------+
void CBuffer::SetShift(const int shift)
  {
   this.SetProperty(BUFFER_PROP_SHIFT,shift);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+

Primeiro, definimos o valor do deslocamento para qualquer buffer, em seguida, se o buffer não for de cálculo, definimos o deslocamento da linha desenhada para ele.

No arquivo \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh do objeto-buffer calculado, precisamos definir o ponto inicial (barra) a partir do qual começa a cópia de dados desde o buffer do indicador padrão. Para isso, no método de cópia de dados do indicador especificado para a matriz do objeto-buffer basta simplesmente especificar o valor do início para a cópia de dados com sinal de menos:

//+------------------------------------------------------------------+
//| Copy data of the specified indicator to the buffer object array  |
//+------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,-start_pos,count,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+

Neste caso, tudo é simples, basta transferirmos o valor de deslocamento da linha do indicador ao método como início de barra para cópia de dados. Se o deslocamento for positivo, os dados serão copiados com um deslocamento negativo; se o deslocamento for negativo, os dados serão copiados com um deslocamento positivo. Assim, sempre calharemos ao início correto dos dados necessários da matriz de origem.

Agora no arquivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh da classe-coleção dos objetos-buffers aprimoramos todos os métodos de criação de indicadores padrão nos quais podem ser deslocadas as linhas do indicador.

Vamos ver o exemplo do método para criar um objeto do indicador Alligator padrão:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period Alligator                       |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAlligator(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=WRONG_VALUE)
  {
//--- Calculate and set the number of line shift bars
   int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(PERIOD_CURRENT);
   int shift_jaw=jaw_shift*num_bars;
   int shift_teeth=teeth_shift*num_bars;
   int shift_lips=lips_shift*num_bars;
//--- Create the indicator handle and set the default ID
   int handle=::iAlligator(symbol,timeframe,jaw_period,shift_jaw,teeth_period,shift_teeth,lips_period,shift_lips,ma_method,applied_price);
   int identifier=(id==WRONG_VALUE ? IND_ALLIGATOR : id);
   color array_colors[1]={clrBlue};
   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 Jaws line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_jaw);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_JAWS);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+")");
      buff.SetColors(array_colors);
      
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary Teeth line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_teeth);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_TEETH);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(string)teeth_period+")");
      array_colors[0]=clrRed;
      buff.SetColors(array_colors);
      
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary Lips line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_lips);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_LIPS);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(string)lips_period+")");
      array_colors[0]=clrLime;
      buff.SetColors(array_colors);
      
      //--- Create a calculated Jaws line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary Jaws line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_jaw);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_JAWS);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+")");
      
      //--- Create a calculated Teeth line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary Teeth line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_teeth);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_TEETH);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(string)teeth_period+")");
      
      //--- Create a calculated Lips line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary Lips line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_lips);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_LIPS);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(string)lips_period+")");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Vejamos o que temos aqui.

Em primeiro lugar, calculamos quantas barras do gráfico atual cabem no período do gráfico no qual é calculado o indicador.
Em seguida, calculamos o número de barras de deslocamento multiplicando os valores de deslocamento passados para o método de três linhas indicadoras pelo o número calculado de barras.
Ao criar um identificador de indicador, como parâmetros que indicam o número de barras do deslocamento de cada linha, indicamos os valores calculados acima em vez dos passados para o método.
Por meio dos objetos-buffers criados (tanto desenhados como calculados) definimos os valores de deslocamento calculados no início, para cada par de buffers (desenhado-calculado) definimos valores próprios em conformidade com a linha do indicador (Jaws, Teeth e Lips).
Assim, o objeto do indicador padrão e seus buffers estão completamente preparados para a plotagem de dados com deslocamento da linha indicadora.
Em artigos anteriores ao descrever métodos para criar objetos de outros indicadores padrão, consideramos todas as outras ações que são descritas no método para criar um objeto do indicador Alligator padrão.

As alterações e suplementos descritos acima para o cálculo do deslocamento da linha indicadora já foram escritos em todos os métodos de criação de indicadores padrão que possuem um parâmetro que determina o número de barras para o deslocamento da linha:
CreateAlligator()
, CreateAMA(), CreateBands(), CreateDEMA(), CreateEnvelopes(), CreateFrAMA(), CreateMA(), CreateStdDev(), CreateTEMA() e CreateVIDYA().

Visto que as alterações em cada método são quase idênticas às discutidas acima, não há necessidade de discuti-las aqui.
O código completo da classe da coleção de objetos-buffers pode ser encontrado nos arquivos anexados ao artigo.

Se tivermos criado vários objetos de buffer de indicadores padrão, mas nem todos forem usados ao mesmo tempo, o gráfico exibirá artefatos de buffers de indicadores não usados atualmente. Para evitar isso, primeiro precisamos preencher todas as matrizes-buffers de indicador com seus valores vazios, e só então calcular aqueles que precisam ser calculados no momento. Para fazer isso, criaremos um método que limpa todos os buffers de cada um dos objetos-buffers existentes dos indicadores padrão com base no índice da série de tempo especificado.

Vamos declarar um método na seção pública da classe:

//--- Clear buffer data (1) of the specified standard indicator and (2) all created standard indicators by the timeseries index
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
   void                    ClearDataAllBuffersStdInd(int series_index);
//--- Set the values for the current chart to the buffers of the specified standard indicator by the timeseries index according to the buffer object period/symbol

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

//+------------------------------------------------------------------+
//| Clear the calculated buffer data of all created                  |
//| standard indicators by the timeseries index                      |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataAllBuffersStdInd(int series_index)
  {
   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;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_CALCULATE || buff.IndicatorType()==WRONG_VALUE)
         continue;
      this.ClearDataBufferStdInd(buff.IndicatorType(),buff.ID(),series_index);
     }
  }
//+------------------------------------------------------------------+

Aqui:
Obtemos uma lista de todos os objetos-buffers com o ID do indicador.
Num loop percorrendo toda a lista recebida
obtemos o próximo objeto-buffer.
Se o objeto não foi recebido por algum motivo, se for um buffer calculado ou se o tipo de indicador padrão não for especificado
(uma vez que qualquer objeto-buffer pode ter um identificador, não apenas um indicador padrão), ignoraremos tal objeto.
Finalmente, chamamos o método para limpar o buffer do indicador padrão especificado, que consideramos no último artigo.

Melhoraremos os métodos de preparação e limpeza de matrizes de buffers calculados, que armazenam dados de buffers de indicadores padrão. Anteriormente, nesses métodos, eram criados blocos de código que agrupam ações semelhantes para indicadores cujos nomes/finalidades de linhas eram as mesmas. Agora, quando atribuímos os mesmos valores numéricos a todas as linhas do mesmo tipo (falamos sobre isso no início do artigo) na enumeração dos tipos de linha dos indicadores padrão, esses métodos podem ser simplificados reduzindo tudo a três blocos de códigos - para uma, duas e três linhas de indicadores padrão.

Método que prepara os dados para o buffer calculado do indicador padrão especificado:

//+------------------------------------------------------------------+
//| 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_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list_ind==NULL || list_ind.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copied=WRONG_VALUE;
   int idx0=0,idx1=1,idx2=2;
   switch((int)std_ind)
     {
   //--- Single-buffer standard indicators
      case IND_AC          :
      case IND_AD          :
      case IND_AMA         :
      case IND_AO          :
      case IND_ATR         :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_BWMFI       :
      case IND_CCI         :
      case IND_CHAIKIN     :
      case IND_DEMA        :
      case IND_DEMARKER    :
      case IND_FORCE       :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_MFI         :
      case IND_MOMENTUM    :
      case IND_OBV         :
      case IND_OSMA        :
      case IND_RSI         :
      case IND_SAR         :
      case IND_STDDEV      :
      case IND_TEMA        :
      case IND_TRIX        :
      case IND_VIDYA       :
      case IND_VOLUMES     :
      case IND_WPR         :
      
        buffer=list_ind.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),0,buffer.Shift(),total_copy);
        return copied;
      
   //--- Multi-buffer standard indicators
      case IND_ENVELOPES   :
      case IND_FRACTALS    :
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_ALLIGATOR   :
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
        if(std_ind==IND_BANDS)
          {
           idx0=1;
           idx1=2;
           idx2=0;
          }
        else
          {
           idx0=0;
           idx1=1;
           idx2=2;
          }
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_GATOR       :
      case IND_ICHIMOKU    :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

No bloco de código para três buffers do indicador, adicionamos uma verificação de se a linha do indicador Bollinger Bands está processada, uma vez que a indexação das matrizes dos seus buffers é diferente da indexação dos buffers de todos os outros indicadores padrão de três buffers. Ainda não há processamento dos indicadores Gator Oscillator e Ichimoku Kinko Hyo, os motivos para isso foram descritos no início do artigo.

Método que limpa os dados do buffer do indicador padrão especificado pelo índice da série temporal:

//+------------------------------------------------------------------+
//| 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_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   if(list_ind==NULL || list_ind.Total()==0)
      return;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list_ind.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
   //--- Single-buffer standard indicators
      case IND_AC          :
      case IND_AD          :
      case IND_AMA         :
      case IND_AO          :
      case IND_ATR         :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_BWMFI       :
      case IND_CCI         :
      case IND_CHAIKIN     :
      case IND_DEMA        :
      case IND_DEMARKER    :
      case IND_FORCE       :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_MFI         :
      case IND_MOMENTUM    :
      case IND_OBV         :
      case IND_OSMA        :
      case IND_RSI         :
      case IND_SAR         :
      case IND_STDDEV      :
      case IND_TEMA        :
      case IND_TRIX        :
      case IND_VIDYA       :
      case IND_VOLUMES     :
      case IND_WPR         :
        buffer=list_ind.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
   //--- Multi-buffer standard indicators
      case IND_ENVELOPES   :
      case IND_FRACTALS    :
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
      case IND_GATOR       :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;

      case IND_ALLIGATOR   :
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Aqui, todos os indicadores padrão são distribuídos da mesma maneira com base nos blocos de código correspondentes: indicadores padrão de um, dois e três buffers. Aqui, como já não é importante em que sequência limpar os dados do buffer, Bollinger Bands e Gator Oscillator já estão incluídos nos blocos de código para processar indicadores de dois e três buffers. O Ichimoku ainda não foi processado de forma alguma.

Uma vez que combinamos o processamento de indicadores padrão de um, dois e três buffers, agora podemos reduzir muito o código do método que define os valores do gráfico atual para os buffers do indicador padrão especificado com base no índice da série temporal de acordo com o símbolo/período do objeto-buffer. Aqui está o código completo do método antes de ser refeito:

//+------------------------------------------------------------------+
//| Set values for the current chart to the buffers of the specified |
//| 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_data0=NULL;
   CBuffer *buffer_data1=NULL;
   CBuffer *buffer_data2=NULL;
   CBuffer *buffer_calc0=NULL;
   CBuffer *buffer_calc1=NULL;
   CBuffer *buffer_calc2=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   uchar clr=color_index;
   long vol0=0,vol1=0;
   datetime time_period=0,time_shift=0;
   double value00=EMPTY_VALUE, value01=EMPTY_VALUE;
   double value10=EMPTY_VALUE, value11=EMPTY_VALUE;
   double value20=EMPTY_VALUE, value21=EMPTY_VALUE;

//--- Depending on the standard indicator type
   switch((int)ind_type)
     {
   //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        //--- Get drawn and calculated buffer objects
        buffer_data0=list_data.At(0);
        buffer_calc0=list_calc.At(0);
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time on the indicator buffer chart period
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Calculate the bar shift relative to the indicator line shift direction
           //index_period+=buffer_data0.Shift();
           //--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
           time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period); // -buffer_calc0.Shift()+index_shift
           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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars+(num_bars*buffer_calc0.Shift())));
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           if(ind_type!=IND_BWMFI)
              clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           else
             {
              vol0=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
              vol1=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period+1);
              clr=
                (
                 value00>value01 && vol0>vol1 ? 0 :
                 value00<value01 && vol0<vol1 ? 1 :
                 value00>value01 && vol0<vol1 ? 2 :
                 value00<value01 && vol0>vol1 ? 3 : 4
                );
             }
           buffer_data0.SetBufferColorIndex(index,clr);
          }
        return true;
      
   //--- Multi-buffer standard indicators
      case IND_ADX   :
      case IND_ADXW  :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data2.SetBufferValue(2,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_BANDS    :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(0,index,value10);
           buffer_data2.SetBufferValue(0,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ALLIGATOR:
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(0,index,value10);
           buffer_data2.SetBufferValue(0,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_GATOR    :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10<value11 ? 0 : value10>value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Os blocos de código idênticos são marcados com cor na listagem de métodos. É imediatamente claro que podemos levar todo esse mesmo tipo de processamento num método, reduzindo o código do método.

Na seção pública da classe, declaramos um método privado que prepara os dados do indicador padrão especificado para definir valores no gráfico de símbolo atual (para este método vamos transferir todo o mesmo processamento do método acima do último artigo):

//--- Clear buffer data (1) of the specified standard indicator and (2) all created standard indicators by the timeseries index
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
   void                    ClearDataAllBuffersStdInd(int series_index);
//--- Set the values for the current chart to the buffers of the specified standard indicator by the timeseries index according to the buffer object period/symbol
   bool                    SetDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE);
private:
//--- Prepare data of the specified standard indicator for setting values on the current symbol chart
   int                     PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                                                  CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                                                  const ENUM_INDICATOR ind_type,
                                                  const int series_index,
                                                  const datetime series_time,
                                                  int &index_period,
                                                  int &num_bars,
                                                  double &value00,
                                                  double &value01,
                                                  double &value10,
                                                  double &value11,
                                                  double &value20,
                                                  double &value21);
public:

//--- Return the buffer (1) by the graphical series name, (2) by timeframe,
//--- (3) by Plot index, (4) by object index in the collection list, (5) the last created one,
//--- list of buffers (6) by ID, (7) by standard indicator type, (8) by type and ID

Para o método vamos transferir ponteiros para objetos de buffer e dados da série temporal, bem como variáveis por referência, os valores que precisarão ser retornados desde o método para seu processamento posterior no método que estamos encurtando.

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

//+------------------------------------------------------------------+
//| Prepare data of the specified standard indicator                 |
//| for setting values on the current symbol chart                   |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                                               CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                                               const ENUM_INDICATOR ind_type,
                                               const int series_index,
                                               const datetime series_time,
                                               int &index_period,
                                               int &num_bars,
                                               double &value00,
                                               double &value01,
                                               double &value10,
                                               double &value11,
                                               double &value20,
                                               double &value21)
  {
     //--- Find the bar index corresponding to the current bar start time
     index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
     if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
        return WRONG_VALUE;
     
     //--- Get the value by the index from the indicator buffer
     if(buffer_calc0!=NULL)
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
     if(buffer_calc1!=NULL)
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
     if(buffer_calc2!=NULL)
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
     
     int series_index_start=series_index;
     //--- The current chart requires no calculation of the number of handled bars since there is only one bar
     if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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
        datetime time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.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 WRONG_VALUE;
        //--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
        num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
        if(num_bars==0) num_bars=1;
       }
     //--- Set values to calculate colors
     if(buffer_calc0!=NULL)
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
     if(buffer_calc1!=NULL)
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
     if(buffer_calc2!=NULL)
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.GetDataBufferValue(0,series_index_start+num_bars));
   
   return series_index_start;
  }
//+------------------------------------------------------------------+

Em geral, tudo é fácil, movemos um bloco de repetição de código desde um método para um outro novo. Como o método abreviado requer o uso de muitos dados que são calculados dentro deste método privado, podemos simplesmente passar variáveis para o novo método por referência, e então todas as alterações nos valores dessas variáveis estarão disponíveis no método de chamada. Além de definir valores para variáveis por referência, este método retorna o índice de barras a partir do qual é necessário preencher no loop os dados do buffer no gráfico atual no método de chamada. Se o processamento de dados falhar, o método retornará -1.

Agora vamos ver quanto foi reduzido o método que define os valores do gráfico atual em buffers do indicador padrão especificado com base no índice da série temporal de acordo com o símbolo/período do objeto-buffer:

//+------------------------------------------------------------------+
//| Set values for the current chart to the buffers of the specified |
//| 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_data0=NULL;
   CBuffer *buffer_data1=NULL;
   CBuffer *buffer_data2=NULL;
   CBuffer *buffer_calc0=NULL;
   CBuffer *buffer_calc1=NULL;
   CBuffer *buffer_calc2=NULL;
   double value00=EMPTY_VALUE, value01=EMPTY_VALUE;
   double value10=EMPTY_VALUE, value11=EMPTY_VALUE;
   double value20=EMPTY_VALUE, value21=EMPTY_VALUE;
   long vol0=0,vol1=0;
   int series_index_start=series_index,index_period=0, index=0,num_bars=1;
   uchar clr=0;
//--- Depending on the standard indicator type

   switch((int)ind_type)
     {
   //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                                                  ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           if(ind_type!=IND_BWMFI)
              clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           else
             {
              vol0=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
              vol1=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period+1);
              clr=
                (
                 value00>value01 && vol0>vol1 ? 0 :
                 value00<value01 && vol0<vol1 ? 1 :
                 value00>value01 && vol0<vol1 ? 2 :
                 value00<value01 && vol0>vol1 ? 3 : 4
                );
             }
           buffer_data0.SetBufferColorIndex(index,clr);
          }
        return true;
      
   //--- Multi-buffer standard indicators
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_data1=list.At(0);
           
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_calc1=list.At(0);
           
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                                                  ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
      case IND_ALLIGATOR   :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                                                  ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data2.SetBufferValue(2,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_GATOR    :
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Todos os cálculos repetidos anteriormente de bloco para bloco foram substituídos chamando um método e verificando o resultado retornado dele. Também não processamos os indicadores Gator Oscillator e Ichimoku Kinko Hyo aqui pelas razões mencionadas acima.

Teste

Vamos pegar um indicador de teste do último artigo
e o salvamo na nova pasta \MQL5\Indicators\TestDoEasy\Part50\ com o novo nome TestDoEasyPart50.mq5.

Para parâmetros externos do indicador adicionamos o deslocamento da linha indicadora:

//--- 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   int                  InpShift          =  0;             // Indicator line shift
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers

Precisamos verificar como funciona o deslocamento das linhas dos indicadores. Os indicadores padrão, para os quais é possível exibir uma linha com deslocamento, todos exibem seus dados na janela do gráfico principal (exceto para o indicador Gator Oscillator, que é exibido numa subjanela, mas ainda não o implementamos). Por isso, no manipulador OnInit(), vamos escrever um bloco de código para criar objetos de indicadores padrão que funcionam na janela principal:

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing a selected standard indicator
   bool success=false;
   switch(InpIndType)
     {
//--- Single-buffer standard indicators in the main window
      case IND_AMA         :  success=engine.BufferCreateAMA(InpUsedSymbols,InpPeriod,9,2,30,InpShift,PRICE_CLOSE,1);                  break;
      case IND_DEMA        :  success=engine.BufferCreateDEMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                     break;
      case IND_FRAMA       :  success=engine.BufferCreateFrAMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                    break;
      case IND_MA          :  success=engine.BufferCreateMA(InpUsedSymbols,InpPeriod,10,InpShift,MODE_SMA,PRICE_CLOSE,1);              break;
      case IND_SAR         :  success=engine.BufferCreateSAR(InpUsedSymbols,InpPeriod,0.02,0.2,1);                                     break;
      case IND_TEMA        :  success=engine.BufferCreateTEMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                     break;
      case IND_VIDYA       :  success=engine.BufferCreateVIDYA(InpUsedSymbols,InpPeriod,9,12,InpShift,PRICE_CLOSE,1);                  break;
      
//--- Multi-buffer standard indicators in a subwindow
      case IND_ALLIGATOR   :  success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN,1);    break;
      case IND_BANDS       :  success=engine.BufferCreateBands(InpUsedSymbols,InpPeriod,20,InpShift,2.0,PRICE_CLOSE,1);                break;
      case IND_ENVELOPES   :  success=engine.BufferCreateEnvelopes(InpUsedSymbols,InpPeriod,14,InpShift,MODE_SMA,PRICE_CLOSE,0.1,1);   break;
      case IND_FRACTALS    :  success=engine.BufferCreateFractals(InpUsedSymbols,InpPeriod,1);                                         break;
      
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }

O parâmetro externo que define o deslocamento da linha é passado para os métodos de criação de objetos de indicadores padrão. Para o indicador Alligator os deslocamentos são especificados para cada uma das suas três linhas, e estes valores já estão "fixos" na lógica do indicador definida pelo seu autor.

Nos indicadores de teste anteriores, tínhamos um bloco de código que definia e define os níveis dos indicadores na subjanela e a profundidade de bits da exibição dos dados do indicador na janela de dados do terminal:

//--- Set levels where they are required and define the data decimal capacity
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    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_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        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_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Set the short name for the indicator and bit depth
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);

Vamos nos livrar desse bloco de código no indicador final. Para fazer isso, no arquivo de funções de serviço da biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh vamos escrever uma nova função para a qual transferiremos este bloco de código:

//+------------------------------------------------------------------+
//| Set standard indicator's decimal capacity and levels             |
//+------------------------------------------------------------------+
void SetIndicatorLevels(const string symbol,const ENUM_INDICATOR ind_type)
  {
   int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   switch(ind_type)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    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_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        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_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
  }
//+------------------------------------------------------------------+

Agora, na parte final do manipulador OnInit() do indicador, basta chamarmos esta função, o que torna o código mais simples e claro:

//--- Set the indicator short name, digital capacity and levels
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   SetIndicatorLevels(InpUsedSymbols,InpIndType);

//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Deixamos o manipulador OnCalculate() inalterado. O código completo do indicador de teste pode ser encontrado nos arquivos anexados ao artigo.

Vamos compilar o indicador e executá-lo no gráfico EURUSD H1, tendo previamente definido o uso do símbolo EURUSD H4 nas configurações, definimos o deslocamento da linha do indicador para 4 barras e selecionamos o indicador Bollinger Bands. Em seguida, selecionamos o indicador Alligator nas configurações:


Como podemos ver, o Bollinger Bands é exibido corretamente com o deslocamento especificado de 4 barras, e o Alligator não responde a um deslocamento de 4 barras, pois seus valores padrão são definidos imediatamente após a criação no código de OnInit() iguais a esses valores para o indicador padrão:

//--- Multi-buffer standard indicators in a subwindow
      case IND_ALLIGATOR   :  success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN,1);    break;

E o Alligator também exibe suas linhas corretamente com o deslocamento padrão de suas linhas.


O que vem agora?

No próximo artigo, continuaremos a desenvolver os métodos da biblioteca para trabalhar com indicadores padrão no modo multissímbolo e multiperíodo.

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
Trabalhando com séries temporais na biblioteca DoEasy (Parte 48): indicadores multissímbolos multiperíodos num buffer de uma subjanela
Trabalhando com séries temporais na biblioteca DoEasy (Parte 49): indicadores padrão multiperíodos multissímbolos multibuffer