Trabajando con las series temporales en la biblioteca DoEasy (Parte 45): Búferes de indicador de periodo múltiple

Artyom Trishkin | 23 octubre, 2020

Contenido


Concepto

En el artículo anterior, procedimos a crear la clase de colección de los búferes de indicador. Hoy continuaremos desarrollando esta clase y diseñaremos en una metodología para crear búferes de indicador y acceder a sus datos. Mostramos los valores del búfer en el gráfico del símbolo/periodo actual, dependiendo del periodo (marco temporal de datos) establecido en el objeto de búfer. Si hemos asignado al búfer un valor de marco temporal que se corresponde con el gráfico actual, todos los datos de este búfer se mostrarán en el gráfico de la forma correcta: por ejemplo, si el gráfico actual tiene un periodo de datos M15, y el periodo del gráfico del objeto de búfer se ha establecido como H1, los datos de este búfer se mostrarán en cada barra del actual gráfico de M15 cuyo tiempo se encuentre dentro de la barra del gráfico H1. En el presente ejemplo, las cuatro barras del gráfico actual están rellenadas con el mismo valor, que se corresponde con el valor solicitado de la barra con el periodo de gráfico H1.

Estos cambios nos permitirán crear indicadores de múltiples períodos cómodamente. Para ello, solo necesitaremos definir el valor del marco temporal para el objeto de búfer y la biblioteca se encargará del resto. Además, simplificaremos paulatinamente el proceso de creación de los búferes de indicador, dejando solo una línea de código en la que crearemos el objeto de búfer de indicador con las propiedades deseadas.
El acceso a cualquier búfer indicador se implementará a través del número del búfer con un tipo de gráfico determinado.

El concepto detrás del "número de búfer" será el siguiente: si se crea en primer lugar un búfer de flechas, luego un búfer de línea y luego un búfer de flechas nuevamente, los números de los objetos de búfer seguirán su secuencia de creación: cada estilo de dibujado tendrá su propia secuencia de números. En el ejemplo anterior, los números serían: el primer búfer de flechas creado será el número 0, el segundo búfer de flechas será el número 1 y el búfer de línea creado inmediatamente después del primer búfer de flechas será el número 0, porque este es el primero de los búferes de estilo "línea", aunque se haya creado como el segundo ordinalmente.

Vamos a ilustrar este concepto de los números de los objetos de búfer:

  1. Creación del búfer "Flechas". Su número = 0,
  2. Creación del búfer "Línea". Su número = 0,
  3. Creación del búfer "Flechas". Su número = 1,
  4. Creación del búfer "Zigzag". Su número = 0,
  5. Creación del búfer "Zigzag". Su número = 1,
  6. Creación del búfer "Flechas". Su número = 2,
  7. Creación del búfer "Flechas". Su número = 3,
  8. Creación del búfer "Línea". Su número = 1,
  9. Creación del búfer "Vela". Su número = 0,
  10. Creación del búfer "Flechas". Su número = 4,
Aparte de acceder al búfer según su número ordinal, podremos acceder al mismo por su nombre de serie gráfica, su serie temporal, su índice en la ventana de datos y su índice en la lista de colección. Asimismo, podemos obtener un objeto de búfer que sea creado y añadido a la lista de colección en último lugar, lo que facilitará el establecimiento de parámetros al objeto de búfer recién creado: creamos un objeto, lo obtenemos y le asignamos las propiedades adicionales.

Mejorando las clases para trabajar con los búferes de indicador en el modo de periodo múltiple

A partir del build 2430, en MetaTrader 5 se ha aumentado el número de símbolos que pueden encontrarse en la ventana "Observación de mercado". Ahora son 5000, en lugar de los mil que había hasta el build 2430. Para trabajar con la lista de símbolos, vamos a introducir una macrosustitución en el archivo 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                                                     |
//+------------------------------------------------------------------+

En el archivo \MQL5\Include\DoEasy\Collections\SymbolsCollection.mqh, cambiamos el tamaño reservado para la matriz de símbolos de 1000 al valor de la nueva macrosustitución en el método para establecer la lista 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);

En el método de creación de la lista de símbolos, cambiamos en la condición del ciclo el valor de control de 1000 a la nueva macrosustitución:

//+------------------------------------------------------------------+
//| 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++)
     {

De la misma forma hacemos en el método de registro de todos los símbolos y marcos temporales utilizados en el archivo \MQL5\Include\DoEasy\Engine.mqh, cambiamos de 1000 a la macrosustitución:

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


En las propiedades del objeto de búfer, tenemos un parámetro que establece el índice de la matriz que será designada por el siguiente búfer de indicador. Resulta muy cómodo que, al crear un búfer y añadirle el número necesario de matrices, podamos asignar en este directamente el índice de la siguiente matriz que será la primera matriz (básica) para el siguiente búfer. En cambio, para el índice del búfer de indicador (construcción gráfica), este valor se usa al asignarle al búfer los valores necesarios mediante las funciones PlotIndexSetDouble(), PlotIndexSetInteger() y PlotIndexSetString(): nosotros no disponemos de la propiedad en los parámetros del objeto de búfer. Para calcular cómodamente el índice de la siguiente construcción gráfica, añadiremos una nueva propiedad a la enumeración de propiedades de tipo entero del objeto de búfer en el archivo Defines.mqh. En este caso, además, renombraremos la propiedad que guarda el índice de la matriz básica del siguiente objeto de búfer:

//+------------------------------------------------------------------+
//| 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 el número de propiedades de tipo entero del objeto de búfer de 19 a 20.

Añadimos a la enumeración de posibles criterios de clasificación de los búferes la clasificación según la propiedad añadida y renombramos la constante de clasificación según el índice de la matriz para asignar la siguiente matriz básica del búfer:

//+------------------------------------------------------------------+
//| 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
  };
//+------------------------------------------------------------------+

Ahora, al crear cualquier objeto de búfer, anotaremos en los parámetros del búfer actual los índices de la matriz básica y el índice de la siguiente serie gráfica. De esta forma, para saber qué índices anotar en un objeto de búfer nuevamente creado, bastará con recurrir al anterior objeto de búfer, es decir, el último creado, y obtener los valores necesarios.
Esta es la forma mejor y más sencilla de hacerlo, después de todo, podemos asignar a cada búfer de una a cinco matrices, por lo que no necesitaremos recalcular todos los valores de índice necesarios de las matrices para el nuevo búfer de indicador: todo se ha especificado ya directamente en las propiedades del último búfer de objeto creado, y estos valores se recalculan para cada búfer de indicador recién añadido dependiendo del número de matrices que usa.

Ahora, añadimos al archivo \MQL5\Include\DoEasy\Datas.mqh el índice del nuevo mensaje para la propiedad del búfer recién creada y renombramos la constante del índice del mensaje sobre la propiedad de la siguiente matriz para el búfer 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

Y añadimos el texto del mensaje que le corresponde al índice nuevamente añadido:

   {"Индекс базового буфера данных","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)"},

Como hemos cambiado el nombre de la constante que guarda el índice de la siguiente matriz básica del objeto de búfer, en todos los archivos de las clases de los objetos de búfer (BufferArrow.mqh, BufferBars.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferLine.mqh, BufferArrow.mqh, BufferSection.mqh, BufferZigZag.mqh) cambiamos las entradas de la línea BUFFER_PROP_INDEX_NEXT por BUFFER_PROP_INDEX_NEXT_BASE, y, ya de paso, mejoramos el método virtual incluido en el diario de descripción breve del objeto de búfer.
Vamos a analizar los cambios de este método usando como ejemplo la clase CBufferArrow en el archivo \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())
     );
  }
//+------------------------------------------------------------------+

El texto del mensaje se compone de forma que sea más informativo. Por ejemplo, para la clase del búfer de flechas, su descripción breve, mostrada con este método y con los cambios introducidos, tendrá el aspecto siguiente:

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

Aquí:

De esta forma, si creamos todos los objetos de búfer posibles y mostramos sus descripciones breves en el diario, veremos las siguientes entradas en el mismo:

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

Así queda mucho más claro, ya que resulta posible ver a qué búfer de indicador se asignan las matrices con qué índices (B y C), y también qué índices de las series gráficas se asignan a este búfer (P).
Asimismo, podemos ver que el índice de la serie gráfica asignado al búfer calculado (que implementaremos hoy) es exactamente igual que el búfer anterior. En realidad, el búfer calculado no tiene series gráficas ni matriz de búfer de color, y, ahora mismo, aquí se muestra la información sobre la depuración: en P8 y C27 se muestran qué índices de la serie gráfica y la matriz de búfer de color deberá tener el siguiente búfer creado después del calculado.
Después de finalizar el trabajo de creación de los búferes de indicador, cambiamos esta información, por ejemplo a PN, CN o PX, CX, lo que indicará que el búfer de cálculo no tiene serie gráfica ni matriz de búfer de color, o podemos eliminar por completo estas designaciones, dejando solo B, el índice de la matriz asignada al búfer.

Ya tenemos los objetos herederos del objeto de búfer abstracto básico encargados de precisar el tipo de búfer de indicador creado según su tipo de dibujado. Sin embargo, nos falta otro objeto de búfer, el de cálculo, que servirá para ejecutar aquellos cálculos en el indicador que requieran una matriz, pero sin dibujado en el gráfico. Esta matriz también es un búfer de indicador, cuya distribución corre a cargo de un subsistema del terminal al mismo nivel con las matrices de búfer de indicador dibujadas.
Vamos a crear un objeto heredero de este tipo con el nombre de clase CBufferCalculate. En la carpeta \MQL5\Include\DoEasy\Objects\Indicators\, creamos el nuevo archivo BufferCalculate.mqh y lo rellenamos directamente con el contenido necesario:

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

Ya analizamos la composición y el principio de funcionamiento de las clases herederas del búfer abstracto básico en el artículo 43. Aquí, la composición y el principio de funcionamiento no se distiguen de los anteriormente descritos.

Ahora que ya tenemos el conjunto completo de clases herederas del objeto de búfer básico (CBuffer), mejoraremos también este.

A veces, resulta necesario llevar a cabo algunas acciones una sola vez con el búfer de indicador según una solicitud o condición. Por ejemplo, limpiar o actualizar los datos del búfer pulsando un botón. Para ello, introduciremos una bandera que indique que la acción necesaria todavía no se ha realizado.
En la sección privada de la clase, declaramos la variable de miembro de clase para guardar dicha bandera, mientras que en la sección pública declaramos dos métodos, uno para establecer y otro para obtener el valor de esta variable:

//+------------------------------------------------------------------+
//| 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){;}

Si nosotros, por ejemplo, al pulsar un botón, desactivamos la representación del búfer, deberemos establecer de forma adicional a la eliminación de todos los datos de la matriz el estilo de dibujado de este búfer como DRAW_NONE. Para ello, introducimos en la sección pública de la clase un método adicional que asigna el tipo de dibujado al objeto de búfer:

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);

Lo implementamos fuera del cuerpo de la clase:

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

Aquí: si el tipo de búfer es "de cálculo", entonces no tendrá dibujado, por lo que salimos del método.
Establecemos en la propiedad del objeto el tipo de dibujado transmitido al método y asignamos dicho estilo al búfer.

Como hemos sustituido la constante BUFFER_PROP_INDEX_NEXT por BUFFER_PROP_INDEX_NEXT_BASE, también renombraremos el método correspondiente IndexNextBuffer() como IndexNextBaseBuffer(), añadiendo después otro método que retorne el índice del siguiente búfer de dibujado:

//--- 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);   }

En último lugar, añadimos al final del cuerpo de la clase cuatro métodos, dos métodos para inicializar las matrices del objeto y dos métodos para su rellenado:

//--- 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[]);
   
  };
//+------------------------------------------------------------------+

En esencia, son dos métodos sobrecargados que adoptan diferentes conjuntos de parámetros para situaciones distintas.

El método paramétrico de inicialización de todos los búferes del objeto adopta como parámetros el valor con el que serán inicializadas todas las matrices del objeto de búfer designadas por los búferes de indicador, y el valor del índice de color con el que se inicializará la matriz designada por el búfer de color en el objeto de búfer.
El método sin parámetros de entrada inicializa todas las matrices del objeto de búfer con un valor establecido como "vacío" en las propiedades del objeto de búfer, mientras que inicializa la matriz de color con un cero, el primero de los colores asignados al objeto de búfer (o el único, si solo hay un color).

Vamos a implementar estos métodos fuera del cuerpo de la clase:

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

Primero, en un ciclo por el número de matrices del objeto de búfer designados por los búferes de indicador, inicializamos cada siguiente matriz con el valor transmitido al método en el primer método, y con el valor establecido para el búfer, en el segundo. A continuación, con la condición de que este objeto de búfer no sea un búfer con el estilo de dibujado "Rellenado de color entre dos niveles", y de que el objeto de búfer no sea búfer de cálculo (estos búferes no tienen búfer de color), inicializamos la matriz de color con el valor transmitido al método en el primer método, y cero, en el 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]);
     }
  }
//+------------------------------------------------------------------+

Estos dos métodos han sido detalladamente comentados en el listado del código. Luego transmitimos al primer método el puntero al objeto de serie temporal cuyos datos debe rellenar el búfer de indicador; asimismo, transmitimos al segundo método la matriz double cuyos datos debe rellenar el búfer de indicador.

Introducimos las correcciones en el constructor cerrado de la clase:

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

Los cambios se relacionan con la asignación del valor true a la variable m_act_state_trigger, lo que significa que la acción con el búfer ya ha sido realizada al crear este (en nuestro caso, por ejemplo, al pulsar el botón, comprobaremos si "se ha realizado la acción necesaria con el objeto de búfer", y si no ha sido así, esta deberá realizarse. La bandera establecida no nos dejaría realizar una acción falsa si la bandera hubiera sido reseteada inicialmente).
Los otros cambios que hemos introducido se relacionan con el hecho de que ahora tengamos un búfer de cálculo que, por una parte, no contiene una matriz de color y, por otra, necesita que definamos sus propiedades de serie gráfica. De acuerdo con ello, al calcular los índices de las matrices, ahora se considera el tipo de búfer del objeto (y si se trata de un búfer de cálculo, se harán los ajustes correspondientes en este cálculo), y antes de asignar los valores de la serie gráfica a los búferes de indicador, comprobaremos el tipo de objeto de búfer (con la particularidad de que si se trata de un búfer de cálculo, saldremos del método, ya que este búfer no tiene series gráficas).

Para los métodos de establecimiento de las propiedades de la serie gráfica del objeto de búfer, introduciremos la comprobación del tipo de búfer, y si el búfer es de cálculo, saldremos del 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);
  }
//+------------------------------------------------------------------+

Estas comprobaciones han sido introducidas en todos los métodos que no son necesarios para el búfer de cálculo: no vamos a analizarlos aquí.

A veces, al calcular el índice de la barra a la que debemos asignar un valor para el búfer de indicador, el valor calculado resulta inferior a cero. Esto sucede al calcular el índice de la barra en el gráfico actual, al mostrar en este los datos de un periodo del gráfico que no es igual al actual. Para no introducir comprobaciones adicionales sobre la corrección del índice calculado al realizar los cálculos, resulta más sencillo (en los métodos encargados de registrar los valores en las matrices según el índice establecido) comprobar si el índice transmitido tiene un valor inferior a cero, y luego salir del método en esta situación:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

En el listado de la clase, también se han realizado algunas mejoras y cambios de tipo externo que no tiene especial sentido describir aquí, dado que estos no influyen en el rendimiento del código: todos los cambios efectuados se muestran en los archivos adjuntos al final del artículo.

Ahora, vamos a completar la clase de colección de búferes de indicador CBuffersCollection en el archivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh.
Como ahora vamos a implementar la posibilidad de trabajar con periodos del gráfico distintos al actual, la clase-concepto deberá tener acceso a la clase de colección de series temporales. De la clase de series temporales obtendremos todas las series temporales necesarias para calcular la construcción de los búferes de indicador en el gráfico actual según los datos de los gráficos de otros periodos.
Para que la clase de colección de búferes tenga acceso a la clase de colección de series temporales, actuaremos de una forma muy simple: vamos a transmitir a la colección de búferes de indicador el puntero a la colección de series temporales. Precisamente según este puntero, podremos seleccionar de la clase los datos y métodos que necesitemos para su obtención.

Exactamente de la misma forma hemos modificado la denominación de la constante en todos los archivos de clases de los objetos de búfer; aquí ya se han sustituido todas las entradas de la línea "BUFFER_PROP_INDEX_PLOT" por la nueva constante BUFFER_PROP_INDEX_NEXT_PLOT, mientras que el acceso al método IndexNextBuffer() ha sido sustituido por su versión renombrada IndexNextBaseBuffer().

Vamos a incluir en el archivo de clase de la colección de búferes de indicador el archivo de clase y el archivo de clase de la colección de series temporales, definiendo a continuación la variable privada de miembro de clase para guardar el puntero al objeto de clase de colección de series temporales:

//+------------------------------------------------------------------+
//| 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

Como hemos añadido muchos métodos diferentes (que en su mayoría ejecutan funciones idénticas para diferentes objetos de búfer), para no describir cada método (tanto más que son descritos en los comentarios), vamos a declarar todos los nuevos métodos en el cuerpo de la clase, y ya después, de forma selectiva, analizaremos su construcción y principios de funcionamiento:

//+------------------------------------------------------------------+
//| 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;  }
  };
//+------------------------------------------------------------------+

Bien. Vamos a analizar el cometido de los nuevamente añadidos. Debajo de la descripción con la misión de los métodos, analizaremos su implementación, que, al igual que la mayoría de los métodos de las clases de las bibliotecas, se ha sacado del cuerpo de la clase.

GetIndexLastPlot() retorna el índice de la serie gráfica del último objeto de búfer creado:

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

El método es bastante específico, y ha sido diseñado en mayor medida para el método de creación de un nuevo búfer, ya que el valor retornado por él está vinculado al tamaño de la lista de búferes (si la lista está vacía, se retornará cero, si no, -1). No obstante, si en lo sucesivo necesitamos este método para otras tareas, podremos modificarlo ligeramente. Si no lo necesitamos más que para una aplicación hoy, haremos el método privado.

El método privado GetBarsData() sirve para obtener los datos de otros métodos que no son propios del objeto de búfer de series temporales; asimismo, retorna el número de barras del marco temporal actual que entran en una barra del periodo del gráfico del objeto de búfer:

//+------------------------------------------------------------------+
//| 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 se usará ampliamente para calcular las barras del gráfico actual en las que se deba registrar el valor del búfer de indicador con los datos de un periodo del gráfico que se distinga del marco temporal actual.

Los métodos PropertyPlotsTotal() y PropertyBuffersTotal() son los métodos renombrados (para conseguir denominaciones más comprensibles) PlotsTotal() y BuffersTotal(), que retornan los valores correctos para indicar estos como propiedades del programa de indicador en #property indicator_plots y #property indicator_buffers, respectivamente.
El método PropertyBuffersTotal() solo ha sido renombrado, y en este también se han sustituido las denominaciones de las constantes por las renombradas (BUFFER_PROP_INDEX_NEXT por BUFFER_PROP_INDEX_NEXT_BASE).
El método PropertyPlotsTotal() ha sido nuevamente reescrito, debido a la aparición de un nuevo búfer (de cálculo), y ahora los datos se deben tomar de una forma distinta a la utilizada 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);
  }
//+------------------------------------------------------------------+

Obtenemos la lista de los objetos de búfer recién dibujados y retornamos su tamaño. Si la lista está vacía, retornamos -1.

CreateCalculate() sirve para crear un objeto de búfer de cálculo (no dibujado).
El método ha sido implementado en el cuerpo de la clase, y retorna el resultado del funcionamiento del método para crear un nuevo búfer, que ya vimos en el artículo anterior, al que se transmite el estado del búfer de cálculo:

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

El método GetBufferByLabel() retorna el puntero a un objeto de búfer según su nombre de serie 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);
  }
//+------------------------------------------------------------------+

Obtenemos una lista de objetos de búfer cuya propiedad "Nombre de la serie gráfica" se corresponde con el valor transmitido al método, y retornamos el puntero al último objeto de la lista. Si la lista está vacía, retornamos NULL. Si tenemos varios objetos de búfer creados con el mismo nombre de serie gráfica, el método retornará el último objeto de búfer creado con ese nombre.

El método GetBufferByTimeframe() retorna el puntero a un objeto de búfer según el valor del marco temporal que se le ha asignado:

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

Obtenemos una lista de objetos de búfer cuya propiedad "Marco temporal" se corresponde con el valor transmitido al método, y retornamos el puntero al último objeto de la lista. Si la lista está vacía, retornamos NULL. Si tenemos varios objetos de búfer creados con el mismo marco temporal, el método retornará el último objeto de búfer creado con ese periodo del gráfico.

El método GetBufferByListIndex() retorna el puntero a un objeto de búfer según su índice en la lista de colección:

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

Simplemente retornamos el puntero al último objeto en la lista de colección de búferes. Si la lista está vacía, el método At() retornará NULL.


El método GetBufferCalculate() retorna el puntero a un objeto de búfer según su número ordinal (en orden de creación de los búferes de cálculo):

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

Obtenemos de la lista de colección de objetos de búfer solo aquellos búferes cuyo tipo sea "Búfer de cálculo", y retornamos de la lista obtenida el puntero al objeto según el índice transmitido al método. Si la lista obtenida está vacía, o el índice del objeto necesario transmitido al método se sale de los límites de la lista obtenida, el método At() retornará NULL.

Los métodos InitializePlots() sobrecargados sirven para inicializar todos los búferes de dibujado en la colección:

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

El método inicializa todos los búferes de dibujado de la colección con el valor de búfer y el valor de índice de color transmitidos en los parámetros.

En primer lugar, se seleccionan en la lista solo los búferes de dibujado; a continuación, en un ciclo por la lista obtenida, obtenemos el siguiente objeto de búfer e inicializamos todas sus matrices con la ayuda del método de la clase del objeto de búfer InitializeAll(), transfiriendo a él los valores de entrada del 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();
     }
  }  
//+------------------------------------------------------------------+

El método inicializa todos los búferes de dibujado de la colección con el valor del búfer establecido para cada objeto de búfer y un valor del índice de color igual a cero.

En primer lugar, se seleccionan en la lista solo los búferes de dibujado; a continuación, en un ciclo por la lista obtenida, obtenemos el siguiente objeto de búfer e inicializamos todas sus matrices con la ayuda del método de la clase del objeto de búfer InitializeAll() sin parámetros.

Los métodos InitializeCalculates() sobrecargados sirven para inicializar todos los búferes de cálculo en la colección:

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

El método inicializa todos los búferes de cálculo de la colección con el valor de búfer transmitido en los parámetros.

En primer lugar, se seleccionan en la lista solo los búferes de cálculo; a continuación, en un ciclo por la lista obtenida, obtenemos el siguiente objeto de búfer e inicializamos su matriz con la ayuda del método de la clase del objeto de búfer InitializeAll(), transfiriendo a él el valor de entrada del 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();
     }
  }
//+------------------------------------------------------------------+

El método inicializa todos los búferes de cálculo de la colección con el valor de inicialización de búfer establecido para él.

En primer lugar, se seleccionan en la lista solo los búferes de cálculo; a continuación, en un ciclo por la lista obtenida, obtenemos el siguiente objeto de búfer e inicializamos su matriz con la ayuda del método de la clase del objeto de búfer InitializeAll() sin parámetros.

El método SetColors() asigna a todos los búferes de dibujado de la colección los valores de color de la matriz de colores.

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

El método asigna a todos los búferes de dibujado de la colección los valores de color transmitidos en los parámetros de la matriz.

En primer lugar, se seleccionan en la lista solo los búferes de dibujado; a continuación, en un ciclo por la lista obtenida, obtenemos el siguiente objeto de búfer y establecemos para él el conjunto de colores con la ayuda del método de la clase del objeto de búfer SetColors(), transfiriendo a él la matriz transmitida en los parámetros de entrada del método.

El método Clear() limpia los datos del búfer según su índice en la lista de colección en la barra indicada de la serie 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);
  }
//+------------------------------------------------------------------+

En primer lugar, obtenemos el puntero al objeto de búfer según su índice en la lista utilizando el método GetBufferByListIndex(), que hemos analizado anteriormente. A continuación, con la ayuda del método del objeto de búfer ClearData() obtenido, limpiamos todas sus matrices.

Para poder calcular los datos necesarios de los índices de las barras, al dibujar las líneas de los búferes de indicador en el gráfico actual (utilizando en este caso los datos de otros marcos temporales), necesitaremos transmitir a la clase de colección de los búferes el puntero a la lista de colección de todas las series temporales utilizadas que se encuentren en la clase de colección de las series temporales.
Resulta más sencillo hacer esto durante la inicialización del programa, cuando ya se hayan creado todos los objetos de las clases de la biblioteca.
El método de clase OnInit() asigna a la variable m_timeseries el puntero (transmitido previamente a él) a la clase de colección de las series temporales:

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

A continuación, llamaremos a este método en la función de inicialización de la biblioteca en los programas. Ya lo tenemos todo listo para ello hace mucho, por lo que solo tenemos que añadir un poco más abajo la llamada a este método en el método necesario de la clase CEngine.

En la clase, ya hemos añadido los métodos encargados de establecer los valores mediante los búferes de indicador (flechas, líneas, etcétera), para establecer el índice de color para estos búferes y limpiar todas las matrices de estos búferes. En total, hemos añadido 28 métodos. No vamos a analizar cada uno por separado aquí: todos los métodos ya se encuentran en los archivos adjuntos al final del artículo, y son totalmente idénticos unos a otros en cuanto a su lógica. La única diferencia reside en el número de matrices en los búferes de diferente tipo. Por eso, aquí solo analizaremos los métodos para el búfer de flechas.

Método que asigna al búfer de flechas un valor según el índice de la serie temporal:

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

La lógica del método se describe en los comentarios al código. Vamos a aclarar algunos puntos.

Al método se transmite el número del objeto del búfer de flechas. El número indica el número ordinal de todos los búferes de flechas creados. El primer búfer de flechas que hemos creado en nuestro programa tendrá el número 0. El segundo, el número 1, el tercero, el número 2, etcétera. No demos confundir los números de los objetos de búfer con los índices de los objetos de búfer en la lista de colección. Los índices de los búferes nuevamente añadidos siempre van en orden ascendente, y no dependen del tipo de objeto de búfer. Los número de los objetos de búfer, en nuestro caso, dependen del tipo de búfer. Ya analizamos este concepto al inicio del artículo.

Al método se transmite el índice de la barra de la serie temporal al que debemos añadir el valor que también se transmite como siguiente parámetro, el índice de color con el que se coloreará la línea del búfer de indicador según el índice establecido de la serie temporal, así como el parámetro adicional "como para la serie temporal actual".

Los métodos están construidos de tal forma que si un objeto de búfer tiene un valor de propiedad "marco temporal" distinto al periodo del gráfico actual, entonces, independientemente de qué índice de la serie temporal se haya transmitido al método, la línea del indicador se dibujará en el gráfico actual teniendo en cuenta los datos del marco temporal del objeto de búfer. Es decir, el método representará correctamente los datos de otros periodos del gráfico en el actual. Esto con la condición de que la bandera as_current tenga el valor false (por defecto). Si establecemos esta bandera como true, el método trabajará solo con los datos del marco temporal actual, independientemente de si el objeto de búfer tiene establecido otro periodo del gráfico en sus propiedades.

Método que asigna al objeto de búfer de flechas según su número ordinal, el índice de color en la matriz de búfer de color:

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

Aquí, todo es muy sencillo: obtenemos el objeto de búfer de flechas según su número y establecemos en el índice de serie temporal transmitido el índice de color indicado.

Método que limpia los datos según el índice de la serie temporal para el búfer de flechas según su 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());
  }
//+------------------------------------------------------------------+

Aquí: obtenemos el objeto de búfer de flechas según su número y establecemos en el índice de serie temporal transmitido un valor "vacío" establecido para el objeto de búfer.

Todos los demás métodos para trabajar con otros tipos de objetos de búfer no tienen diferencias esenciales respecto a los analizados anteriormente, salvo el número de matrices y el tipo del objeto de búfer. El lector podrá estudiarlos por su propia cuenta.

Esto es todo por hoy en cuanto a la clase de colección de los objetos de búfer.
Podrá ver el listado completo con todos los cambios en los archivos adjuntos al artículo.

Ahora, vamos a completar y corregir la clase del objeto principal de la biblioteca CEngine en el archivo \MQL5\Include\DoEasy\Engine.mqh. Actuaremos de la misma forma que al analizar las mejoras de la clase de colección de los búferes: primero veremos las adiciones introducidas y los cambios en el cuerpo de la clase, y después estudiaremos cada uno de los métodos.
No vamos a mostrar el listado completo del cuerpo de la clase porque es bastante voluminoso, solo representaremos los lugares con cambios y adiciones:

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

El método sobrecargado SeriesBarType() retorna el tipo de la barra indicada en el símbolo y el periodo del gráfico indicados
según el índice establecido de la serie temporal correspondiente:

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

Obtenemos el objeto de barra necesario de la colección de series temporales según el índice establecido de la serie temporal, y luego retornamos su tipo (alcista/bajista/cero/con cuerpo cero). Si ocurre un error al obtener la barra, se retornará -1.

según el tiempo:

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

Obtenemos el objeto de barra necesario de la colección de series temporales según el tiempo establecido, y luego retornamos su tipo (alcista/bajista/cero/con cuerpo cero). Si ocurre un error al obtener la barra, se retornará -1.

Resulta cómodo utilizar los métodos en los indicadores para determinar el color de la barra necesaria (o las barras necesarias, al dibujar los datos de otras series temporales).

El método GetBufferByLabel() retorna el puntero a un objeto de búfer según su denominación de serie gráfica:

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

Retorna el resultado del funcionamiento del método homónimo de la clase de colección de búferes que hemos analizado anteriormente.

El método GetBufferByTimeframe() retorna el puntero a un objeto de búfer según su propiedad "marco temporal":

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

Retorna el resultado del funcionamiento del método homónimo de la clase de colección de búferes que hemos analizado anteriormente.

El método GetBufferByListIndex() retorna el puntero a un objeto de búfer según su índice en la lista de colección de objetos de búfer de la clase de colección de búferes:

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

Retorna el resultado del funcionamiento del método homónimo de la clase de colección de búferes que hemos analizado anteriormente.

El método GetLastBuffer() retorna el puntero al objeto de búfer creado en último lugar:

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

Obtenemos el puntero a la lista de colección de búferes y retornamos de la lista el último objeto de búfer.

El método GetBufferCalculate() retorna el resultado del funcionamiento del método homónimo de la clase de colección de búferes que hemos analizado anteriormente.

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

Los métodos BuffersPropertyPlotsTotal() y BuffersPropertyBuffersTotal() han sido renombrados a partir de los anteriormente nombrados BufferPlotsTotal() y BuffersTotal(), respectivamente, y retornan los resultados del funcionamiento de los métodos PropertyPlotsTotal() y PropertyBuffersTotal() de la clase de colección de búferes que hemos analizado anteriormente:

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

El método BufferCreateCalculate() retorna el resultado del funcionamiento del método CreateCalculate() de la clase de colección de búferes que hemos analizado con anterioridad:

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

Respecto al método de creación del búfer de cálculo, nos gustaría matizar que, en estos momentos, existe una limitación para su uso: todos los búferes de cálculo se deben crear después de crear todos los búferes de dibujado necesarios. Si creamos un búfer de cálculo entre varios dibujados, esto perturbará la secuencia de establecimiento de matrices para los búferes de indicador, y ninguno de los búferes de indicador dibujados creados después del búfer de cálculo se representarán. Por el momento, no hemos podido establecer el motivo de este comportamiento, pero encontrar y corregir este punto consta entre nuestras próximas tareas.

El método sobrecargado BuffersInitPlots() inicializa todos los búferes de dibujado con la ayuda del método InitializePlots() de la clase de colección de búferes que hemos analizado anteriormente:

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

El método sobrecargado BuffersInitCalculates() inicializa todos los búferes de cálculo con la ayuda del método InitializeCalculates() de la clase de colección de búferes que hemos analizado anteriormente:

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

El método sobrecargado BufferSetDataCalculate() establece los datos para el búfer de cálculo con la ayuda del método SetBufferCalculateValue() de la clase de colección de objetos de búfer que hemos analizado anteriormente:

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

Los métodos BufferSetDataArrow(), BufferSetDataLine(), BufferSetDataSection(), BufferSetDataHistogram(), BufferSetDataHistogram2(), BufferSetDataZigZag(), BufferSetDataFilling(), BufferSetDataBars() y BufferSetDataCandles() han sido modificados, y establecen los datos de los búferes correspondientes con la ayuda de los métodos de clase de la colección de los objetos de búfer que hemos analizado anteriormente:

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

Tdos estos métodos son periodo múltiple, y permiten en solo una llamada mostrar correctamente en el búfer de indicador los datos solicitados de otros periodos en el gráfico actual.

Los métodos BufferArrowColor(), BufferLineColor(), BufferSectionColor(), BufferHistogramColor(), BufferHistogram2Color(), BufferZigZagColor(), BufferFillingColor(), BufferBarsColor(), BufferCandlesColor(),
BufferArrowColorIndex(), BufferLineColorIndex(), BufferSectionColorIndex(), BufferHistogramColorIndex(), BufferHistogram2ColorIndex(), BufferZigZagColorIndex(), BufferFillingColorIndex(), BufferBarsColorIndex() y BufferCandlesColorIndex() han sido simplemente renombrados para reproducirlos en el programa con mayor comodidad.

Por ejemplo, el método BufferArrowColor() antes se llamaba BufferColorArrow(), lo cual podía provocar confusión al definir su cometido: por lo menos, "color del búfer de flechas" sí que confundía al autor. Ahora, todos estos métodos primero describen el tipo del búfer, y luego, lo que ellos retornan, cosa que se antoja más visual y correcta.

El método BuffersSetColors() establece para todos los búferes de indicador de la colección los valores de color de la matriz de colores transmitida con la ayuda del método SetColors() de la clase de colección de objetos de búfer que hemos analizado anteriormente:

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

Los métodos de establecimiento del índice de color para objetos de búfer concretos ahora ha sido rediseñado, y llaman a los métodos correspondientes de la clase de colección de los objetos de búfer que hemos analizado anteriormente:

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);     }

El método Clear() limpia los datos del búfer según su índice en la lista en la barra indicada de la serie temporal con la ayuda de la llamada del método Clear() de la clase de colección de los objetos de búfer:

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

Los métodos para limpiar objetos de búfer concretos limpian los datos de todas las matrices del búfer indicado con la ayuda de los métodos correspondientes de la clase de colección de los objetos de búfer que hemos analizado más arriba:

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);   }

El método BuffersPrintShort() muestra la descripción breve de todos los búferes de indicador creados en el programa, que se guardan en la colección de objetos de búfer:

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

La lógica del método ha sido descrita en los comentarios al código, y es sencilla: en un ciclo por la lista de todos los objetos de búfer de la colección, obtenemos el siguiente búfer y mostramos en el diario su descripción breve con la ayuda del método PrintShort() de la clase de objeto de búfer.

Antes hemos mencionado que debemos transmitir a la clase de colección de los objetos de búfer el puntero al objeto de la clase de colección de las series temporales.
Ya tenemos el método CollectionOnInit(); llamándolo podemos transmitir a la biblioteca todas las colecciones necesarias, y solo nos quedará añadirle la transmisión del puntero necesario, lo que precisamente haremos mediante la llamada del método OnInit() de la clase de colección de los objetos de búfer, que hemos visto anteriormente:

//--- 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 se llama en OnInit() de los programas que funcionan usando como base la biblioteca.
Para ser más exactos, en OnInit() se llama a la función de inicialización de la biblioteca OnInitDoEasy(), en la que, junto con otras acciones de configuración, se ha añadido también la llamada de este método.

Con esto, damos por finalizada la mejora de las clases de la biblioteca por hoy.
Vamos a poner a prueba la creación de un indicador multiperiodo.

Simulación

Para la simulación, vamos a tomar el indicador de prueba del artículo anterior y guardarlo en la nueva carpeta \MQL5\Indicators\TestDoEasy\Part45\
con el nombre TestDoEasyPart45.mq5.

Lo haremos así: el indicador tendrá en sus ajustes el marco temporal con el que deberá trabajar, y añadiremos ahí mismo la selección de los tipos de dibujado; los búferes que se dibujen en el gráfico dependerán de los tipos de dibujado elegidos.

Cambiaremos la línea del indicador del artículo anterior

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

por una nueva:

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

Aquí, hemos cambiado el trabajo del indicador con el marco temporal actual por el trabajo con una lista de marcos temporales. Ahora, el indicador esperará la lista de marcos temporales para trabajar; dicha lista se encontrará en la matriz de almacenamiento establecida para ello.
A continuación, añadimos a los ajustes la variable de entrada en la que se establecerá el marco temporal necesario para trabajar (por defecto, el actual), y después, este marco temporal seleccionado será registrado en la matriz en el manejador OnInit(). De esta forma, mostraremos al indicador el periodo necesario del gráfico del que deberá tomar los datos a representar en el actual.

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);

Después de registrar el marco temporal en la matriz, la biblioteca utilizará este valor para crear las series temporales necesarias.

A continuación, solo tenemos que crear los búferes con todos los tipos de dibujado y establecer para ellos las banderas de representación en la ventana de datos dependiendo de los ajustes correspondientes de los parámetros de entrada, y también establecer para ellos el marco temporal indicado en los ajustes:

//--- 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 siempre vamos a utilizar en los indicadores la misma dirección de indexación que en las series temporales (esto está relacionado con la organización del guardado de las series temporales en la biblioteca), crearemos otra función para transmitir todas las matrices de datos de OnCalculate() a la biblioteca. Antes se encargaba de esto la función CopyData(), en la que se asignaba a las matrices la bandera "como en la serie temporal", y después se retornaba su estado pasado:

//+------------------------------------------------------------------+
//| 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 continuación, nosotros "girábamos" de nuevo estas matrices para que trabajaran correctamente con las series temporales de la biblioteca en 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 no realizar un trabajo innecesario, en el archivo de funciones de servicio de la biblioteca, crearemos otra función más, en la cual las matrices transmitidas a esta obtendrán la misma dirección de indexación que en la serie temporal. Abrimos el archivo \MQL5\Include\DoEasy\Services\DELib.mqh y le añadimos la nueva función:

//+------------------------------------------------------------------+
//| 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 función hace lo mismo que la anterior, pero no retorna a las matrices su indexación inicial. Ahora, en lugar de llamar a la función CopyData() desde el indicador, llamaremos a la nueva: CopyDataAsSeries(), lo que nos evitará establecer de nuevo para las matrices la indexación necesaria:

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

Las demás acciones y la lógica del manejador OnCalculate() se describen con detalle en los comentarios al código. Podemos ver lo fácil que resulta ahora trabajar con el indicador multiperiodo, pues ya no necesitamos calcular nada por nosotros mismos, solo tenemos que añadir los datos al búfer, y la biblioteca calculará dónde introducirlos y cómo representarlos:



¿Qué es lo próximo?

En el próximo artículo, continuaremos desarrollando la clase de colección de los búferes de indicador, y también organizaremos el funcionamiento de los indicadores en el modo de símbolo múltiple.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.
Querríamos recordar al lector que en este artículo hemos creado un indicador de prueba en MQL5 para MetaTrader 5.
Los archivos adjuntos han sido diseñados solo para MetaTrader 5, y en MetaTrader 4, la biblioteca en su versión actual no ha sido puesta a prueba.
Después de crear todas las colecciones de los búferes de los indicadores y poner estas a prueba, trataremos también de implementar algunas cosas en MetaTrader 4.

Volver al contenido

Artículos de esta serie:

Trabajando con las series temporales en la biblioteca DoEasy (Parte 35): El objeto "Barra" y la lista de serie temporal del símbolo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 36): El objeto de series temporales de todos los periodos utilizados del símbolo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 37): Colección de series temporales - Base de datos de series temporales según el símbolo y el periodo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 38): Colección de series temporales - Actualización en tiempo real y acceso a los datos desde el programa
Trabajando con las series temporales en la biblioteca DoEasy (Parte 39): Indicadores basados en la biblioteca - Preparación de datos y eventos de la series temporales
Trabajando con las series temporales en la biblioteca DoEasy (Parte 40): Indicadores basados en la biblioteca - actualización de datos en tiempo real
Trabajando con las series temporales en la biblioteca DoEasy (Parte 41): Ejemplo de indicador de símbolo y periodo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 42): La clase del objeto de búfer de indicador abstracto
Trabajando con las series temporales en la biblioteca DoEasy (Parte 43): Las clases de los objetos de búferes de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 44): Las clases de colección de los objetos de búferes de indicador