Trabalhando com séries temporais na biblioteca DoEasy (Parte 45): buffers de indicador multiperíodo

20 outubro 2020, 14:57
Artyom Trishkin
0
546

Sumário


Ideia

No artigo anterior começamos a criar uma classe-coleção de buffers de indicador. Hoje vamos continuar com o desenvolvimento desta classe e pensaremos numa metodologia para criar buffers de indicador e acessar dados. Faremos a exibição de valores do buffer no gráfico com base no período/símbolo atual dependendo da propriedade do seu timeframe de dados definido no objeto-buffer. Se para o buffer for definido um valor de timeframe que não corresponde ao do gráfico atual, todos os dados deste buffer serão exibidos no gráfico corretamente. Por exemplo, se o gráfico atual tiver o período de dados M15 e o período do gráfico do objeto-buffer for definido como H1, os dados deste buffer serão exibidos em cada barra do gráfico M15 atual, cujo tempo está dentro da barra do gráfico H1. Neste exemplo, quatro barras do gráfico atual serão preenchidas com o mesmo valor correspondente ao valor solicitado da barra com o período gráfico H1.

Estas modificações nos permitirão criar confortavelmente indicadores multiperíodos — para fazer isso, só precisaremos definir para o objeto-buffer seu valor de timeframe, e a biblioteca assumirá o resto. Agora, pouco a pouco iremos fazer mais simples o processo de criação de buffers de indicador, deixando apenas uma linha de código na qual será criado um objeto-buffer de indicador com as propriedades desejadas.
O acesso a qualquer buffer de indicador será feito por meio do número do buffer com um determinado tipo de plotagem.

A ideia por trás de um "número de buffer" será a seguinte: se o buffer de setas for criado primeiro, em seguida, o de linhas e, depois, novamente o de setas, os números dos objetos-buffers irão na sequência da sua criação — cada estilo de plotagem terá sua própria sequência de números. No exemplo acima, os números serão: o primeiro buffer de seta criado será o número 0, o segundo buffer de seta será o número 1 e o buffer de linha criado imediatamente após o primeiro buffer de seta será o número 0, porque este é o primeiro dos buffers de estilo "linha", embora tenha sido criado como o segundo de acordo com a contagem.

Vamos ilustrar este conceito de números de objetos-buffers:

  1. Criação do buffer "Setas". Seu número = 0,
  2. Criação do buffer "Linha". Seu número = 0,
  3. Criação do buffer "Setas". Seu número = 1,
  4. Criação do buffer "ZigZag". Seu número = 0,
  5. Criação do buffer "ZigZag". Seu número = 1,
  6. Criação do buffer "Setas". Seu número = 2,
  7. Criação do buffer "Setas". Seu número = 3,
  8. Criação do buffer "Linha". Seu número = 1,
  9. Criação do buffer "Vela". Seu número = 0,
  10. Criação do buffer "Setas". Seu número = 4
Além de acessar o buffer pelo seu número ordinal, será possível fazê-lo pelo nome da série gráfica, período gráfico, índice na janela de dados ou índice na lista de coleções. Também poderemos obter o objeto-buffer que foi criado e adicionado à lista de coleção como último, tornando mais fácil definir parâmetros para o objeto-buffer recém-criado — criamos um objeto, o obtemos e definimos suas propriedades adicionais.

Modificando classes para trabalhar com buffers de indicador em modo multiperíodo

A partir do build 2430, o MetaTrader 5 aumentou o número de símbolos que podem estar na janela "Observação do mercado". Agora, existem 5 000 em vez de mil antes do build 2430. Para trabalhar com uma lista de símbolos vamos introduzir uma nova substituição de macro no arquivo Defines.mqh:

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//--- Describe the function with the error line number
....
//--- Symbol parameters
#define CLR_DEFAULT                    (0xFF000000)               // Default symbol background color in the navigator
#ifdef __MQL5__
   #define SYMBOLS_COMMON_TOTAL        (TerminalInfoInteger(TERMINAL_BUILD)<2430 ? 1000 : 5000)   // Total number of MQL5 working symbols
#else 
   #define SYMBOLS_COMMON_TOTAL        (1000)                     // Total number of MQL4 working symbols
#endif 
//--- Pending request type IDs
#define PENDING_REQUEST_ID_TYPE_ERR    (1)                        // Type of a pending request created based on the server return code
#define PENDING_REQUEST_ID_TYPE_REQ    (2)                        // Type of a pending request created by request
//--- Timeseries parameters
#define SERIES_DEFAULT_BARS_COUNT      (1000)                     // Required default amount of timeseries data
#define PAUSE_FOR_SYNC_ATTEMPTS        (16)                       // Amount of pause milliseconds between synchronization attempts
#define ATTEMPTS_FOR_SYNC              (5)                        // Number of attempts to receive synchronization with the server
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+

No arquivo \MQL5\Include\DoEasy\Collections\SymbolsCollection.mqh substituímos o tamanho reservado da matriz de símbolos de 1000 pelo valor da nova macro de substituição no método de definição de símbolos utilizados:

//+------------------------------------------------------------------+
//| Set the list of used symbols                                     |
//+------------------------------------------------------------------+
bool CSymbolsCollection::SetUsedSymbols(const string &symbol_used_array[])
  {
   ::ArrayResize(this.m_array_symbols,0,SYMBOLS_COMMON_TOTAL);
   ::ArrayCopy(this.m_array_symbols,symbol_used_array);

Já no método de criação da lista de símbolos substituímos, na condição do ciclo, o valor de controle de 1 000 pela nova substituição de macro:

//+------------------------------------------------------------------+
//| Creating the symbol list (Market Watch or the complete list)     |
//+------------------------------------------------------------------+
bool CSymbolsCollection::CreateSymbolsList(const bool flag)
  {
   bool res=true;
   int total=::SymbolsTotal(flag);
   for(int i=0;i<total && i<SYMBOLS_COMMON_TOTAL;i++)
     {

Assim no método de registro de todos os símbolos e períodos gráficos no arquivo \MQL5\Include\DoEasy\Engine.mqh substituímos 1 000 pela substituição de macro:

//+------------------------------------------------------------------+
//| Write all used symbols and timeframes                            |
//| to the ArrayUsedSymbols and ArrayUsedTimeframes arrays           |
//+------------------------------------------------------------------+
void CEngine::WriteSymbolsPeriodsToArrays(void)
  {
//--- Get the list of all created timeseries (created by the number of used symbols)
   CArrayObj *list_timeseries=this.GetListTimeSeries();
   if(list_timeseries==NULL)
      return;
//--- Get the total number of created timeseries
   int total_timeseries=list_timeseries.Total();
   if(total_timeseries==0)
      return;
//--- Set the size of the array of used symbols equal to the number of created timeseries, while
//--- the size of the array of used timeframes is set equal to the maximum possible number of timeframes in the terminal
   if(::ArrayResize(ArrayUsedSymbols,total_timeseries,SYMBOLS_COMMON_TOTAL)!=total_timeseries || ::ArrayResize(ArrayUsedTimeframes,21,21)!=21)
      return;
//--- Set both arrays to zero
   ::ZeroMemory(ArrayUsedSymbols);
   ::ZeroMemory(ArrayUsedTimeframes);
//--- Reset the number of added symbols and timeframes to zero and,
//--- in a loop by the total number of timeseries,
   int num_symbols=0,num_periods=0;
   for(int i=0;i<total_timeseries;i++)
     {
      //--- get the next object of all timeseries of a single symbol
      CTimeSeriesDE *timeseries=list_timeseries.At(i);
      if(timeseries==NULL || this.IsExistSymbol(timeseries.Symbol()))
         continue;
      //--- increase the number of used symbols and (num_symbols variable), and
      //--- write the timeseries symbol name to the array of used symbols by the num_symbols-1 index
      num_symbols++;
      ArrayUsedSymbols[num_symbols-1]=timeseries.Symbol();
      //--- Get the list of all its timeseries from the object of all symbol timeseries
      CArrayObj *list_series=timeseries.GetListSeries();
      if(list_series==NULL)
         continue;
      //--- In the loop by the total number of symbol timeseries,
      int total_series=list_series.Total();
      for(int j=0;j<total_series;j++)
        {
         //--- get the next timeseries object
         CSeriesDE *series=list_series.At(j);
         if(series==NULL || this.IsExistTimeframe(series.Timeframe()))
            continue;
         //--- increase the number of used timeframes and (num_periods variable), and
         //--- write the timeseries timeframe value to the array of used timeframes by num_periods-1 index
         num_periods++;
         ArrayUsedTimeframes[num_periods-1]=series.Timeframe();
        }
     }
//--- Upon the loop completion, change the size of both arrays to match the exact number of added symbols and timeframes
   ::ArrayResize(ArrayUsedSymbols,num_symbols,SYMBOLS_COMMON_TOTAL);
   ::ArrayResize(ArrayUsedTimeframes,num_periods,21);
  }
//+------------------------------------------------------------------+


Nas propriedades do objeto-buffer temos o parâmetro que indica o índice da matriz que será atribuído pelo seguinte buffer de indicador. Ao criar um buffer e adicionar o número necessário de matrizes a ele, é conveniente logo escrever nele o índice da próxima matriz, que será a primeira matriz (base) para o próximo buffer. Já para o índice do buffer de indicador (construção gráfica), este valor é usado ao definir valores por meio das funções PlotIndexSetDouble(), PlotIndexSetInteger() e PlotIndexSetString() para o buffer, até porque não temos uma propriedade nos parâmetros do objeto-buffer. Para o cálculo conveniente do índice da próxima construção gráfica, adicionamos uma nova propriedade à enumeração de propriedades inteiras do objeto-buffer no arquivo Defines.mqh. E também renomeamos a propriedade que armazena o índice da matriz base do seguinte objeto-buffer:

//+------------------------------------------------------------------+
//| Buffer integer properties                                        |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_INTEGER
  {
   BUFFER_PROP_INDEX_PLOT = 0,                              // Plotted buffer serial number
   BUFFER_PROP_STATUS,                                      // Buffer status (by drawing style) from the ENUM_BUFFER_STATUS enumeration
   BUFFER_PROP_TYPE,                                        // Buffer type (from the ENUM_BUFFER_TYPE enumeration)
   BUFFER_PROP_TIMEFRAME,                                   // Buffer period data (timeframe)
   BUFFER_PROP_ACTIVE,                                      // Buffer usage flag
   BUFFER_PROP_DRAW_TYPE,                                   // Graphical construction type (from the ENUM_DRAW_TYPE enumeration)
   BUFFER_PROP_ARROW_CODE,                                  // Arrow code for DRAW_ARROW style
   BUFFER_PROP_ARROW_SHIFT,                                 // The vertical shift of the arrows for DRAW_ARROW style
   BUFFER_PROP_LINE_STYLE,                                  // Line style
   BUFFER_PROP_LINE_WIDTH,                                  // Line width
   BUFFER_PROP_DRAW_BEGIN,                                  // The number of initial bars that are not drawn and values in DataWindow
   BUFFER_PROP_SHOW_DATA,                                   // Flag of displaying construction values in DataWindow
   BUFFER_PROP_SHIFT,                                       // Indicator graphical construction shift by time axis in bars
   BUFFER_PROP_COLOR_INDEXES,                               // Number of colors
   BUFFER_PROP_COLOR,                                       // Drawing color
   BUFFER_PROP_INDEX_BASE,                                  // Base data buffer index
   BUFFER_PROP_INDEX_NEXT_BASE,                             // Index of the array to be assigned as the next indicator buffer
   BUFFER_PROP_INDEX_NEXT_PLOT,                             // Index of the next drawn buffer
   BUFFER_PROP_NUM_DATAS,                                   // Number of data buffers
   BUFFER_PROP_INDEX_COLOR,                                 // Color buffer index
  }; 
#define BUFFER_PROP_INTEGER_TOTAL (20)                      // Total number of integer bar properties
#define BUFFER_PROP_INTEGER_SKIP  (2)                       // Number of buffer properties not used in sorting
//+------------------------------------------------------------------+

Respectivamente, aumentamos o número de propriedades inteiras de 19 para 20.

Na enumeração de possíveis critérios de classificação de buffers inserimos a classificação por propriedade recém-adicionada e alteramos o nome da constante de classificação por índice da matriz, para a seguinte matriz base atribuir o buffer:

//+------------------------------------------------------------------+
//| Possible buffer sorting criteria                                 |
//+------------------------------------------------------------------+
#define FIRST_BUFFER_DBL_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP)
#define FIRST_BUFFER_STR_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP+BUFFER_PROP_DOUBLE_TOTAL-BUFFER_PROP_DOUBLE_SKIP)
enum ENUM_SORT_BUFFER_MODE
  {
//--- Sort by integer properties
   SORT_BY_BUFFER_INDEX_PLOT = 0,                           // Sort by the plotted buffer serial number
   SORT_BY_BUFFER_STATUS,                                   // Sort by buffer drawing style (status) from the ENUM_BUFFER_STATUS enumeration
   SORT_BY_BUFFER_TYPE,                                     // Sort by buffer type (from the ENUM_BUFFER_TYPE enumeration)
   SORT_BY_BUFFER_TIMEFRAME,                                // Sort by the buffer data period (timeframe)
   SORT_BY_BUFFER_ACTIVE,                                   // Sort by the buffer usage flag
   SORT_BY_BUFFER_DRAW_TYPE,                                // Sort by graphical construction type (from the ENUM_DRAW_TYPE enumeration)
   SORT_BY_BUFFER_ARROW_CODE,                               // Sort by the arrow code for DRAW_ARROW style
   SORT_BY_BUFFER_ARROW_SHIFT,                              // Sort by the vertical shift of the arrows for DRAW_ARROW style
   SORT_BY_BUFFER_LINE_STYLE,                               // Sort by the line style
   SORT_BY_BUFFER_LINE_WIDTH,                               // Sort by the line width
   SORT_BY_BUFFER_DRAW_BEGIN,                               // Sort by the number of initial bars that are not drawn and values in DataWindow
   SORT_BY_BUFFER_SHOW_DATA,                                // Sort by the flag of displaying construction values in DataWindow
   SORT_BY_BUFFER_SHIFT,                                    // Sort by the indicator graphical construction shift by time axis in bars
   SORT_BY_BUFFER_COLOR_INDEXES,                            // Sort by a number of attempts
   SORT_BY_BUFFER_COLOR,                                    // Sort by the drawing color
   SORT_BY_BUFFER_INDEX_BASE,                               // Sort by the basic data buffer index
   SORT_BY_BUFFER_INDEX_NEXT_BASE,                          // Sort by the index of the array to be assigned as the next indicator buffer
   SORT_BY_BUFFER_INDEX_NEXT_PLOT,                          // Sort by the index of the next drawn buffer
//--- Sort by real properties
   SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP,      // Sort by the empty value for plotting where nothing will be drawn
//--- Sort by string properties
   SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP,           // Sort by the buffer symbol
   SORT_BY_BUFFER_LABEL,                                    // Sort by the name of the graphical indicator series displayed in DataWindow
  };
//+------------------------------------------------------------------+

Agora, ao criar qualquer objeto-buffer, iremos escrever os índices da matriz base e o índice da próxima série gráfica nos parâmetros do buffer atual. Assim, para saber que índices inserir no objeto-buffer recém-criado, bastará aceder ao anterior, que dizer, ao último objeto-buffer criado e obter os valores necessários.
Essa é a melhor e mais simples maneira de fazê-lo, afinal a cada buffer podem ser atribuídas de uma a cinco matrizes, e não precisamos recalcular todos os valores dos índices necessários das matrizes para o novo buffer de indicador, até porque tudo já está especificado diretamente nas propriedades do último objeto-buffer criado, e esses valores são recalculados a cada buffer de indicador recém-adicionado dependendo do número de matrizes que ele usar.

Agora no arquivo \MQL5\Include\DoEasy\Datas.mqh inserimos o índice da nova mensagem para a propriedade do buffer recém-adicionada e alteramos o nome da constante do índice da mensagem sobre a propriedade da seguinte matriz para o buffer de indicador:

//--- CBuffer
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE,               // Base data buffer index
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT,               // Plotted buffer serial number
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR,              // Color buffer index
   MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS,                // Number of data buffers
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE,          // Index of the array to be assigned as the next indicator buffer
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT,          // Index of the next drawn buffer
   MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME,                // Buffer (timeframe) data period

E adicionamos o texto da mensagem correspondendo ao índice recém-adicionado:

   {"Индекс базового буфера данных","Index of Base data buffer"},
   {"Порядковый номер рисуемого буфера","Plot buffer sequence number"},
   {"Индекс буфера цвета","Color buffer index"},
   {"Количество буферов данных","Number of data buffers"},
   {"Индекс массива для назначения следующим индикаторным буфером","Array index for assignment as the next indicator buffer"},
   {"Индекс следующего по счёту рисуемого буфера","Index of the next drawable buffer"},
   {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"},

Visto que alteramos o nome da constante que armazena o índice da seguinte matriz base do objeto-buffer, agora em todos os arquivo das classes dos objetos-buffers (BufferArrow.mqh, BufferBars.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferLine.mqh, BufferArrow.mqh, BufferSection.mqh, BufferZigZag.mqh) substituiremos a ocorrência da linha BUFFER_PROP_INDEX_NEXT por BUFFER_PROP_INDEX_NEXT_BASE, enquanto modificamos o método virtual que imprime no log uma descrição curta do objeto-buffer.
Vamos considerar as mudanças neste método usando o exemplo da classe CBufferArrow no arquivo \MQL5\Include\DoEasy\Objects\Indicators\BufferArrow.mqh:

//+------------------------------------------------------------------+
//| Display short buffer description in the journal                  |
//+------------------------------------------------------------------+
void CBufferArrow::PrintShort(void)
  {
   ::Print
     (
      CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_BUFFER),"(P",(string)this.IndexPlot(),"/B",(string)this.IndexBase(),"/C",(string)this.IndexColor(),"): ",
      this.GetStatusDescription(true)," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())
     );
  }
//+------------------------------------------------------------------+

O texto da mensagem é escrito de forma a ser mais informativo. Por exemplo, a descrição breve de uma classe de buffer de seta será semelhante a:

Buffer(P0/B0/C1): Drawing with arrows EURUSD H1

Aqui:

  • "P" significa "Plot" — número da construção gráfica,
  • "B" significa "Base" — índice da matriz base atribuído primeiro a este buffer (as matrizes restantes deste buffer estão em ordem crescente de índice, começando a partir do índice da matriz base), 
  • "C" significa "Color" — índice da matriz de cores atribuída a este buffer.

Assim, se criarmos todos os objetos de buffer possíveis e imprimirmos suas descrições, veremos as seguintes entradas no log:

Buffer(P0/B0/C1): Drawing with arrows EURUSD H1
Buffer(P1/B2/C3): EURUSD H1 line
Buffer(P2/B4/C5): EURUSD H1 sections
Buffer(P3/B6/C7): Histogram from the zero line EURUSD H1
Buffer(P4/B8/C10): Histogram on two indicator buffers EURUSD H1
Buffer(P5/B11/C13): EURUSD H1 zigzag
Buffer(P6/B14/C16): Color filling between two levels EURUSD H1
Buffer(P7/B16/C20): Display as EURUSD H1 bars
Buffer(P8/B21/C25): Display as EURUSD H1 candles
Buffer[P8/B26/C27]: Calculated buffer

Isto é muito mais claro, até porque é possível ver a quais buffers de indicador são atribuídas as matrizes, que índices são usados (B e C) e quais índices de séries gráficas este buffer atribui (P).
Aqui podemos observar que o índice da série gráfica atribuído ao buffer calculado é exatamente igual ao do buffer anterior. Na verdade, o buffer calculado não tem série gráfica e matriz-buffer de cores, e no momento aqui são exibidas informações de depuração — em P8 e C27 são mostrados os índices de série gráfica e de matriz-buffer de cores que deve ter o seguinte buffer criado após o calculado.
Após concluir o trabalho sobre a criação de buffers de indicador, alteramos estas informações, por exemplo, para PN, CN ou PX, CX, o que indicará a ausência de uma série gráfica e de uma matriz-buffer de cor no buffer calculado; também podemos remover essas designações, deixando apenas B, o índice do atribuído ao buffer da matriz.

Temos os objetos-herdeiros do buffer-objeto abstrato base que especificam o tipo de buffer de indicador criado com base no seu tipo de plotagem. Falta-nos um objeto-buffer calculado que servirá para realizar alguns cálculos - no indicador - que requerem de uma matriz, mas sem plotagem no gráfico. Essa matriz também é um buffer de indicador cuja distribuição é realizada pelo subsistema do terminal como com as matrizes-buffers de indicador.
Criamos um objeto-herdeiro com nome de classe CBufferCalculate. Na pasta \MQL5\Include\DoEasy\Objects\Indicators\ criamos o novo arquivo BufferCalculate.mqh e, logo, o preenchemos com todo o conteúdo necessário:

//+------------------------------------------------------------------+
//|                                              BufferCalculate.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Buffer.mqh"
//+------------------------------------------------------------------+
//| Calculated buffer                                                |
//+------------------------------------------------------------------+
class CBufferCalculate : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferCalculate(const uint index_plot,const uint index_array) :
                        CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,1,0,"Calculate") {}
//--- Supported integer properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property);
//--- Supported real properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property);
//--- Supported string properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property);
//--- Display a short buffer description in the journal
   virtual void      PrintShort(void);
   
//--- Set the value to the data buffer array
   void              SetData(const uint series_index,const double value)               { this.SetBufferValue(0,series_index,value);       }
//--- Return the value from the data buffer array
   double            GetData(const uint series_index)                            const { return this.GetDataBufferValue(0,series_index);  }
   
  };
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if(
      property==BUFFER_PROP_INDEX_PLOT       || 
      property==BUFFER_PROP_STATUS           ||  
      property==BUFFER_PROP_TYPE             || 
      property==BUFFER_PROP_INDEX_BASE       || 
      property==BUFFER_PROP_INDEX_NEXT_BASE
     ) return true; 
   return false;
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| string property, otherwise return 'false'                        |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Display short buffer description in the journal                  |
//+------------------------------------------------------------------+
void CBufferCalculate::PrintShort(void)
  {
   ::Print
     (
      CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_BUFFER),"[P",(string)this.IndexPlot(),"/B",(string)this.IndexBase(),"/C",(string)this.IndexColor(),"]: ",
      this.GetTypeBufferDescription()
     );
  }
//+------------------------------------------------------------------+

No artigo 43 consideramos a estrutura e o princípio de operação das classes-herdeiras do buffer abstrato de base. E aqui a estrutura e o princípio não são diferentes daqueles já descritos anteriormente.

Agora que temos um conjunto completo contendo todas as classes-herdadas do buffer-objeto base (CBuffer), também o modificaremos.

Às vezes, é necessário realizar algumas ações com o buffer de indicador uma vez mediante solicitação ou condição. Por exemplo, limpar ou atualizar dados do buffer após pressionar um botão. Para fazer isso, vamos introduzir um sinalizador indicando que ainda não foi realizada a ação necessária.
Na seção privada da classe declaramos uma variável-membro de classe para armazenar este sinalizador, já na seção pública, dois métodos para definir e obter o valor desta variável:

//+------------------------------------------------------------------+
//| Abstract indicator buffer class                                  |
//+------------------------------------------------------------------+
class CBuffer : public CBaseObj
  {
private:
   long              m_long_prop[BUFFER_PROP_INTEGER_TOTAL];                     // Integer properties
   double            m_double_prop[BUFFER_PROP_DOUBLE_TOTAL];                    // Real properties
   string            m_string_prop[BUFFER_PROP_STRING_TOTAL];                    // String properties
   bool              m_act_state_trigger;                                        // Auxiliary buffer status switch flag
//--- Return the index of the array the buffer's (1) double and (2) string properties are located at
   int               IndexProp(ENUM_BUFFER_PROP_DOUBLE property)           const { return(int)property-BUFFER_PROP_INTEGER_TOTAL;                           }
   int               IndexProp(ENUM_BUFFER_PROP_STRING property)           const { return(int)property-BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_DOUBLE_TOTAL;  }
//--- Set the graphical construction type by buffer type and status
   void              SetDrawType(void);
//--- Return the adjusted buffer array index
   int               GetCorrectIndexBuffer(const uint buffer_index) const;

protected:
   struct SDataBuffer { double Array[]; };                                       // Structure for storing buffer object buffer arrays
   SDataBuffer       DataBuffer[];                                               // Array of all object indicator buffers
   double            ColorBufferArray[];                                         // Color buffer array
   int               ArrayColors[];                                              // Array for storing colors



public:
//--- Set buffer's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_BUFFER_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                                        }
   void              SetProperty(ENUM_BUFFER_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;                      }
   void              SetProperty(ENUM_BUFFER_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;                      }
//--- Return (1) integer, (2) real and (3) string buffer properties from the properties array
   long              GetProperty(ENUM_BUFFER_PROP_INTEGER property)        const { return this.m_long_prop[property];                                       }
   double            GetProperty(ENUM_BUFFER_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];                     }
   string            GetProperty(ENUM_BUFFER_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];                     }
//--- Get description of buffer's (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_BUFFER_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_BUFFER_PROP_STRING property);
//--- Return the flag of the buffer supporting the property
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property)          { return true;       }
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)           { return true;       }
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property)           { return true;       }

//--- Compare CBuffer objects by all possible properties (for sorting the lists by a specified buffer object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CBuffer objects by all properties (to search for equal buffer objects)
   bool              IsEqual(CBuffer* compared_obj) const;
                     
//--- Set the buffer name
   void              SetName(const string name)                                  { this.m_name=name;  }
//--- (1) Set and (2) return the buffer status switch flag
   void              SetActStateFlag(const bool flag)                            { this.m_act_state_trigger=flag;    }
   bool              GetActStateFlag(void)                                 const { return this.m_act_state_trigger;  }
   
//--- Default constructor
                     CBuffer(void){;}

Se, por exemplo, após pressionarmos um botão desligarmos a exibição do buffer, então, além de limpar todos os dados da matriz, também seremos capazes de definir o estilo de plotagem de dado buffer como DRAW_NONE. Para fazer isso, na seção pública da classe inserimos um método adicional que define o tipo de plotagem para o objeto-buffer:

public:  
//--- Send description of buffer properties to the journal (full_prop=true - all properties, false - only supported ones)
   void              Print(const bool full_prop=false);
//--- Display a short buffer description in the journal (implementation in the descendants)
   virtual void      PrintShort(void) {;}
   
//--- Set (1) the arrow code, (2) vertical shift of arrows, (3) symbol, (4) timeframe, (5) buffer activity flag
//--- (6) drawing type, (7) number of initial bars without drawing, (8) flag of displaying construction values in DataWindow,
//--- (9) shift of the indicator graphical construction along the time axis, (10) line style, (11) line width,
//--- (12) total number of colors, (13) one drawing color, (14) color of drawing in the specified color index,
//--- (15) drawing colors from the color array, (16) empty value, (17) name of the graphical series displayed in DataWindow
   virtual void      SetArrowCode(const uchar code)                  { return;                                                            }
   virtual void      SetArrowShift(const int shift)                  { return;                                                            }
   void              SetSymbol(const string symbol)                  { this.SetProperty(BUFFER_PROP_SYMBOL,symbol);                       }
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)   { this.SetProperty(BUFFER_PROP_TIMEFRAME,timeframe);                 }
   void              SetActive(const bool flag)                      { this.SetProperty(BUFFER_PROP_ACTIVE,flag);                         }
   void              SetDrawType(const ENUM_DRAW_TYPE draw_type);
   void              SetDrawBegin(const int value);
   void              SetShowData(const bool flag);
   void              SetShift(const int shift);
   void              SetStyle(const ENUM_LINE_STYLE style);
   void              SetWidth(const int width);
   void              SetColorNumbers(const int number);
   void              SetColor(const color colour);
   void              SetColor(const color colour,const uchar index);
   void              SetColors(const color &array_colors[]);
   void              SetEmptyValue(const double value);
   virtual void      SetLabel(const string label);

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

//+------------------------------------------------------------------+
//| Set the passed graphical construction type                       |
//+------------------------------------------------------------------+
void CBuffer::SetDrawType(const ENUM_DRAW_TYPE draw_type)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   this.SetProperty(BUFFER_PROP_DRAW_TYPE,draw_type);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,draw_type);
  }
//+------------------------------------------------------------------+

Neste caso, se o tipo de buffer for "Calculado", ele não terá plotagem e, portanto, sairemos do método.
Na propriedade do objeto definimos tanto o tipo de plotagem passada para o método, como este estilo para o buffer.

Visto que substituímos a constante BUFFER_PROP_INDEX_NEXT por BUFFER_PROP_INDEX_NEXT_BASE, renomeamos o método correspondente IndexNextBuffer() por IndexNextBaseBuffer() e adicionamos mais um método que retorna o índice do seguinte buffer plotado:

//--- Return (1) the serial number of the drawn buffer, (2) bound array index, (3) color buffer index,
//--- (4) index of the first free bound array, (5) index of the next drawn buffer, (6) buffer data period, (7) buffer status,
//--- (8) buffer type, (9) buffer usage flag, (10) arrow code, (11) arrow shift for DRAW_ARROW style,
//--- (12) number of initial bars that are not drawn and values in DataWindow, (13) graphical construction type,
//--- (14) flag of displaying construction values in DataWindow, (15) indicator graphical construction shift along the time axis,
//--- (16) drawing line style, (17) drawing line width, (18) number of colors, (19) drawing color, number of buffers for construction
//--- (20) set empty value, (21) buffer symbol, (22) name of the indicator graphical series displayed in DataWindow
   int               IndexPlot(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT);              }
   int               IndexBase(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE);              }
   int               IndexColor(void)                          const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR);             }
   int               IndexNextBaseBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE);         }
   int               IndexNextPlotBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT);         }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAME);   }

Finalmente, ao final do corpo da classe adicionamos quatro métodos, dois para inicialização das matrizes do objeto e dois para seu preenchimento:

//--- Return the size of the data buffer array
   virtual int       GetDataTotal(const uint buffer_index=0)   const;
//--- Return the value from the specified index of the specified (1) data, (2) color index and (3) color buffer arrays
   double            GetDataBufferValue(const uint buffer_index,const uint series_index) const;
   int               GetColorBufferValueIndex(const uint series_index) const;
   color             GetColorBufferValueColor(const uint series_index) const;
//--- Set the value to the specified index of the specified (1) data and (2) color buffer arrays
   void              SetBufferValue(const uint buffer_index,const uint series_index,const double value);
   void              SetBufferColorIndex(const uint series_index,const uchar color_index);
//--- Initialize all object buffers by (1) a specified value, (2) the empty value specified for the object
   void              InitializeAll(const double value,const uchar color_index);
   void              InitializeAll(void);
//--- Fill all data buffers with empty values in the specified timeseries index
   void              ClearData(const int series_index);
//--- Fill the specified buffer with data from (1) the passed timeseries and (2) the array
   void              FillAsSeries(const int buffer_index,CSeriesDE *series,const ENUM_SORT_BAR_MODE property);
   void              FillAsSeries(const int buffer_index,const double &array[]);
   
  };
//+------------------------------------------------------------------+

Em princípio, esses são dois métodos sobrecarregados adoptando um conjunto de parâmetros diverso para diferentes situações.

O método paramétrico que inicializa todos os buffers do objeto como parâmetros toma o valor que inicia todas as matrizes do objeto-buffer atribuídas pelo buffer de indicador, bem como o valor do índice de cor que inicia a matriz atribuída pelo buffer de cor no objeto-buffer.
O método sem parâmetros de entrada inicializa todas as matrizes com valor definido como "vazio" nas propriedades do objeto-buffer, já a matriz de cor, com zero, isto é, com a primeira das cores definidas para o objeto-buffer (ou com a única, se houver apenas uma cor).

Escrevemos a implementação destes métodos fora do corpo da classe:

//+------------------------------------------------------------------+
//| Initialize all object buffers by the specified value             |
//+------------------------------------------------------------------+
void CBuffer::InitializeAll(const double value,const uchar color_index)
  {
   for(int i=0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++)
      ::ArrayInitialize(this.DataBuffer[i].Array,value);
   if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::ArrayInitialize(this.ColorBufferArray,(color_index>this.ColorsTotal()-1 ? 0 : color_index));
  }
//+------------------------------------------------------------------+
//| Initialize all object buffers                                    |
//| by the empty value set for the object                            |
//+------------------------------------------------------------------+
void CBuffer::InitializeAll(void)
  {
   for(int i=0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++)
      ::ArrayInitialize(this.DataBuffer[i].Array,this.EmptyValue());
   if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::ArrayInitialize(this.ColorBufferArray,0);
  }
//+------------------------------------------------------------------+

Primeiro, num loop percorrendo o número de matrizes do objeto-buffer atribuídas pelos buffers de indicador, inicializamos cada matriz com o valor transferido ao método no primeiro método e também inicializamos com o valor definido para o buffer no segundo método. Então, desde que este objeto-buffer não seja um buffer com o estilo de plotagem "Preenchimento de cor entre dois níveis" e o objeto-buffer não seja um buffer calculado (esses buffers não têm um buffer de cor), a matriz de cores será inicializada com o valor passado para o primeiro método e com zero para o segundo.

//+-----------------------------------------------------------------------+
//| Fill the specified buffer with data from the passed timeseries' bars  |
//+-----------------------------------------------------------------------+
void CBuffer::FillAsSeries(const int buffer_index,CSeriesDE *series,const ENUM_SORT_BAR_MODE property)
  {
//--- If an empty timeseries is passed or a property to fill the array is a string one, leave the method
   if(series==NULL || property>FIRST_BAR_STR_PROP-1)
      return;
//--- Get the list of all its timeseries from the passed timeseries object
   CArrayObj *list=series.GetList();
   if(list==NULL || list.Total()==0)
      return;
//--- Get the number of bars in the timeseries list
   int total=list.Total();
//--- In the loop from the very last timeseries list element (from the current bar)
   int n=0;
   for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--)
     {
      //--- get the next bar object by the loop index,
      CBar *bar=list.At(i);
      //--- Set the value of the copied property
      double value=
        (bar==NULL ? this.EmptyValue()                                                    :
         property<FIRST_BAR_DBL_PROP ? bar.GetProperty((ENUM_BAR_PROP_INTEGER)property)   :
         property<FIRST_BAR_STR_PROP ? bar.GetProperty((ENUM_BAR_PROP_DOUBLE)property)    :
         this.EmptyValue()
        );
      //--- calculate the index, based on which the bar property is saved to the buffer, and
      //--- write the value of the obtained bar property to the buffer array using the calculated index
      n=total-1-i;
      this.SetBufferValue(buffer_index,n,value);
     }
  }
//+------------------------------------------------------------------+
//| Fill the specified buffer with data from the passed array        |
//+------------------------------------------------------------------+
void CBuffer::FillAsSeries(const int buffer_index,const double &array[])
  {
//--- Get the copied array size
   int total=::ArraySize(array);
   if(total==0)
      return;
//--- In the loop from the very last array element (from the current bar)
   int n=0;
   for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--)
     {
      //--- calculate the index, based on which the array value is saved to the buffer, and
      //--- write the value of the array cell by i index using the calculated index
      n=total-1-i;
      this.SetBufferValue(buffer_index,n,array[i]);
     }
  }
//+------------------------------------------------------------------+

Esses dois métodos são comentados na listagem de códigos. Ao primeiro método é transferido um ponteiro para o objeto-série temporal cujos dados são necessários para preencher o buffer de indicador, já ao segundo método é transferida uma matriz-double cujos dados também são necessários para isso.

Fazemos um ajuste no construtor privado da classe:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status,
                 ENUM_BUFFER_TYPE buffer_type,
                 const uint index_plot,
                 const uint index_base_array,
                 const int num_datas,
                 const int width,
                 const string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                        = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                          = buffer_type;
   ENUM_DRAW_TYPE type=
     (
      !this.TypeBuffer() || !this.Status() ? DRAW_NONE      : 
      this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING   : 
      ENUM_DRAW_TYPE(this.Status()+8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                     = type;
   this.m_long_prop[BUFFER_PROP_TIMEFRAME]                     = PERIOD_CURRENT;
   this.m_long_prop[BUFFER_PROP_ACTIVE]                        = true;
   this.m_long_prop[BUFFER_PROP_ARROW_CODE]                    = 0x9F;
   this.m_long_prop[BUFFER_PROP_ARROW_SHIFT]                   = 0;
   this.m_long_prop[BUFFER_PROP_DRAW_BEGIN]                    = 0;
   this.m_long_prop[BUFFER_PROP_SHOW_DATA]                     = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false);
   this.m_long_prop[BUFFER_PROP_SHIFT]                         = 0;
   this.m_long_prop[BUFFER_PROP_LINE_STYLE]                    = STYLE_SOLID;
   this.m_long_prop[BUFFER_PROP_LINE_WIDTH]                    = width;
   this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]                 = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0);
   this.m_long_prop[BUFFER_PROP_COLOR]                         = clrRed;
   this.m_long_prop[BUFFER_PROP_NUM_DATAS]                     = num_datas;
   this.m_long_prop[BUFFER_PROP_INDEX_PLOT]                    = index_plot;
   this.m_long_prop[BUFFER_PROP_INDEX_BASE]                    = index_base_array;
   this.m_long_prop[BUFFER_PROP_INDEX_COLOR]                   = this.GetProperty(BUFFER_PROP_INDEX_BASE)+this.GetProperty(BUFFER_PROP_NUM_DATAS);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]               = this.GetProperty(BUFFER_PROP_INDEX_COLOR)+
                                                                    (this.Status()==BUFFER_STATUS_FILLING || this.TypeBuffer()==BUFFER_TYPE_CALCULATE ? 0 : 1);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT]               = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot);
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0);
//--- Save string properties
   this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)]      = ::Symbol();
   this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)]       = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL);

//--- If failed to change the size of the indicator buffer array, display the appropriate message indicating the string
   if(::ArrayResize(this.DataBuffer,(int)this.GetProperty(BUFFER_PROP_NUM_DATAS))==WRONG_VALUE)
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError());
      
//--- If failed to change the size of the color array (only for a non-calculated buffer), display the appropriate message indicating the string
   if(this.TypeBuffer()>BUFFER_TYPE_CALCULATE)
      if(::ArrayResize(this.ArrayColors,(int)this.ColorsTotal())==WRONG_VALUE)
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError());

//--- For DRAW_FILLING, fill in the color array with two default colors
   if(this.Status()==BUFFER_STATUS_FILLING)
     {
      this.SetColor(clrBlue,0);
      this.SetColor(clrRed,1);
     }

//--- Bind indicator buffers with arrays
//--- In a loop by the number of indicator buffers
   int total=::ArraySize(DataBuffer);
   for(int i=0;i<total;i++)
     {
      //--- calculate the index of the next array and
      //--- bind the indicator buffer by the calculated index with the dynamic array
      //--- located by the i loop index in the DataBuffer array
      int index=(int)this.GetProperty(BUFFER_PROP_INDEX_BASE)+i;
      ::SetIndexBuffer(index,this.DataBuffer[i].Array,(this.TypeBuffer()==BUFFER_TYPE_DATA ? INDICATOR_DATA : INDICATOR_CALCULATIONS));
      //--- Set indexation flag as in the timeseries to all buffer arrays
      ::ArraySetAsSeries(this.DataBuffer[i].Array,true);
     }
//--- Bind the color buffer with the array (only for a non-calculated buffer and not for the filling buffer)
   if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
     {
      ::SetIndexBuffer((int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX);
      ::ArraySetAsSeries(this.ColorBufferArray,true);
     }
//--- If this is a calculated buffer, all is done
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
//--- Set integer parameters of the drawn buffer
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_TYPE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_CODE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_BEGIN));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHOW_DATA,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHOW_DATA));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH));
   this.SetColor((color)this.GetProperty(BUFFER_PROP_COLOR));
//--- Set real buffer parameters
   ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE));
//--- Set string buffer parameters
   ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL));
  }
//+------------------------------------------------------------------+

Visto que a variável m_act_state_trigger foi alterada a nível de valor true, a ação realizada com o buffer ocorreu durante sua criação (e acontece que, por exemplo, ao pressionar um botão verificaremos se "foi ou não realizada a ação adequada com o objeto-buffer", se não foi, ela deverá ser realizada. O sinalizador definido não permitirá executar nenhuma ação falsa se o sinalizador tiver sido redefinido inicialmente).
As outras modificações que introduzimos têm a ver com o fato de agora termos um buffer calculado que, por um lado, não contém matriz de cores e, por outro, precisa de nós definirmos suas as propriedades de série gráfica. Portanto, ao calcular os índices das matrizes agora é levado em consideração o tipo de objeto-buffer (sendo que se for um buffer calculado, nesse cálculo serão realizados os ajustes correspondentes), e antes de definir os valores da série gráfica por meio de buffers de indicador, verificamos o tipo de objeto-buffer (com a particularidade de se for um buffer calculado, sairemos do método, uma vez que esse buffer não tem série gráfica).

Para métodos que definem as propriedades da série gráfica do objeto-buffer introduzimos uma verificação de tipo de buffer e, se for um buffer calculado, sairemos imediatamente o método:

//+------------------------------------------------------------------+
//| Set the number of initial bars                                   |
//| without drawing and values in DataWindow                         |
//+------------------------------------------------------------------+
void CBuffer::SetDrawBegin(const int value)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   this.SetProperty(BUFFER_PROP_DRAW_BEGIN,value);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,value);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Set the number of colors                                         |
//+------------------------------------------------------------------+
void CBuffer::SetColorNumbers(const int number)
  {
   if(number>IND_COLORS_TOTAL || this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   int n=(this.Status()!=BUFFER_STATUS_FILLING ? number : 2);
   this.SetProperty(BUFFER_PROP_COLOR_INDEXES,n);
   ::ArrayResize(this.ArrayColors,n);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_COLOR_INDEXES,n);
  }
//+------------------------------------------------------------------+

Foram feitas verificações semelhantes em todos os métodos que o buffer calculado não necessita, elas não serão consideradas aqui.

Às vezes, ao calcular o índice de uma barra, índice esse em que é necessário definir o valor para o buffer de indicador, o valor calculado é menor que zero. Isso acontece ao fazer esse cálculo quando no gráfico atual são exibidos dados de um timeframe diferente do atual. Para não verificar o índice calculado excessivamente, basta, nos métodos encarregados de registrar os valores de acordo com o índice especificado, verificar se o índice transferido é menor que zero e sair do método se for assim:

//+------------------------------------------------------------------+
//| Set the value to the specified timeseries index                  |
//| for the specified data buffer array                              |
//+------------------------------------------------------------------+
void CBuffer::SetBufferValue(const uint buffer_index,const uint series_index,const double value)
  {
   if(this.GetDataTotal(buffer_index)==0)
      return;
   int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index);
   int data_total=this.GetDataTotal(buffer_index);
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   if(data_index<0)
      return;
   this.DataBuffer[correct_buff_index].Array[data_index]=value;
  }
//+------------------------------------------------------------------+
//| Set the color index to the specified timeseries index            |
//| of the color buffer array                                        |
//+------------------------------------------------------------------+
void CBuffer::SetBufferColorIndex(const uint series_index,const uchar color_index)
  {
   if(this.GetDataTotal(0)==0 || color_index>this.ColorsTotal()-1 || this.Status()==BUFFER_STATUS_FILLING || this.Status()==BUFFER_STATUS_NONE)
      return;
   int data_total=this.GetDataTotal(0);
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   if(::ArraySize(this.ColorBufferArray)==0)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF));
   if(data_index<0)
      return;
   this.ColorBufferArray[data_index]=color_index;
  }
//+------------------------------------------------------------------+

Na listagem da classe, também foram feitas algumas melhorias cosméticas e alterações, o que não faz muito sentido descrever aqui, uma vez que não afetam o desempenho do código. Todas as alterações feitas podem ser visualizadas nos arquivos anexados ao final do artigo.

Agora complementamos a classe da coleção de buffers de indicador CBuffersCollection no arquivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh.
Como agora tornamos possível trabalhar com períodos gráficos diferentes do atual, a classe-coleção de buffers deve ter acesso à classe da coleção das séries temporais. A partir da coleção das séries temporais vamos obter todas as séries temporais necessárias para o cálculo da construção de buffers do indicador no gráfico atual com base nos dados vindos de gráficos de outros períodos.
Para que a classe-coleção tenha acesso à classe-coleção das séries temporais, à coleção de buffers de indicador simplesmente transferiremos o ponteiro para a coleção das séries temporais. Com base nesse ponteiro vamos selecionar os dados e métodos que precisamos para obtê-las.

Da mesma forma como substituímos o nome de todas as constantes em todos arquivos das classes dos objetos-buffers, aqui já foram substituídos todas as ocorrências da linha "BUFFER_PROP_INDEX_PLOT" pela nova constante BUFFER_PROP_INDEX_NEXT_PLOT, enquanto o acesso ao método IndexNextBuffer() foi substituído pela sua versão renomeada IndexNextBaseBuffer().

Ao arquivo da classe da coleção de buffers de indicador anexamos o arquivo da classe do objeto do buffer calculado e o arquivo da classe do objeto das séries temporais e definimos uma variável-membro privada de classe para armazenar um ponteiro para um objeto da classe-coleção das séries temporais:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Indicators\BufferArrow.mqh"
#include "..\Objects\Indicators\BufferLine.mqh"
#include "..\Objects\Indicators\BufferSection.mqh"
#include "..\Objects\Indicators\BufferHistogram.mqh"
#include "..\Objects\Indicators\BufferHistogram2.mqh"
#include "..\Objects\Indicators\BufferZigZag.mqh"
#include "..\Objects\Indicators\BufferFilling.mqh"
#include "..\Objects\Indicators\BufferBars.mqh"
#include "..\Objects\Indicators\BufferCandles.mqh"
#include "..\Objects\Indicators\BufferCalculate.mqh"
#include "TimeSeriesCollection.mqh"
//+------------------------------------------------------------------+
//| Collection of indicator buffers                                  |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Buffer object list
   CTimeSeriesCollection  *m_timeseries;                 // Pointer to the timeseries collection object

Visto que foram adicionados muitos métodos que executam as mesmas funções para diferentes objetos-buffers, para não descrever cada método (ainda por cima porque estão todos assinados nos comentários), declaramos todos os métodos novos no corpo da classe, e depois os analisamos seletivamente sua estrutura e princípios:

//+------------------------------------------------------------------+
//| Collection of indicator buffers                                  |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Buffer object list
   CTimeSeriesCollection  *m_timeseries;                 // Pointer to the timeseries collection object
   
//--- Return the index of the (1) last, (2) next drawn and (3) basic buffer
   int                     GetIndexLastPlot(void);
   int                     GetIndexNextPlot(void);
   int                     GetIndexNextBase(void);
//--- Create a new buffer object and place it to the collection list
   bool                    CreateBuffer(ENUM_BUFFER_STATUS status);
//--- Get data of the necessary timeseries and bars for working with a single buffer bar, and return the number of bars
   int                     GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period);

public:
//--- Return (1) oneself and (2) the timeseries list
   CBuffersCollection     *GetObject(void)               { return &this;                                       }
   CArrayObj              *GetList(void)                 { return &this.m_list;                                }
//--- Return the number of (1) drawn buffers, (2) all arrays used to build all buffers in the collection
   int                     PropertyPlotsTotal(void);
   int                     PropertyBuffersTotal(void);
   
//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", 
//--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels",
//--- (8) "Display as bars", (9) "Display as candles", calculated buffer
   bool                    CreateArrow(void)             { return this.CreateBuffer(BUFFER_STATUS_ARROW);      }
   bool                    CreateLine(void)              { return this.CreateBuffer(BUFFER_STATUS_LINE);       }
   bool                    CreateSection(void)           { return this.CreateBuffer(BUFFER_STATUS_SECTION);    }
   bool                    CreateHistogram(void)         { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM);  }
   bool                    CreateHistogram2(void)        { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); }
   bool                    CreateZigZag(void)            { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG);     }
   bool                    CreateFilling(void)           { return this.CreateBuffer(BUFFER_STATUS_FILLING);    }
   bool                    CreateBars(void)              { return this.CreateBuffer(BUFFER_STATUS_BARS);       }
   bool                    CreateCandles(void)           { return this.CreateBuffer(BUFFER_STATUS_CANDLES);    }
   bool                    CreateCalculate(void)         { return this.CreateBuffer(BUFFER_STATUS_NONE);       }
   
//--- Return the buffer by (1) the graphical series name, (2) timeframe, (2) Plot index and (3) collection list object
   CBuffer                *GetBufferByLabel(const string plot_label);
   CBuffer                *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe);
   CBuffer                *GetBufferByPlot(const int plot_index);
   CBuffer                *GetBufferByListIndex(const int index_list);
//--- Return buffers by their status by the specified serial number
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   CBufferArrow           *GetBufferArrow(const int number);
   CBufferLine            *GetBufferLine(const int number);
   CBufferSection         *GetBufferSection(const int number);
   CBufferHistogram       *GetBufferHistogram(const int number);
   CBufferHistogram2      *GetBufferHistogram2(const int number);
   CBufferZigZag          *GetBufferZigZag(const int number);
   CBufferFilling         *GetBufferFilling(const int number);
   CBufferBars            *GetBufferBars(const int number);
   CBufferCandles         *GetBufferCandles(const int number);
   CBufferCalculate       *GetBufferCalculate(const int number);
   
//--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object
   void                    InitializePlots(const double value,const uchar color_index);
   void                    InitializePlots(void);
//--- Initialize all calculated buffers by a (1) specified value, (2) empty value set for the buffer object
   void                    InitializeCalculates(const double value);
   void                    InitializeCalculates(void);
//--- Set color values from the passed color array for all indicator buffers within the collection
   void                    SetColors(const color &array_colors[]);
   
//--- Set the value by the timeseries index for the (1) arrow, (2) line, (3) section, (4) zero line histogram,
//--- (5) two buffer histogram, (6) zigzag, (7) filling, (8) bar, (9) candle, (10) calculated buffer
   void                    SetBufferArrowValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferLineValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferSectionValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferHistogramValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferHistogram2Value(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false);
   void                    SetBufferZigZagValue(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false);
   void                    SetBufferFillingValue(const int number,const int series_index,const double value1,const double value2,bool as_current=false);
   void                    SetBufferBarsValue(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
   void                    SetBufferCandlesValue(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
   void                    SetBufferCalculateValue(const int number,const int series_index,const double value);
   
//--- Set the color index to the color buffer by its serial number of
//--- (1) arrows, (2) lines, (3) sections, (4) zero histogram
//--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   void                    SetBufferArrowColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferLineColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferSectionColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferHistogramColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferHistogram2ColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferZigZagColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferFillingColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferBarsColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferCandlesColorIndex(const int number,const int series_index,const uchar color_index);
   
//--- Clear buffer data by its index in the list in the specified timeseries bar
   void                    Clear(const int buffer_list_index,const int series_index);
//--- Clear data by the timeseries index for the (1) arrow, (2) line, (3) section, (4) zero line histogram,
//--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles
   void                    ClearBufferArrow(const int number,const int series_index);
   void                    ClearBufferLine(const int number,const int series_index);
   void                    ClearBufferSection(const int number,const int series_index);
   void                    ClearBufferHistogram(const int number,const int series_index);
   void                    ClearBufferHistogram2(const int number,const int series_index);
   void                    ClearBufferZigZag(const int number,const int series_index);
   void                    ClearBufferFilling(const int number,const int series_index);
   void                    ClearBufferBars(const int number,const int series_index);
   void                    ClearBufferCandles(const int number,const int series_index);
   
//--- Constructor
                           CBuffersCollection();
//--- Get pointers to the timeseries collection (the method is called in the CollectionOnInit() method of the CEngine object)
   void                    OnInit(CTimeSeriesCollection *timeseries) { this.m_timeseries=timeseries;  }
  };
//+------------------------------------------------------------------+

Vejamos o propósito dos métodos recém-adicionados. Após descrever o propósito dos métodos, veremos sua implementação, que, como a maioria dos métodos das classes da biblioteca, é realizada fora do corpo da classe.

GetIndexLastPlot() retorna o índice da série gráfica do último objeto-buffer criado:

//+------------------------------------------------------------------+
//| Return the index of the last drawn buffer                        |
//+------------------------------------------------------------------+
int CBuffersCollection::GetIndexLastPlot(void)
  {
//--- Return the pointer to the list. If the list is not created for some reason, return -1
   CArrayObj *list=this.GetList();
   if(list==NULL)
      return WRONG_VALUE;
//--- Get the index of the drawn buffer with the highest value. If the FindBufferMax() method returns -1,
//--- the list is empty, return index 0 for the very first buffer in the list
   int index=CSelect::FindBufferMax(list,BUFFER_PROP_INDEX_PLOT);
   if(index==WRONG_VALUE)
      return 0;
//--- if the index is not -1,
//--- get the buffer object from the list by its index
   CBuffer *buffer=this.m_list.At(index);
   if(buffer==NULL)
      return WRONG_VALUE;
//--- Return the Plot index of the buffer object
   return buffer.IndexPlot();
  }
//+------------------------------------------------------------------+

O método é bastante específico e se destina mais ao método para o método de criação de novo buffer, uma vez que o valor que ele retorna está vinculado ao tamanho da lista de buffers (se a lista estiver vazia, será retornado zero, em vez de -1). Porém, se no futuro for encontrada outra finalidade para este método, vamos modificá-lo ligeiramente. Se não for mais necessário, exceto para a única implementação vista hoje, tornaremos o método privado.

O método privado GetBarsData() serve para receber dados de outras séries temporais, que não são nativas para o objeto-buffer das séries temporais, e retorna a quantidade de barras do timeframe atual incluída numa barra do período gráfico do objeto-buffer:

//+------------------------------------------------------------------+
//| Get data of the necessary timeseries and bars                    |
//| for working with a single bar of the buffer                      |
//+------------------------------------------------------------------+
int CBuffersCollection::GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period)
  {
//--- Get timeseries of the current chart and the chart of the buffer timeframe
   CSeriesDE *series_current=this.m_timeseries.GetSeries(buffer.Symbol(),PERIOD_CURRENT);
   CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe());
   if(series_current==NULL || series_period==NULL)
      return WRONG_VALUE;
//--- Get the bar object of the current timeseries corresponding to the required timeseries index
   CBar *bar_current=series_current.GetBar(series_index);
   if(bar_current==NULL)
      return WRONG_VALUE;
//--- Get the timeseries bar object of the buffer chart period corresponding to the time the timeseries bar of the current chart falls into
   CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),NULL,series_period.Timeframe());
   if(bar_period==NULL)
      return WRONG_VALUE;
//--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart 
   index_bar_period=bar_period.Index(PERIOD_CURRENT);
//--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period
//--- and return this value (1 if the result is 0)
   int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe());
   return(num_bars>0 ? num_bars : 1);
  }
//+------------------------------------------------------------------+

Este método será amplamente utilizado para calcular barras do gráfico atual que devem seu usadas para gravar os valores do buffer do indicador contendo os dados vindos de um período gráfico diferente do atual.

Os métodos PropertyPlotsTotal() e PropertyBuffersTotal() são os antigos métodos PlotsTotal() e BuffersTotal() renomeados; eles retornam os valores corretos a serem especificados como propriedades do programa-indicador em #property indicator_plots и #property indicator_buffers respectivamente.
O método PropertyBuffersTotal() apenas foi renomeado, e suas constantes também foram renomeadas (BUFFER_PROP_INDEX_NEXT para BUFFER_PROP_INDEX_NEXT_BASE).
O método PropertyPlotsTotal() foi reescrito novamente por causa do surgimento de um novo buffer calculado, e agora os dados devem ser obtidos de uma maneira diferente da usada anteriormente:

//+------------------------------------------------------------------+
//| Return the number of drawn buffers                               |
//+------------------------------------------------------------------+
int CBuffersCollection::PropertyPlotsTotal(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   return(list!=NULL ? list.Total() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

Obtemos uma lista de apenas objetos-buffer plotados e retornamos seu tamanho. Se a lista estiver vazia, será retornado -1.

CreateCalculate() serve para criar um objeto do buffer calculado (não plotado).
O método é implementado diretamente no corpo da classe e retorna o resultado do método de criação de novo buffer, que vimos no artigo anterior, ao qual é transferido o status do buffer calculado:

bool CreateCalculate(void) { return this.CreateBuffer(BUFFER_STATUS_NONE); }

O método GetBufferByLabel() retorna o ponteiro para o objeto-buffer de acordo com seu nome de série gráfica:

//+------------------------------------------------------------------+
//| Return the buffer by the graphical series name                   |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetBufferByLabel(const string plot_label)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_LABEL,plot_label,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(list.Total()-1) : NULL);
  }
//+------------------------------------------------------------------+

Obtemos a lista de objetos-buffers cuja propriedade "Nome da série gráfica" corresponde ao valor transferido ao método, e retornamos o ponteiro para o último objeto da lista. Se a lista estiver vazia, será retornado NULL. Se houver vários objetos-buffers com o mesmo nome da série gráfica, o método retornará o último objeto-buffer criado com tal nome.

O método GetBufferByTimeframe() retorna o ponteiro para o objeto-buffer de acordo com o valor do período gráfico definido para ele:

//+------------------------------------------------------------------+
//| Return the buffer by timeframe                                   |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TIMEFRAME,timeframe,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(list.Total()-1) : NULL);
  }
//+------------------------------------------------------------------+

Obtemos a lista de objetos-buffers cuja propriedade "Período gráfico" corresponde ao valor transferido ao método, e retornamos o ponteiro para o último objeto da lista. Se a lista estiver vazia, será retornado NULL. Se houver vários objetos-buffers com o mesmo período gráfico, o método retornará o último objeto-buffer criado com tal período gráfico.

O método GetBufferByListIndex() retorna o ponteiro para o objeto-buffer de acordo com o índice na lista-coleção:

//+------------------------------------------------------------------+
//| Return the buffer by the collection list index                   |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetBufferByListIndex(const int index_list)
  {
   return this.m_list.At(index_list);
  }
//+------------------------------------------------------------------+

Simplesmente retornamos um ponteiro para o objeto mais recente na lista-coleção de buffers. Se a lista estiver vazia, o método At() retornará NULL.


O método GetBufferCalculate() retorna um ponteiro para um objeto-buffer de acordo com seu número de sequência (na ordem de criação de buffers calculados):

//+------------------------------------------------------------------+
//|Return the calculated buffer by serial number                     |
//| (0 - the very first candle buffer, 1,2,N - subsequent ones)      |
//+------------------------------------------------------------------+
CBufferCalculate *CBuffersCollection::GetBufferCalculate(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+

A partir da lista-coleção de objetos-buffers obtemos apenas aqueles buffers cujo tipo é "Buffer calculado", e da lista obtida retornamos o ponteiro para o objeto de acordo com o índice transferido ao método. Se a lista filtrada resultante estiver vazia ou o índice do objeto desejado ficar fora da lista resultante, o método At() retornará NULL.

Os métodos sobrecarregados InitializePlots() servem para inicializar todos os buffers plotados na coleção:

//+------------------------------------------------------------------+
//| Initialize all drawn buffers by a specified empty value          |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializePlots(const double value,const uchar color_index)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll(value,color_index);
     }
  }
//+------------------------------------------------------------------+

Todos os buffers plotados da coleção são inicializados pelo método através do valor do buffer e o valor do índice de cor transferidos aos parâmetros.

Primeiro, na lista são selecionados apenas buffers plotados, em seguida, num ciclo percorrendo a lista resultante obtemos o seguinte objeto-buffer e inicializamos todas suas matrizes com ajuda do método da classe do objeto-buffer InitializeAll() e transferimos para ele os valores de entrada do método.

//+------------------------------------------------------------------+
//| Initialize all drawn buffers using                               |
//| the empty value set for the buffer object                        |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializePlots(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll();
     }
  }  
//+------------------------------------------------------------------+

Todos os buffers plotados da coleção são inicializados pelo método através do valor do buffer definido para cada objeto-buffer e através do valor do índice de cor como zero.

Primeiro, na lista são selecionados apenas buffers plotados, em seguida, num ciclo percorrendo a lista resultante obtemos o seguinte objeto-buffer e inicializamos todas suas matrizes com ajuda do método da classe do objeto-buffer InitializeAll() sem parâmetros.

Os métodos sobrecarregados InitializeCalculates() servem para inicializar todos os buffers calculados na coleção:

//+------------------------------------------------------------------+
//| Initialize all calculated buffers by a specified empty value     |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializeCalculates(const double value)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll(value,0);
     }
  }
//+------------------------------------------------------------------+

Todos os buffers calculados da coleção são inicializados pelo método através do valor do buffer transferido aos parâmetros.

Primeiro, na lista são selecionados apenas buffers calculados, em seguida, num ciclo percorrendo a lista resultante obtemos o seguinte objeto-buffer e inicializamos suas matrizes com ajuda do método da classe do objeto-buffer InitializeAll() e transferimos para ele o valor de entrada do método.

//+------------------------------------------------------------------+
//| Initialize all calculated buffers using                          |
//| the empty value set for the buffer object                        |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializeCalculates(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll();
     }
  }
//+------------------------------------------------------------------+

Todos os buffers calculados da coleção são inicializados pelo método através do valor do buffer definido para ele.

Primeiro, na lista são selecionados apenas buffers calculados, em seguida, num ciclo percorrendo a lista resultante obtemos o seguinte objeto-buffer e inicializamos sua matriz com ajuda do método da classe do objeto-buffer InitializeAll() sem parâmetros.

O método SetColors() define, para todos os buffers plotados da coleção, os valores de cor a partir da matriz de cores.

//+------------------------------------------------------------------+
//| Set color values from the passed color array                     |
//| for all collection indicator buffers                             |
//+------------------------------------------------------------------+
void CBuffersCollection::SetColors(const color &array_colors[])
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.SetColors(array_colors);
     }
  }
//+------------------------------------------------------------------+

O método define, para todos os buffers plotados da coleção, os valores de cor passados como parâmetros na matriz.

Primeiro, na lista são selecionados apenas buffers plotados, em seguida, num ciclo percorrendo a lista resultante obtemos o seguinte objeto-buffer e definimos um conjunto de cores para ele usando o método SetColors() e transferimos para ele a matriz passada nos parâmetros de entrada do método.

O método Clear() limpa os dados do buffer segundo seu índice na lista-coleção na barra especificada da série temporal:

//+------------------------------------------------------------------+
//| Clear buffer data by its index in the list                       |
//| in the specified timeseries bar                                  |
//+------------------------------------------------------------------+
void CBuffersCollection::Clear(const int buffer_list_index,const int series_index)
  {
   CBuffer *buff=this.GetBufferByListIndex(buffer_list_index);
   if(buff==NULL)
      return;
   buff.ClearData(series_index);
  }
//+------------------------------------------------------------------+

Primeiro, obtemos o ponteiro para o objeto-buffer segundo seu índice na lista com ajuda do método GetBufferByListIndex() considerado acima, em seguida, usamos o método do objeto-buffer obtido ClearData() para limpar todas suas matrizes.

Para podermos calcular os dados necessários dos índices das barras ao plotar as linhas dos buffers de indicador no gráfico atual e, ao mesmo tempo, usar os dados vindos de outros períodos gráficos, precisamos transferir, à classe-coleção dos buffers, o ponteiro para a lista-coleção das séries temporais usadas localizadas na classe-coleção das séries temporais.
A maneira mais fácil de fazer isso é durante a inicialização do programa, quando todos os objetos das classes da biblioteca já estão criados.
O método da classe OnInit() atribui à variável m_timeseries o ponteiro, transferido a ela, para a classe-coleção das séries temporais:

void OnInit(CTimeSeriesCollection *timeseries) { this.m_timeseries=timeseries; }

Além disso, vamos chamar esse método na função de inicialização da biblioteca nos programas. Como há muito tempo já temos tudo pronto para isso, um pouco mais abaixo simplesmente acabaremos de escrever a chamada deste método no método necessário da classe CEngine.

A classe já contém métodos para definir valores por meio dos buffers de indicador com base no tipo de buffer (setas, linhas, etc.), para definir o índice de cor para esses buffers e para limpar todas as matrizes desses buffers. No total, foram adicionados 28 métodos. Não vamos ver cada um deles, porque todos os métodos já estão nos arquivos anexados ao final do artigo, a lógica de cada um deles é idêntica, a única diferença está apenas no número de matrizes para buffers de diferente tipo. Por isso, aqui consideraremos apenas métodos para o buffer de setas.

Método que define o valor segundo o índice da série temporal para o buffer de setas:

//+------------------------------------------------------------------+
//| Set the value to the arrow buffer by the timeseries index        |
//+------------------------------------------------------------------+
void CBuffersCollection::SetBufferArrowValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
//--- Get the arrow buffer object
   CBufferArrow *buff=this.GetBufferArrow(number);
   if(buff==NULL)
      return;
//--- If the buffer usage flag is set only as for the current timeframe,
//--- write the passed value to the current buffer bar and exit (the color is not used)
   if(as_current)
     {
      buff.SetBufferValue(0,series_index,value);
      return;
     }
//--- Get data on the necessary timeseries and bars, and calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period
   int index_bar_period=series_index;
   int num_bars=this.GetBarsData(buff,series_index,index_bar_period);
   if(num_bars==WRONG_VALUE)
      return;
//--- Calculate the index of the next bar for the current chart in the loop by the number of bars and
//--- set the value and color passed to the method by the calculated index
   for(int i=0;i<num_bars;i++)
     {
      int index=index_bar_period-i;
      if(index<0)
         break;
      buff.SetBufferValue(0,index,value);
      buff.SetBufferColorIndex(index,color_index);
     }
  }  
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários no código. Vou explicar alguns pontos.

Ao método é transferido o número do objeto do buffers de setas. O número indica o número de sequência de todos os buffers de setas criados. O primeiro buffer de setas que criamos em nosso programa terá o número 0, o segundo, 1, o terceiro, 2, etc. Não se deve confundir os números dos objetos-buffers criados com os índices dos objetos-buffers na lista-coleção. Os índices de buffers recém-adicionados sempre vão em ordem crescente e não dependem do tipo de objeto-buffer. O número de objetos-buffers depende do tipo de buffer. Examinamos esse conceito em detalhes no início do artigo.

Ao método é transferido o índice da barra da série temporal onde é necessário inserir o valor que também é transferido pelo seguinte parâmetro, o índice de cor que será usado para colorir a linha de buffer de indicador segundo o índice especificado da série temporal, e o parâmetro adicional "como para a série temporal atual".

Os métodos estão definidos de forma que, se o objeto-buffer tiver um valor de propriedade "período gráfico" diferente do período gráfico atual, independentemente do índice da série temporal transferido ao método, a linha do indicador será plotada no gráfico atual levando em consideração os dados do período gráfico do objeto-buffer. Ou seja, o método exibirá corretamente os dados de outros períodos gráficos no atual. Desde que o sinalizador as_current tenha o valor false (por padrão). Se este sinalizador for definido como true, o método funcionará apenas com os dados do período gráfico atual, independentemente de se o objeto-buffer tem outro período gráfico em suas propriedades.

Método que define, para objeto-buffer de setas segundo seu número de sequência, o índice de cor na matriz-buffer de cor:

//+------------------------------------------------------------------+
//| Set the color index to the color buffer                          |
//| of the arrow buffer by the timeseries index                      |
//+------------------------------------------------------------------+
void CBuffersCollection::SetBufferArrowColorIndex(const int number,const int series_index,const uchar color_index)
  {
   CBufferArrow *buff=this.GetBufferArrow(number);
   if(buff==NULL)
      return;
   buff.SetBufferColorIndex(series_index,color_index);
  }
//+------------------------------------------------------------------+

Neste caso, tudo é bem simples, porque obtemos o objeto-buffer de setas segundo seu número e definimos, no índice transferido da série temporal, o índice de cor especificado.

Método que limpa os dados segundo o índice da série temporal para o buffer de setas segundo seu número:

//+------------------------------------------------------------------+
//| Clear the arrow buffer data by the timeseries index              |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearBufferArrow(const int number,const int series_index)
  {
   CBufferArrow *buff=this.GetBufferArrow(number);
   if(buff==NULL)
      return;
   buff.SetBufferValue(0,series_index,buff.EmptyValue());
  }
//+------------------------------------------------------------------+

Neste caso, obtemos o objeto-buffer de setas segundo seu número e definimos, no índice transferido da série temporal, o valor "vazio" definido para o objeto-buffer.

Todos os outros métodos para trabalhar com outros tipos de objetos-buffers não diferem fundamentalmente daqueles discutidos acima, com exceção do número de matrizes e do tipo de objeto-buffer. Eles podem ser estudados individualmente

Por hoje essas são todas as modificações da classe-coleção dos objetos-buffers.
A listagem completa com todas as alterações está nos arquivos anexados ao artigo.

Agora complementamos e corrigimos a classe do objeto da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh. Vamos proceder da mesma forma que ao analisar as modificações da classe-coleção dos buffers: primeiro, vamos ver as adições e alterações feitas no corpo da classe e, em seguida, analisaremos cada um dos métodos.
Não vamos exibir a listagem completa do corpo da classe, uma vez que é bastante grande, e mostraremos apenas os locais com alterações e adições:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
//--- The code has been removed for the sake of space
//--- ...
public:
//--- The code has been removed for the sake of space
//--- ...
//--- Return the bar type of the specified timeframe's symbol by (1) index and (2) time
   ENUM_BAR_BODY_TYPE   SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   ENUM_BAR_BODY_TYPE   SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);

//--- Copy the specified double property of the specified timeseries of the specified symbol to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool                 SeriesCopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_BAR_PROP_DOUBLE property,
                                                   double &array[],const double empty=EMPTY_VALUE)
                          { return this.m_time_series.CopyToBufferAsSeries(symbol,timeframe,property,array,empty);}

//--- Return (1) the buffer collection and (2) the buffer list from the collection 
   CBuffersCollection  *GetBuffersCollection(void)                                     { return &this.m_buffers;                             }
   CArrayObj           *GetListBuffers(void)                                           { return this.m_buffers.GetList();                    }
//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastBuffer(void);
//--- Return buffers by drawing style by a serial number
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   CBufferArrow        *GetBufferArrow(const int number)                               { return this.m_buffers.GetBufferArrow(number);       }
   CBufferLine         *GetBufferLine(const int number)                                { return this.m_buffers.GetBufferLine(number);        }
   CBufferSection      *GetBufferSection(const int number)                             { return this.m_buffers.GetBufferSection(number);     }
   CBufferHistogram    *GetBufferHistogram(const int number)                           { return this.m_buffers.GetBufferHistogram(number);   }
   CBufferHistogram2   *GetBufferHistogram2(const int number)                          { return this.m_buffers.GetBufferHistogram2(number);  }
   CBufferZigZag       *GetBufferZigZag(const int number)                              { return this.m_buffers.GetBufferZigZag(number);      }
   CBufferFilling      *GetBufferFilling(const int number)                             { return this.m_buffers.GetBufferFilling(number);     }
   CBufferBars         *GetBufferBars(const int number)                                { return this.m_buffers.GetBufferBars(number);        }
   CBufferCandles      *GetBufferCandles(const int number)                             { return this.m_buffers.GetBufferCandles(number);     }
   CBufferCalculate    *GetBufferCalculate(const int number)                           { return this.m_buffers.GetBufferCalculate(number);   }
   
//--- Return the number of (1) drawn buffers and (2) all indicator arrays
   int                  BuffersPropertyPlotsTotal(void)                                { return this.m_buffers.PropertyPlotsTotal();         }
   int                  BuffersPropertyBuffersTotal(void)                              { return this.m_buffers.PropertyBuffersTotal();       }

//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", 
//--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels",
//--- (8) "Display as bars", (9) "Display as candles", calculated buffer
   bool                 BufferCreateArrow(void)                                        { return this.m_buffers.CreateArrow();                }
   bool                 BufferCreateLine(void)                                         { return this.m_buffers.CreateLine();                 }
   bool                 BufferCreateSection(void)                                      { return this.m_buffers.CreateSection();              }
   bool                 BufferCreateHistogram(void)                                    { return this.m_buffers.CreateHistogram();            }
   bool                 BufferCreateHistogram2(void)                                   { return this.m_buffers.CreateHistogram2();           }
   bool                 BufferCreateZigZag(void)                                       { return this.m_buffers.CreateZigZag();               }
   bool                 BufferCreateFilling(void)                                      { return this.m_buffers.CreateFilling();              }
   bool                 BufferCreateBars(void)                                         { return this.m_buffers.CreateBars();                 }
   bool                 BufferCreateCandles(void)                                      { return this.m_buffers.CreateCandles();              }
   bool                 BufferCreateCalculate(void)                                    { return this.m_buffers.CreateCalculate();            }
   
//--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object
   void                 BuffersInitPlots(const double value,const uchar color_index)   { this.m_buffers.InitializePlots(value,color_index);  }
   void                 BuffersInitPlots(void)                                         { this.m_buffers.InitializePlots();                   }
//--- Initialize all calculated buffers by a (1) specified value, (2) empty value set for the buffer object
   void                 BuffersInitCalculates(const double value)                      { this.m_buffers.InitializeCalculates(value);         }
   void                 BuffersInitCalculates(void)                                    { this.m_buffers.InitializeCalculates();              }

//--- Return buffer data by its serial number of (1) arrows, (2) line, (3) sections and (4) histogram from zero
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   double               BufferDataArrow(const int number,const int series_index);
   double               BufferDataLine(const int number,const int series_index);
   double               BufferDataSection(const int number,const int series_index);
   double               BufferDataHistogram(const int number,const int series_index);
//--- Return buffer data by its serial number of (1) the zero and (2) the first histogram buffer on two buffers
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   double               BufferDataHistogram20(const int number,const int series_index);
   double               BufferDataHistogram21(const int number,const int series_index);
//--- Return buffer data by its serial number of (1) the zero and (2) the first zigzag buffer
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   double               BufferDataZigZag0(const int number,const int series_index);
   double               BufferDataZigZag1(const int number,const int series_index);
//--- Return buffer data by its serial number of (1) the zero and (2) the first filling buffer
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   double               BufferDataFilling0(const int number,const int series_index);
   double               BufferDataFilling1(const int number,const int series_index);
//--- Return buffer data by its serial number of (1) Open, (2) High, (3) Low and (4) Close bar buffers
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   double               BufferDataBarsOpen(const int number,const int series_index);
   double               BufferDataBarsHigh(const int number,const int series_index);
   double               BufferDataBarsLow(const int number,const int series_index);
   double               BufferDataBarsClose(const int number,const int series_index);
//--- Return buffer data by its serial number of (1) Open, (2) High, (3) Low and (4) Close candle buffers
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   double               BufferDataCandlesOpen(const int number,const int series_index);
   double               BufferDataCandlesHigh(const int number,const int series_index);
   double               BufferDataCandlesLow(const int number,const int series_index);
   double               BufferDataCandlesClose(const int number,const int series_index);

//--- Set buffer data by its serial number of (1) arrows, (2) line, (3) sections, (4) histogram from zero and the (5) calculated buffer
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   void                 BufferSetDataArrow(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataLine(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataSection(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataHistogram(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataCalculate(const int number,const int series_index,const double value);
//--- Set data of the (1) zero, (2) first and (3) all histogram buffers on two buffers by a serial number of a created buffer
//--- (0 - the very first created buffer with the HISTOGRAM2 drawing style, 1,2,N - subsequent ones)
   void                 BufferSetDataHistogram20(const int number,const int series_index,const double value);
   void                 BufferSetDataHistogram21(const int number,const int series_index,const double value);
   void                 BufferSetDataHistogram2(const int number,const int series_index,const double value0,const double value1,const uchar color_index,bool as_current=false);
//--- Set data of the (1) zero, (2) first and (3) all zigzag buffers by a serial number of a created buffer
//--- (0 - the very first created buffer with the ZIGZAG drawing style, 1,2,N - subsequent ones)
   void                 BufferSetDataZigZag0(const int number,const int series_index,const double value);
   void                 BufferSetDataZigZag1(const int number,const int series_index,const double value);
   void                 BufferSetDataZigZag(const int number,const int series_index,const double value0,const double value1,const uchar color_index,bool as_current=false);
//--- Set data of the (1) zero, (2) first and (3) all filling buffers by a serial number of a created buffer
//--- (0 - the very first created buffer with the FILLING drawing style, 1,2,N - subsequent ones)
   void                 BufferSetDataFilling0(const int number,const int series_index,const double value);
   void                 BufferSetDataFilling1(const int number,const int series_index,const double value);
   void                 BufferSetDataFilling(const int number,const int series_index,const double value0,const double value1,bool as_current=false);
//--- Set data of the (1) Open, (2) High, (3) Low, (4) Close and (5) all bar buffers by a serial number of a created buffer
//--- (0 - the very first created buffer with the BARS drawing style, 1,2,N - subsequent ones)
   void                 BufferSetDataBarsOpen(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsHigh(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsLow(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsClose(const int number,const int series_index,const double value);
   void                 BufferSetDataBars(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
//--- Set data of the (1) Open, (2) High, (3) Low, (4) Close and (5) all candle buffers by a serial number of a created buffer
//--- (0 - the very first created buffer with the CANDLES drawing style, 1,2,N - subsequent ones)
   void                 BufferSetDataCandlesOpen(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesHigh(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesLow(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesClose(const int number,const int series_index,const double value);
   void                 BufferSetDataCandles(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
   
//--- Return buffer color by its serial number of (1) arrows, (2) line, (3) sections, (4) histogram from zero
//--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   color                BufferArrowColor(const int number,const int series_index);
   color                BufferLineColor(const int number,const int series_index);
   color                BufferSectionColor(const int number,const int series_index);
   color                BufferHistogramColor(const int number,const int series_index);
   color                BufferHistogram2Color(const int number,const int series_index);
   color                BufferZigZagColor(const int number,const int series_index);
   color                BufferFillingColor(const int number,const int series_index);
   color                BufferBarsColor(const int number,const int series_index);
   color                BufferCandlesColor(const int number,const int series_index);
   
//--- Return buffer color index by its serial number of (1) arrows, (2) line, (3) sections, (4) histogram from zero
//--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   int                  BufferArrowColorIndex(const int number,const int series_index);
   int                  BufferLineColorIndex(const int number,const int series_index);
   int                  BufferSectionColorIndex(const int number,const int series_index);
   int                  BufferHistogramColorIndex(const int number,const int series_index);
   int                  BufferHistogram2ColorIndex(const int number,const int series_index);
   int                  BufferZigZagColorIndex(const int number,const int series_index);
   int                  BufferFillingColorIndex(const int number,const int series_index);
   int                  BufferBarsColorIndex(const int number,const int series_index);
   int                  BufferCandlesColorIndex(const int number,const int series_index);

//--- Set color values from the passed color array for all indicator buffers within the collection
   void                 BuffersSetColors(const color &array_colors[])                     { this.m_buffers.SetColors(array_colors);                   } 
//--- Set the color index to the color buffer by its serial number of (1) arrows, (2) line, (3) sections, (4) histogram from zero
//--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles
//--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones)
   void                 BufferArrowSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferArrowColorIndex(number,series_index,color_index);       }
   void                 BufferLineSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferLineColorIndex(number,series_index,color_index);        }
   void                 BufferSectionSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferSectionColorIndex(number,series_index,color_index);     }
   void                 BufferHistogramSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferHistogramColorIndex(number,series_index,color_index);   }
   void                 BufferHistogram2SetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferHistogram2ColorIndex(number,series_index,color_index);  }
   void                 BufferZigZagSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferZigZagColorIndex(number,series_index,color_index);      }
   void                 BufferFillingSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferFillingColorIndex(number,series_index,color_index);     }
   void                 BufferBarsSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferBarsColorIndex(number,series_index,color_index);        }
   void                 BufferCandlesSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferCandlesColorIndex(number,series_index,color_index);     }

//--- Clear buffer data by its index in the list in the specified timeseries bar
   void                 BufferClear(const int buffer_list_index,const int series_index)   { this.m_buffers.Clear(buffer_list_index,series_index);     }
//--- Clear data by the timeseries index for the (1) arrow, (2) line, (3) section, (4) zero line histogram,
//--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles
   void                 BufferArrowClear(const int number,const int series_index)         { this.m_buffers.ClearBufferArrow(number,series_index);     }
   void                 BufferLineClear(const int number,const int series_index)          { this.m_buffers.ClearBufferLine(number,series_index);      }
   void                 BufferSectionClear(const int number,const int series_index)       { this.m_buffers.ClearBufferSection(number,series_index);   }
   void                 BufferHistogramClear(const int number,const int series_index)     { this.m_buffers.ClearBufferHistogram(number,series_index); }
   void                 BufferHistogram2Clear(const int number,const int series_index)    { this.m_buffers.ClearBufferHistogram2(number,series_index);}
   void                 BufferZigZagClear(const int number,const int series_index)        { this.m_buffers.ClearBufferZigZag(number,series_index);    }
   void                 BufferFillingClear(const int number,const int series_index)       { this.m_buffers.ClearBufferFilling(number,series_index);   }
   void                 BufferBarsClear(const int number,const int series_index)          { this.m_buffers.ClearBufferBars(number,series_index);      }
   void                 BufferCandlesClear(const int number,const int series_index)       { this.m_buffers.ClearBufferCandles(number,series_index);   }

//--- Display short description of all indicator buffers of the buffer collection
   void                 BuffersPrintShort(void);
   
//--- Set the following for the trading classes:
//--- (1) correct filling policy, (2) filling policy,
//--- (3) correct order expiration type, (4) order expiration type,
//--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date,
//--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts
   void                 TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetMagic(const uint magic,const string symbol_name=NULL);
   void                 TradingSetComment(const string comment,const string symbol_name=NULL);
   void                 TradingSetDeviation(const ulong deviation,const string symbol_name=NULL);
   void                 TradingSetVolume(const double volume=0,const string symbol_name=NULL);
   void                 TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL);
   void                 TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL);
   void                 TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL);
   void                 TradingSetTotalTry(const uchar attempts)                       { this.m_trading.SetTotalTry(attempts);                     }
   
//--- Return the logging level of a trading class symbol trading object
   ENUM_LOG_LEVEL       TradingGetLogLevel(const string symbol_name)                   { return this.m_trading.GetTradeObjLogLevel(symbol_name);   }
   
//--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols
   void                 SetSoundsStandart(const string symbol=NULL)
                          {
                           this.m_trading.SetSoundsStandart(symbol);
                          }
//--- Set the flag of using sounds
   void                 SetUseSounds(const bool flag) { this.m_trading.SetUseSounds(flag);   }
//--- Set a sound for a specified order/position type and symbol. 'mode' specifies an event a sound is set for
//--- (symbol=NULL) for trading objects of all symbols, (symbol!=NULL) for a trading object of a specified symbol
   void                 SetSound(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,const string symbol=NULL)
                          {
                           this.m_trading.SetSound(mode,action,sound,symbol);
                          }
//--- Play a sound by its description
   bool                 PlaySoundByDescription(const string sound_description);

//--- Pass the pointers to all the necessary collections to the trading class and the indicator buffer collection class
   void                 CollectionOnInit(void)
                          {
                           this.m_trading.OnInit(this.GetAccountCurrent(),m_symbols.GetObject(),m_market.GetObject(),m_history.GetObject(),m_events.GetObject());
                           this.m_buffers.OnInit(this.m_time_series.GetObject());
                          }

O método sobrecarregado SeriesBarType() retorna o tipo da barra especificada com base no símbolo e período gráfico
de acordo com o índice definido da série temporal respectiva:

//+------------------------------------------------------------------+
//| Return the bar type of the specified symbol                      |
//| of the specified timeframe by index                              |
//+------------------------------------------------------------------+
ENUM_BAR_BODY_TYPE CEngine::SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.TypeBody() : (ENUM_BAR_BODY_TYPE)WRONG_VALUE);
  }  
//+------------------------------------------------------------------+

A partir da coleção das séries temporais segundo o índice especificado da série temporal obtemos o objeto-barra necessário e retornamos seu tipo (altista/baixista/zero/com corpo zero). Se houver um erro ao obter a barra, será retornado -1.

segundo o tempo:

//+------------------------------------------------------------------+
//| Return the bar type of the specified symbol                      |
//| of the specified timeframe by time                               |
//+------------------------------------------------------------------+
ENUM_BAR_BODY_TYPE CEngine::SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.TypeBody() : (ENUM_BAR_BODY_TYPE)WRONG_VALUE);
  }  
//+------------------------------------------------------------------+

A partir da coleção das séries temporais segundo o tempo especificado obtemos o objeto-barra necessário e retornamos seu tipo (altista/baixista/zero/com corpo zero).
Se houver um erro ao obter a barra, será retornado -1.

Os métodos podem ser facilmente usados nos indicadores para determinar a cor da barra necessária (ou das barras necessárias ao desenhar dados de outras séries temporais).

O método GetBufferByLabel() retorna o ponteiro para o objeto-buffer segundo o nome da sua série gráfica:

CBuffer *GetBufferByLabel(const string plot_label) { return this.m_buffers.GetBufferByLabel(plot_label); }

Retorna o resultado de operação do método de mesmo nome da classe-coleção de buffers discutida acima.

O método GetBufferByTimeframe() retorna o ponteiro para o objeto-buffer de acordo com sua propriedade "timeframe":

CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe) { return this.m_buffers.GetBufferByTimeframe(timeframe);}

Retorna o resultado de operação do método de mesmo nome da classe-coleção de buffers discutida acima.

O método GetBufferByListIndex() retorna um ponteiro para o objeto-buffer de acordo com seu índice na lista-coleção dos objetos-buffers da classe da coleção de buffers:

CBuffer *GetBufferByListIndex(const int index_list) { return this.m_buffers.GetBufferByListIndex(index_list);}

Retorna o resultado de operação do método de mesmo nome da classe-coleção de buffers discutida acima.

O método GetLastBuffer() retorna o ponteiro para o último objeto-buffer criado:

//+------------------------------------------------------------------+
//| Return the last indicator buffer                                 |
//| in the indicator buffer collection list                          |
//+------------------------------------------------------------------+
CBuffer *CEngine::GetLastBuffer(void)
  {
   CArrayObj *list=this.GetListBuffers();
   if(list==NULL)
      return NULL;
   return list.At(list.Total()-1);
  }
//+------------------------------------------------------------------+

Obtemos um ponteiro para a lista-coleção de buffers e retornamos desde a lista o último objeto-buffer.

O método GetBufferCalculate() retorna o resultado de operação do método de mesmo nome da classe-coleção de buffers discutida acima.

CBufferCalculate *GetBufferCalculate(const int number) { return this.m_buffers.GetBufferCalculate(number); }

Os métodos BuffersPropertyPlotsTotal() e BuffersPropertyBuffersTotal() foram renomeados a partir dos que antigamente se chamavam BufferPlotsTotal() e BuffersTotal() respectivamente. Eles retornam os resultados de trabalho dos métodos PropertyPlotsTotal() e PropertyBuffersTotal() da classe-coleção de buffers que consideramos acima:

int BuffersPropertyPlotsTotal(void)   { return this.m_buffers.PropertyPlotsTotal();   }
int BuffersPropertyBuffersTotal(void) { return this.m_buffers.PropertyBuffersTotal(); }

O método BufferCreateCalculate() retorna o resultado de operação do método CreateCalculate() da classe-coleção de buffers discutido acima:

bool BufferCreateCalculate(void) { return this.m_buffers.CreateCalculate(); }

Quanto ao método de criação de buffer calculado, quero esclarecer que, no momento, o seu uso é bastante limitado, uma vez que todos os buffers calculados devem ser gerados após criar todos os buffers plotados necessários. Se criarmos um buffer calculado entre vários buffers plotados, a sequência de definição de matrizes não será atendida para os buffers de indicador, e todos os buffers de indicador plotados criados após o buffer calculado não serão exibidos. Ainda não descobri por que motivo isso acontece, mas "localizar e consertar" faz parte da nossa na próxima tarefa.

O método sobrecarregado BuffersInitPlots() inicializa todos os buffers plotados com ajuda do método InitializePlots() da classe-coleção de buffers discutida acima:

void BuffersInitPlots(const double value,const uchar color_index) { this.m_buffers.InitializePlots(value,color_index); }
void BuffersInitPlots(void)                                       { this.m_buffers.InitializePlots();                  }

O método sobrecarregado BuffersInitCalculates() inicializa todos os buffers calculados com ajuda do método InitializeCalculates() da classe-coleção de buffers discutida acima:

void BuffersInitCalculates(const double value) { this.m_buffers.InitializeCalculates(value); }
void BuffersInitCalculates(void)               { this.m_buffers.InitializeCalculates();      }

O método BufferSetDataCalculate() define os dados para o buffer calculado com ajuda do método SetBufferCalculateValue() da classe-coleção dos objetos-buffers que consideramos acima:

//+------------------------------------------------------------------+
//| Set the calculated buffer data by its serial number              |
//| (0 - the very first buffer, 1,2,N - subsequent ones)             |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCalculate(const int number,const int series_index,const double value)
  {
   this.m_buffers.SetBufferCalculateValue(number,series_index,value);
  }
//+------------------------------------------------------------------+

Os métodos BufferSetDataArrow(), BufferSetDataLine(), BufferSetDataSection(), BufferSetDataHistogram(), BufferSetDataHistogram2(), BufferSetDataZigZag(), BufferSetDataFilling(), BufferSetDataBars() e BufferSetDataCandles() foram alterados, e definem os dados dos buffers correspondentes usando os métodos da coleção de objetos-buffers que discutimos acima:

//+------------------------------------------------------------------+
//| Set arrow buffer data by its serial number                       |
//| (0 - the very first arrow buffer, 1,2,N - subsequent ones)       |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataArrow(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferArrowValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+
//| Set line buffer data by its serial number                        |
//| (0 - the very first line buffer, 1,2,N - subsequent ones)        |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataLine(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferLineValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+
//| Set section buffer data by its serial number                     |
//| (0 - the very first sections buffer, 1,2,N - subsequent ones)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataSection(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferSectionValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+
//| Set histogram buffer data from zero                              |
//| by its serial number                                             |
//| (0 - the very first buffer, 1,2,N - subsequent ones)             |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferHistogramValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Set data of all histogram buffers on two buffers                 |
//| by its serial number                                             |
//| (0 - the very first buffer, 1,2,N - subsequent ones)             |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram2(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferHistogram2Value(number,series_index,value1,value2,color_index,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Set data of all zizag buffers                                    |
//| by its serial number                                             |
//| (0 - the very first zigzag buffer, 1,2,N - subsequent ones)      |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataZigZag(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferZigZagValue(number,series_index,value1,value2,color_index,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Set data of all filling buffers                                  |
//| by its serial number                                             |
//| (0 - the very first filling buffer, 1,2,N - subsequent ones)     |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataFilling(const int number,const int series_index,const double value1,const double value2,bool as_current=false)
  {
   this.m_buffers.SetBufferFillingValue(number,series_index,value1,value2,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Set data of all bar buffers                                      |
//| by its serial number                                             |
//| (0 - the very first bar buffer, 1,2,N - subsequent ones)         |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataBars(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   if(buff==NULL)
      return;
   this.m_buffers.SetBufferBarsValue(number,series_index,open,high,low,close,color_index,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Set data of all candle buffers                                   |
//| by its serial number                                             |
//| (0 - the very first candle buffer, 1,2,N - subsequent ones)      |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCandles(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   if(buff==NULL)
      return;
   this.m_buffers.SetBufferCandlesValue(number,series_index,open,high,low,close,color_index,as_current);
  }
//+------------------------------------------------------------------+

Todos esses métodos são multiperíodo e permitem, numa chamada, transferir corretamente ao buffer de indicador os dados solicitados a partir de outros períodos para o gráfico atual.

Os métodos BufferArrowColor(), BufferLineColor(), BufferSectionColor(), BufferHistogramColor(), BufferHistogram2Color(), BufferZigZagColor(), BufferFillingColor(), BufferBarsColor(), BufferCandlesColor(),
BufferArrowColorIndex(), BufferLineColorIndex(), BufferSectionColorIndex(), BufferHistogramColorIndex(), BufferHistogram2ColorIndex(), BufferZigZagColorIndex(), BufferFillingColorIndex(), BufferBarsColorIndex() e BufferCandlesColorIndex() foram simplesmente renomeados para melhor legibilidade do código.

Por exemplo, antigamente o método BufferArrowColor() era chamado de BufferColorArrow(), o que poderia causar confução ao tentar definir seu propósito, pelo menos me pareceu confuso "cor do buffer de setas". Agora, todos esses métodos primeiro descrevem o tipo de buffer e, depois, o que eles retornam, o que me parece mais visual e certo.

O método BuffersSetColors() define, para todos os buffers de indicador da coleção, os valores de cor a partir da matriz de cores transferida por meio do método SetColors() da classe-coleção dos objetos-buffers que consideramos acima:

void BuffersSetColors(const color &array_colors[]) { this.m_buffers.SetColors(array_colors); }

Agora os métodos que servem para definir o índice da cor para objetos-buffers específicos estão modificados e chamam os respectivos métodos da classe-coleção dos objetos-buffers que discutimos acima:

void BufferArrowSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferArrowColorIndex(number,series_index,color_index);       }
void BufferLineSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferLineColorIndex(number,series_index,color_index);        }
void BufferSectionSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferSectionColorIndex(number,series_index,color_index);     }
void BufferHistogramSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferHistogramColorIndex(number,series_index,color_index);   }
void BufferHistogram2SetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferHistogram2ColorIndex(number,series_index,color_index);  }
void BufferZigZagSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferZigZagColorIndex(number,series_index,color_index);      }
void BufferFillingSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferFillingColorIndex(number,series_index,color_index);     }
void BufferBarsSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferBarsColorIndex(number,series_index,color_index);        }
void BufferCandlesSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferCandlesColorIndex(number,series_index,color_index);     }

O método BufferClear() limpa os dados do buffer com base no seu índice de lista na barra especificada da série temporal com ajuda da chamada do método Clear() da classe-coleção dos objetos-buffers:

void BufferClear(const int buffer_list_index,const int series_index) { this.m_buffers.Clear(buffer_list_index,series_index); }

Os métodos para limpar objetos-buffers específicos limpam os dados de todas as matrizes do buffer em questão com ajuda dos respectivos métodosda classe-coleção dos objetos-buffers discutidos acima:

void BufferArrowClear(const int number,const int series_index)      { this.m_buffers.ClearBufferArrow(number,series_index);     }
void BufferLineClear(const int number,const int series_index)       { this.m_buffers.ClearBufferLine(number,series_index);      }
void BufferSectionClear(const int number,const int series_index)    { this.m_buffers.ClearBufferSection(number,series_index);   }
void BufferHistogramClear(const int number,const int series_index)  { this.m_buffers.ClearBufferHistogram(number,series_index); }
void BufferHistogram2Clear(const int number,const int series_index) { this.m_buffers.ClearBufferHistogram2(number,series_index);}
void BufferZigZagClear(const int number,const int series_index)     { this.m_buffers.ClearBufferZigZag(number,series_index);    }
void BufferFillingClear(const int number,const int series_index)    { this.m_buffers.ClearBufferFilling(number,series_index);   }
void BufferBarsClear(const int number,const int series_index)       { this.m_buffers.ClearBufferBars(number,series_index);      }
void BufferCandlesClear(const int number,const int series_index)    { this.m_buffers.ClearBufferCandles(number,series_index);   }

O método BuffersPrintShort() exibe uma breve descrição de todos os buffers de indicador criados no programa e armazenados na coleção de objetos-buffers:

//+-------------------------------------------------------------------------+
//| Display short description of all indicator buffers within the collection|
//+-------------------------------------------------------------------------+
void CEngine::BuffersPrintShort(void)
  {
//--- Get the pointer to the collection list of buffer objects
   CArrayObj *list=this.GetListBuffers();
   if(list==NULL)
      return;
   int total=list.Total();
//--- In a loop by the number of buffers in the list,
//--- get the next buffer and display its brief description in the journal
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.PrintShort();
     }
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código e é simples: num ciclo percorrendo a lista de objetos-buffers da coleção, obtemos o próximo buffer e imprimimos sua breve descrição no log com ajuda do método PrintShort() da classe do objeto-buffer.

Acima, dissemos que precisávamos passar para a classe-coleção dos objetos-buffers um ponteiro para um objeto da classe-coleção da série temporal.
Já temos o método CollectionOnInit(), sua chamada nos permite passar para a biblioteca todas as coleções necessárias, resta-nos adicionar a ele a transferência do ponteiro necessário, fazemos isso por meio da chamada do método OnInit() considerado anteriormente da classe da coleção de objetos-buffers:

//--- Pass the pointers to all the necessary collections to the trading class and the indicator buffer collection class
   void                 CollectionOnInit(void)
                          {
                           this.m_trading.OnInit(this.GetAccountCurrent(),m_symbols.GetObject(),m_market.GetObject(),m_history.GetObject(),m_events.GetObject());
                           this.m_buffers.OnInit(this.m_time_series.GetObject());
                          }

Este método é chamado em OnInit() de programas que funcionam com base na biblioteca.
Em OnInit() é chamada a função de inicialização da biblioteca OnInitDoEasy(), na qual, junto com outras ações de configuração, também está escrita a chamada desse método.

Assim fica concluída a modificação das classes da biblioteca.
Agora testemos a criação de um indicador multiperíodo.

Teste

Para teste, vamos pegar o indicador do artigo anterior e salvá-lo numa nova pasta \MQL5\Indicators\TestDoEasy\Part45\
com o novo nome TestDoEasyPart45.mq5.

Vamos fazer o seguinte, o indicador terá nas configurações um período gráfico com o qual deve funcionar, e adicionaremos uma seleção de diferentes tipos de plotagem para os buffers que serão desenhados no gráfico.

A linha do indicador do artigo

/*sinput*/   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_CURRENT;            // Mode of used timeframes list

deve ser alterada para uma nova:

/*sinput*/   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list

Aqui alteramos o trabalho do indicador como período gráfico atual para o trabalho com a lista de períodos gráficos. Agora o indicador irá aguardar a lista de períodos gráficos para trabalhar, lista essa localizadas na matriz de armazenamento.
Em seguida, adicionamos à configurações uma variável de entrada em que será definido o período gráfico necessário para trabalhar (por padrão, o atual), e em seguida só tal período selecionado será registrado na matriz no manipulador OnInit(). Assim, especificamos, para o indicador, o período gráfico necessário que deve usar para pegar os dados de exibição no atual.

sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_CURRENT;                  // Used chart period

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable
   InpUsedTFs=TimeframeDescription(InpPeriod);

Após registrar o período gráfico na matriz, biblioteca usará este valor para criar as séries temporais necessárias.

Depois, simplesmente criamos os buffers com todos os tipos de plotagens e definimos, para eles, os sinalizadores de exibição na janela de dados dependendo das respectivas configurações dos parâmetros de entrada e especificamos para eles o período gráfico indicado nas configurações:

//--- indicator buffers mapping
//--- Create all the necessary buffer objects
   engine.BufferCreateArrow();         // 2 arrays
   engine.BufferCreateLine();          // 2 arrays
   engine.BufferCreateSection();       // 2 arrays
   engine.BufferCreateHistogram();     // 2 arrays
   engine.BufferCreateHistogram2();    // 3 arrays
   engine.BufferCreateZigZag();        // 3 arrays
   engine.BufferCreateFilling();       // 2 arrays
   engine.BufferCreateBars();          // 5 arrays
   engine.BufferCreateCandles();       // 5 arrays
   engine.BufferCreateCalculate();     // 1 array

//--- Check the number of buffers specified in the 'properties' block
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Create the color array and set non-default colors to all buffers within the collection
   color array_colors[]={clrDodgerBlue,clrRed,clrGray};
   engine.BuffersSetColors(array_colors);
//--- Set the line width for ZigZag (the sixth drawn buffer)
//--- It has the index of 5 considering that the starting point is zero
   CBuffer *buff_zz=engine.GetBufferByPlot(5);
   if(buff_zz!=NULL)
     {
      buff_zz.SetWidth(2);
     }
     
//--- In a loop by the list of collection buffer objects,
   for(int i=0;i<engine.GetListBuffers().Total();i++)
     {
      //--- get the next buffer
      CBuffer *buff=engine.GetListBuffers().At(i);
      if(buff==NULL)
         continue;
      //--- and set its display in the data window depending on its specified usage
      //--- and also the chart period selected in the settings
      buff.SetShowData(IsUse(buff.Status()));
      buff.SetTimeframe(InpPeriod);
     }

Como sempre usaremos a direção de indexação nos indicadores como nas séries temporais (por causa de como é feito o armazenamento de séries temporais na biblioteca), criaremos mais uma função para transferir todas as matrizes de dados de OnCalculate() à biblioteca. Anteriormente, a função CopyData() fazia isso definindo, para as matrizes, o sinalizador "como nas séries temporais", e, após isso, era retornado seu estado anterior:

//+------------------------------------------------------------------+
//| Copy data from the second OnCalculate() form to the structure    |
//+------------------------------------------------------------------+
void CopyData(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[])
  {
//--- Get the array indexing flags as in the timeseries. If failed,
//--- set the indexing direction or the arrays as in the timeseries
   bool as_series_time=ArrayGetAsSeries(time);
   if(!as_series_time)
      ArraySetAsSeries(time,true);
   bool as_series_open=ArrayGetAsSeries(open);
   if(!as_series_open)
      ArraySetAsSeries(open,true);
   bool as_series_high=ArrayGetAsSeries(high);
   if(!as_series_high)
      ArraySetAsSeries(high,true);
   bool as_series_low=ArrayGetAsSeries(low);
   if(!as_series_low)
      ArraySetAsSeries(low,true);
   bool as_series_close=ArrayGetAsSeries(close);
   if(!as_series_close)
      ArraySetAsSeries(close,true);
   bool as_series_tick_volume=ArrayGetAsSeries(tick_volume);
   if(!as_series_tick_volume)
      ArraySetAsSeries(tick_volume,true);
   bool as_series_volume=ArrayGetAsSeries(volume);
   if(!as_series_volume)
      ArraySetAsSeries(volume,true);
   bool as_series_spread=ArrayGetAsSeries(spread);
   if(!as_series_spread)
      ArraySetAsSeries(spread,true);
//--- Copy the arrays' zero bar to the OnCalculate() SDataCalculate data structure
   rates_data.rates_total=rates_total;
   rates_data.prev_calculated=prev_calculated;
   rates_data.rates.time=time[0];
   rates_data.rates.open=open[0];
   rates_data.rates.high=high[0];
   rates_data.rates.low=low[0];
   rates_data.rates.close=close[0];
   rates_data.rates.tick_volume=tick_volume[0];
   rates_data.rates.real_volume=(#ifdef __MQL5__ volume[0] #else 0 #endif);
   rates_data.rates.spread=(#ifdef __MQL5__ spread[0] #else 0 #endif);
//--- Return the arrays' initial indexing direction
   if(!as_series_time)
      ArraySetAsSeries(time,false);
   if(!as_series_open)
      ArraySetAsSeries(open,false);
   if(!as_series_high)
      ArraySetAsSeries(high,false);
   if(!as_series_low)
      ArraySetAsSeries(low,false);
   if(!as_series_close)
      ArraySetAsSeries(close,false);
   if(!as_series_tick_volume)
      ArraySetAsSeries(tick_volume,false);
   if(!as_series_volume)
      ArraySetAsSeries(volume,false);
   if(!as_series_spread)
      ArraySetAsSeries(spread,false);
  }
//+------------------------------------------------------------------+

A seguir, nós novamente "viramos" estas matrizes para que trabalhassem corretamente com as séries temporais da biblioteca em OnCalculate():

//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Set OnCalculate arrays as timeseries
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);

Para evitar o trabalho desnecessário, no arquivo de funções de serviço da biblioteca criamos mais uma função, na qual as matrizes transferidas a ela irão receber a direção de indexação com nas séries temporais. Abrimos o arquivo \MQL5\Include\DoEasy\Services\DELib.mqh e inserimos nele a nova função:

//+------------------------------------------------------------------+
//| Copy data from the second OnCalculate() form to the structure    |
//| and set the "as timeseries" flag to all arrays                   |
//+------------------------------------------------------------------+
void CopyDataAsSeries(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[])
  {
//--- set the indexing direction or the arrays as in the timeseries
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);
//--- Copy the arrays' zero bar to the OnCalculate() SDataCalculate data structure
   rates_data.rates_total=rates_total;
   rates_data.prev_calculated=prev_calculated;
   rates_data.rates.time=time[0];
   rates_data.rates.open=open[0];
   rates_data.rates.high=high[0];
   rates_data.rates.low=low[0];
   rates_data.rates.close=close[0];
   rates_data.rates.tick_volume=tick_volume[0];
   rates_data.rates.real_volume=(#ifdef __MQL5__ volume[0] #else 0 #endif);
   rates_data.rates.spread=(#ifdef __MQL5__ spread[0] #else 0 #endif);
  }
//+------------------------------------------------------------------+

Esta função faz o mesmo que a anterior, mas não retorna as matrizes à sua indexação original. Agora, em vez de chamar a função CopyData() desde o indicador, chamaremos CopyDataAsSeries(), o que nos evitará de redefinir a indexação de matriz desejada:

//+------------------------------------------------------------------+
//| 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
      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();
     }
//--- Prepare data

//--- Calculate the indicator
   CBar *bar=NULL;         // Bar object for defining the candle direction
   uchar color_index=0;    // Color index to be set for the buffer depending on the candle direction

//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- Clear the current bar of all created buffers
      engine.BufferArrowClear(0,0);
      engine.BufferLineClear(0,0);
      engine.BufferSectionClear(0,0);
      engine.BufferHistogramClear(0,0);
      engine.BufferHistogram2Clear(0,0);
      engine.BufferZigZagClear(0,0);
      engine.BufferFillingClear(0,0);
      engine.BufferBarsClear(0,0);
      engine.BufferCandlesClear(0,0);
      
      //--- Get the timeseries bar corresponding to the loop index time on the chart period specified in the settings
      bar=engine.SeriesGetBar(NULL,InpPeriod,time[i]);
      if(bar==NULL)
         continue;
      //--- Calculate the color index depending on the candle direction on the timeframe specified in the settings
      color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2);
      //--- Check the settings and calculate the arrow buffer
      if(IsUse(BUFFER_STATUS_ARROW))
         engine.BufferSetDataArrow(0,i,bar.Close(),color_index);
      //--- Check the settings and calculate the line buffer
      if(IsUse(BUFFER_STATUS_LINE))
         engine.BufferSetDataLine(0,i,bar.Open(),color_index);
      //--- Check the settings and calculate the section buffer
      if(IsUse(BUFFER_STATUS_SECTION))
         engine.BufferSetDataSection(0,i,bar.Close(),color_index);
      //--- Check the settings and calculate the histogram from zero buffer
      if(IsUse(BUFFER_STATUS_HISTOGRAM))
         engine.BufferSetDataHistogram(0,i,open[i],color_index);
      //--- Check the settings and calculate the 'histogram on two buffers' buffer
      if(IsUse(BUFFER_STATUS_HISTOGRAM2))
         engine.BufferSetDataHistogram2(0,i,bar.Open(),bar.Close(),color_index);
      //--- Check the settings and calculate the zigzag buffer
      if(IsUse(BUFFER_STATUS_ZIGZAG))
        {
         //--- Set the bar's Low value value for the zigzag (for bullish candles)
         double value1=bar.Low();
         double value2=value1;
         //--- If the candle is bearish (color index = 1), set the bar's High value for the zigzag
         if(color_index==1)
           {
            value1=value2=bar.High();
           }
         engine.BufferSetDataZigZag(0,i,value1,value2,color_index);
        }
      //--- Check the settings and calculate the filling buffer
      if(IsUse(BUFFER_STATUS_FILLING))
        {
         //--- Set filling border values for bullish candles
         double value1=bar.High();
         double value2=bar.Low();
         //--- In case of the bearish candle (color index = 1), swap the filling borders to change the color
         if(color_index==1)
           {
            value1=bar.Low();
            value2=bar.High();
           }
         engine.BufferSetDataFilling(0,i,value1,value2,color_index);
        }
      //--- Check the settings and calculate the bar buffer
      if(IsUse(BUFFER_STATUS_BARS))
         engine.BufferSetDataBars(0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index);
      //--- Check the settings and calculate the candle buffer
      if(IsUse(BUFFER_STATUS_CANDLES))
         engine.BufferSetDataCandles(0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Todas as outras ações e lógica do manipulador OnCalculate() são descritas em detalhes nos comentários do código. Podemos ver que trabalhar com o indicador multiperíodo é fácil, uma vez que não precisamos calcular nada, basta gravar os dados no buffer, e a própria biblioteca calculará onde inseri-los e como exibi-los:



O que vem agora?

No próximo artigo, continuaremos a desenvolver uma classe-coleção de buffers de indicador e a organizar a operação dos indicadores em modo multissímbolo.

Abaixo estão anexados todos os arquivos da versão atual da biblioteca e os arquivos do EA de teste. Você pode baixá-los e testar tudo sozinho.
Se você tiver 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.
Depois de criar uma coleção de buffers de indicador e testá-la, tentaremos implementar algumas coisas do 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


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

Arquivos anexados |
MQL5.zip (3739.84 KB)
Cálculo de expressões matemáticas (Parte 1). Analisadores descendentes recursivos Cálculo de expressões matemáticas (Parte 1). Analisadores descendentes recursivos

Neste artigo são abordados os princípios básicos de análise e cálculo de expressões matemáticas, são implementados analisadores descendentes recursivos que funcionam nos modos interpretador e cálculo rápido com base numa árvore de sintaxe pré-construída.

Conjunto de ferramentas para negociação manual rápida: funcionalidade básica Conjunto de ferramentas para negociação manual rápida: funcionalidade básica

Atualmente mais e mais traders estão mudando para sistemas de negociação automáticos que ou requerem configuração inicial ou estão totalmente automatizados. No entanto, ainda existe uma parte considerável de traders que negociam manualmente à moda antiga, Neste artigo, criaremos um conjunto de ferramentas para negociação manual rápida usando teclas de atalho e realizando ações de negociação típicas com um clique.

Discretização da série de preços, componente aleatória e "ruído" Discretização da série de preços, componente aleatória e "ruído"

Estamos acostumados a analisar o mercado usando candles ou barras que "fatiam" a série de preços em intervalos regulares. Mas até que ponto essa forma de discretização distorce a estrutura real dos movimentos de mercado? Discretizar um sinal de áudio em intervalos regulares é uma solução aceitável, porque o sinal de áudio é uma função que muda com o tempo. O sinal em si é uma amplitude que depende do tempo e essa propriedade nele é fundamental.

Cálculo de expressões matemáticas (Parte 2). Analisadores Pratt e estação de triagem Cálculo de expressões matemáticas (Parte 2). Analisadores Pratt e estação de triagem

O artigo aborda os princípios de análise e cálculo de expressões matemáticas com ajuda de analisadores baseados na precedência de operadores. Implementa analisadores Pratt e estação de triagem, geração de bytecode cálculos com base nele. Mostra o uso de indicadores como funções em expressões e como aplicá-los ao configurar sinais de negociação em EAs.