Timeseries in DoEasy library (part 49): Multi-period multi-symbol multi-buffer standard indicators

Artyom Trishkin | 18 November, 2020

Contents


Concept

In this article, I will continue the topic of displaying data in indicators from any symbol/period to the current symbol chart by creating the functionality for displaying standard indicators using several buffers for drawing their data. We have already learned to obtain data from any chart symbol/period and display them on the current symbol chart. Not everything goes smoothly but I gradually detect and eliminate all shortcomings. After all, we are learning to develop from scratch which does not exclude the possibility of not taking something into account. The most important thing here is to promptly detect existing flaws and gradually fix them having a lot of observation time in stock. In the current article, I will implement the ability to create multi-buffer standard indicators.

The main difference from single-buffer indicators in the concept of building a library is that we need to somehow mark the drawn and calculated indicator buffers so that the library can refer them all to one common indicator.

At the moment, we already have a standard indicator type written to the properties of buffer objects and the buffer identifier:

These parameters are well suited for identifying the affiliation of buffer objects to single-buffer standard indicators. In multi-buffer indicators, we also need to define which standard indicator line the calculated and drawn buffer objects belong to, since there are multiple lines. Here we come to the conclusion that it is necessary to set at least one more parameter - the line type (top, bottom, middle, etc.).
In that case, we will be able to clearly distinguish the affiliation of buffer objects to each of the created indicators and easily access the necessary line of any of the created indicators by the line type:

  • The "Indicator type" parameter shows a standard indicator the drawn and calculated buffer objects of a created indicator belong to;
  • The "Indicator identifier" parameter is used to specify which of the two created indicators of the same type the drawn and calculated buffer objects belong to (for example, different МА indicators — one of them has the ID of 1, the second one — 2, the third one — 3, etc. All of them have the same IND_MA indicator type);
  • The "Indicator line" parameter specifies a certain indicator line obtained by setting two previous parameters

Let's add yet another parameter: "Short indicator name", which is to store the name of a standard indicator matching the one displayed by standard indicators in a subwindow with the addition of a symbol and a period. For example, this name looks as follows for Stochastic:

Fig. 1. Standard Stochastic Oscillator indicator

Fig. 2. Multi-symbol multi-period standard Stochastic Oscillator indicator


Improving library classes

Let's change the \MQL5\Include\DoEasy\Datas.mqh file name to \MQL5\Include\DoEasy\Data.mqh (which is more correct from the linguistic point of view) and add constants of the new library message indices:

//--- 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_ID,                       // Indicator buffer ID
   MSG_LIB_TEXT_BUFFER_TEXT_IND_LINE_MODE,            // Indicator line
   MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE,               // Handle of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE,                 // Type of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME,                // Buffer (timeframe) data period
   MSG_LIB_TEXT_BUFFER_TEXT_STATUS,                   // Buffer status
   MSG_LIB_TEXT_BUFFER_TEXT_TYPE,                     // Buffer type
   MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE,                   // Active
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE,               // Arrow code
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT,              // The vertical shift of the arrows
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN,               // The number of initial bars that are not drawn and values in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE,                // Graphical construction type
   MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA,                // Display construction values in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_SHIFT,                    // Indicator graphical construction shift by time axis in bars
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE,               // Line style
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH,               // Line width
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE,               // Arrow size
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM,                // Number of colors
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR,                    // Drawing color
   MSG_LIB_TEXT_BUFFER_TEXT_EMPTY_VALUE,              // Empty value for plotting where nothing will be drawn
   MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL,                   // Buffer symbol
   MSG_LIB_TEXT_BUFFER_TEXT_LABEL,                    // Name of the graphical indicator series displayed in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME,                 // Name of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME_SHORT,           // Short name of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_STATUS_NAME,              // Indicator buffer with graphical construction type 
   MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF,    // Invalid number of indicator buffers (#property indicator_buffers)
   MSG_LIB_TEXT_BUFFER_TEXT_MAX_BUFFERS_REACHED,      // Reached maximum possible number of indicator buffers
   MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ,            // No buffer object for standard indicator

Add text messages corresponding to newly added indices:

   {"Индекс базового буфера данных","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"},
   {"Идентификатор буферов индикатора","Indicator Buffer Id"},
   {"Линия индикатора","Indicator line"},
   {"Хэндл индикатора, использующего буфер","Indicator handle that uses the buffer"},
   {"Тип индикатора, использующего буфер","Indicator type that uses the buffer"},
   {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"},
   {"Статус буфера","Buffer status"},
   {"Тип буфера","Buffer type"},
   {"Активен","Active"},
   {"Код стрелки","Arrow code"},
   {"Смещение стрелок по вертикали","Vertical shift of arrows"},
   {"Количество начальных баров без отрисовки и значений в DataWindow","Number of initial bars without drawing and values in DataWindow"},
   {"Тип графического построения","Type of graphical construction"},
   {"Отображение значений построения в окне DataWindow","Display construction values in DataWindow"},
   {"Сдвиг графического построения индикатора по оси времени в барах","Shift of indicator plotting along time axis in bars"},
   {"Стиль линии отрисовки","Drawing line style "},
   {"Толщина линии отрисовки","Thickness of drawing line"},
   {"Размер значка стрелки","Arrow icon size"},
   {"Количество цветов","Number of colors"},
   {"Цвет отрисовки","Index of buffer containing drawing color"},
   {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"},
   {"Символ буфера","Buffer Symbol"},
   {"Имя индикаторной графической серии, отображаемое в окне DataWindow","Name of indicator graphical series to display in DataWindow"},
   {"Наименование индикатора, использующего буфер","Name of indicator that uses buffer"},
   {"Короткое наименование индикатора, использующего буфер","Short name of indicator that uses buffer"},
   {"Индикаторный буфер с типом графического построения","Indicator buffer with graphic plot type"},
   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},
   {"Нет ни одного объекта-буфера для стандартного индикатора","No buffer object for standard indicator"},

Remove the old Datas.mqh file from the \MQL5\Include\DoEasy\ library folder as its functions are now fulfilled by Data.mqh.

Add all the necessary new data for working with multi-buffer standard indicators to \MQL5\Include\DoEasy\Defines.mqh.

First, change the name in the renamed file include string:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Data.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+

Add the new enumeration of indicator line types to the block containing data for working with indicator buffers:

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

Different standard indicators have lines, which may have their own names depending on how the indicator developer named them. Here I have created the enumeration that contains constants specifying a name of an indicator line. The constant values are to be used to mark the affiliation of created buffer objects (calculated and drawn ones) to a certain standard indicator line. Thus, when accessing the buffer object by constant name, we can unambiguously obtain the required object to be handled (considering the indicator type and its ID).

Later, I will assign accurate values to these constants to reduce the amount of code I am going to generate here (since many indicators completely repeat the calculation of the same-type indicators having other line names).

Add the new value to the enumeration of integer buffer object properties and increase the number of integer properties from 23 to 24:

//+------------------------------------------------------------------+
//| 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_IND_LINE_MODE,                               // Indicator line
   BUFFER_PROP_ID,                                          // ID of multiple buffers of a single indicator
   BUFFER_PROP_IND_HANDLE,                                  // Handle of an indicator using a buffer
   BUFFER_PROP_IND_TYPE,                                    // Type of an indicator using a buffer
   BUFFER_PROP_NUM_DATAS,                                   // Number of data buffers
   BUFFER_PROP_INDEX_COLOR,                                 // Color buffer index
  }; 
#define BUFFER_PROP_INTEGER_TOTAL (24)                      // Total number of integer bar properties
#define BUFFER_PROP_INTEGER_SKIP  (2)                       // Number of buffer properties not used in sorting
//+------------------------------------------------------------------+

In the same way, add the new string property and increase their number to 4:

//+------------------------------------------------------------------+
//| Buffer string properties                                         |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_STRING
  {
   BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), // Buffer symbol
   BUFFER_PROP_LABEL,                                       // Name of the graphical indicator series displayed in DataWindow
   BUFFER_PROP_IND_NAME,                                    // Name of an indicator using a buffer
   BUFFER_PROP_IND_NAME_SHORT,                              // Short name of an indicator using a buffer
  };
#define BUFFER_PROP_STRING_TOTAL  (4)                       // Total number of string buffer properties
//+------------------------------------------------------------------+

Since I have added two new properties to the buffer object, I also need to add two new criteria to search and sort buffer objects in their collection list by these properties:

//+------------------------------------------------------------------+
//| 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_BUFFER_IND_LINE_MODE,                            // Sort by the indicator line
   SORT_BY_BUFFER_ID,                                       // Sort by ID of multiple buffers of a single indicator
   SORT_BY_BUFFER_IND_HANDLE,                               // Sort by handle of an indicator using a buffer
   SORT_BY_BUFFER_IND_TYPE,                                 // Sort by type of an indicator using a 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
   SORT_BY_BUFFER_IND_NAME,                                 // Sort by name of an indicator using a buffer
   SORT_BY_BUFFER_IND_NAME_SHORT,                           // Sort by a short name of an indicator using a buffer
  };
//+------------------------------------------------------------------+

In the public section of the class, write the methods of installing and receiving new buffer object properties in \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh:

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);
   void              SetID(const int id)                             { this.SetProperty(BUFFER_PROP_ID,id);                                  }
   void              SetIndicatorHandle(const int handle)            { this.SetProperty(BUFFER_PROP_IND_HANDLE,handle);                      }
   void              SetIndicatorType(const ENUM_INDICATOR type)     { this.SetProperty(BUFFER_PROP_IND_TYPE,type);                          }
   void              SetIndicatorName(const string name)             { this.SetProperty(BUFFER_PROP_IND_NAME,name);                          }
   void              SetIndicatorShortName(const string name)        { this.SetProperty(BUFFER_PROP_IND_NAME_SHORT,name);                    }
   void              SetLineMode(const ENUM_INDICATOR_LINE_MODE mode){ this.SetProperty(BUFFER_PROP_IND_LINE_MODE,mode);                     }
   
//--- 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, (20) number of buffers for construction
//--- (21) set empty value, (22) buffer symbol and (23) name of the indicator graphical series displayed in DataWindow
//--- (24) buffer ID, (25) indicator handle, (26) standard indicator type, (27) standard indicator name,
//--- (28) umber of standard indicator's calculated bars, (29) line type (main, signal, etc)
   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);      }
   ENUM_BUFFER_STATUS Status(void)                             const { return (ENUM_BUFFER_STATUS)this.GetProperty(BUFFER_PROP_STATUS);      }
   ENUM_BUFFER_TYPE  TypeBuffer(void)                          const { return (ENUM_BUFFER_TYPE)this.GetProperty(BUFFER_PROP_TYPE);          }
   bool              IsActive(void)                            const { return (bool)this.GetProperty(BUFFER_PROP_ACTIVE);                    }
   uchar             ArrowCode(void)                           const { return (uchar)this.GetProperty(BUFFER_PROP_ARROW_CODE);               }
   int               ArrowShift(void)                          const { return (int)this.GetProperty(BUFFER_PROP_ARROW_SHIFT);                }
   int               DrawBegin(void)                           const { return (int)this.GetProperty(BUFFER_PROP_DRAW_BEGIN);                 }
   ENUM_DRAW_TYPE    DrawType(void)                            const { return (ENUM_DRAW_TYPE)this.GetProperty(BUFFER_PROP_DRAW_TYPE);       }
   bool              IsShowData(void)                          const { return (bool)this.GetProperty(BUFFER_PROP_SHOW_DATA);                 }
   int               Shift(void)                               const { return (int)this.GetProperty(BUFFER_PROP_SHIFT);                      }
   ENUM_LINE_STYLE   LineStyle(void)                           const { return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE);     }
   int               LineWidth(void)                           const { return (int)this.GetProperty(BUFFER_PROP_LINE_WIDTH);                 }
   int               ColorsTotal(void)                         const { return (int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES);              }
   color             Color(void)                               const { return (color)this.GetProperty(BUFFER_PROP_COLOR);                    }
   int               BuffersTotal(void)                        const { return (int)this.GetProperty(BUFFER_PROP_NUM_DATAS);                  }
   double            EmptyValue(void)                          const { return this.GetProperty(BUFFER_PROP_EMPTY_VALUE);                     }
   string            Symbol(void)                              const { return this.GetProperty(BUFFER_PROP_SYMBOL);                          }
   string            Label(void)                               const { return this.GetProperty(BUFFER_PROP_LABEL);                           }
   int               ID(void)                                  const { return (int)this.GetProperty(BUFFER_PROP_ID);                         }
   int               IndicatorHandle(void)                     const { return (int)this.GetProperty(BUFFER_PROP_IND_HANDLE);                 }
   ENUM_INDICATOR    IndicatorType(void)                       const { return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE);        }
   string            IndicatorName(void)                       const { return this.GetProperty(BUFFER_PROP_IND_NAME);                        }
   string            IndicatorShortName(void)                  const { return this.GetProperty(BUFFER_PROP_IND_NAME_SHORT);                  }
   int               IndicatorBarsCalculated(void)             const { return ::BarsCalculated((int)this.GetProperty(BUFFER_PROP_IND_HANDLE));}
   ENUM_INDICATOR_LINE_MODE LineMode(void)                     const { return (ENUM_INDICATOR_LINE_MODE)this.GetProperty(BUFFER_PROP_IND_LINE_MODE);}

In the class constructor, set default values to the new properties:

//+------------------------------------------------------------------+
//| 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 uchar total_arrays,
                 const int width,
                 const string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
   this.m_total_arrays=total_arrays;
//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                        = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                          = buffer_type;
   this.m_long_prop[BUFFER_PROP_ID]                            = WRONG_VALUE;
   this.m_long_prop[BUFFER_PROP_IND_LINE_MODE]                 = INDICATOR_LINE_MODE_MAIN;
   this.m_long_prop[BUFFER_PROP_IND_HANDLE]                    = INVALID_HANDLE;
   this.m_long_prop[BUFFER_PROP_IND_TYPE]                      = WRONG_VALUE;
   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.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]               = index_base_array+this.m_total_arrays;
   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);
   this.m_string_prop[this.IndexProp(BUFFER_PROP_IND_NAME)]    = NULL;
   this.m_string_prop[this.IndexProp(BUFFER_PROP_IND_NAME_SHORT)]= 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 graphical series
   ::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 parameters of the graphical series
   ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE));
//--- Set string parameters of the graphical series
   ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL));
  }
//+------------------------------------------------------------------+

Implement the return of the indicator line purpose description in the method returning the description of the buffer integer properties:

//+------------------------------------------------------------------+
//| Return description of a buffer's integer property                |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property)
  {
   return
     (
      property==BUFFER_PROP_INDEX_PLOT    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_STATUS        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_STATUS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
         )  :
      property==BUFFER_PROP_TYPE          ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTypeBufferDescription()
         )  :
      property==BUFFER_PROP_TIMEFRAME     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTimeframeDescription()
         )  :
      property==BUFFER_PROP_ACTIVE        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetActiveDescription()
         )  :
      property==BUFFER_PROP_DRAW_TYPE     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetDrawTypeDescription()
         )  :
      property==BUFFER_PROP_ARROW_CODE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ARROW_SHIFT   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_LINE_STYLE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetLineStyleDescription()
         )  :
      property==BUFFER_PROP_LINE_WIDTH    ?  
         (this.Status()==BUFFER_STATUS_ARROW ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE) :
          CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH))+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_DRAW_BEGIN    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_SHOW_DATA     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetShowDataDescription()
         )  :
      property==BUFFER_PROP_SHIFT         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHIFT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR_INDEXES ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_COLOR   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_BASE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_BASE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_PLOT ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ID ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_LINE_MODE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_LINE_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::StringSubstr(::EnumToString((ENUM_INDICATOR_LINE_MODE)this.GetProperty(property)),10)
         )  :
      property==BUFFER_PROP_IND_HANDLE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_TYPE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::StringSubstr(::EnumToString((ENUM_INDICATOR)this.GetProperty(property)),4)
         )  :
      property==BUFFER_PROP_NUM_DATAS     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetColorsDescription()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Implement the return of the indicator short name description in the method returning the description of string buffer properties:

//+------------------------------------------------------------------+
//| Return description of a buffer's string property                 |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_STRING property)
  {
   return
     (
      property==BUFFER_PROP_SYMBOL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.Symbol()
         )  :
      property==BUFFER_PROP_LABEL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LABEL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.Label()==NULL || this.Label()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Label()+"\"")
         )  :
      property==BUFFER_PROP_IND_NAME   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IndicatorName()==NULL || this.IndicatorName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.IndicatorName()+"\"")
         )  :
      property==BUFFER_PROP_IND_NAME_SHORT   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME_SHORT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IndicatorShortName()==NULL || this.IndicatorName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.IndicatorShortName()+"\"")
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Since I have added two new properties to the buffer object, I should add them to the list of the allowed ones since we have descendant objects of the abstract buffer's basic object. These descendant objects, in turn, feature virtual methods returning the flags of the object supporting various properties. Adding the properties to the list of allowed ones is the only way to enable the ability to search, select and sort buffer objects in the buffer object collection list.
Each drawing type features its own buffer object, and improvements should be made to files of all such objects. Since all changes in all objects are similar, let's consider the improvement using the line buffer object in \MQL5\Include\DoEasy\Objects\Indicators\BufferLine.mqh as an example.

The changes have been made into two virtual methods returning the flag of the object supporting integer and string properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if((property==BUFFER_PROP_ARROW_CODE || property==BUFFER_PROP_ARROW_SHIFT) || 
      (
       this.TypeBuffer()==BUFFER_TYPE_CALCULATE && 
       property!=BUFFER_PROP_TYPE && 
       property!=BUFFER_PROP_INDEX_NEXT_BASE && 
       property!=BUFFER_PROP_IND_LINE_MODE && 
       property!=BUFFER_PROP_IND_HANDLE &&
       property!=BUFFER_PROP_IND_TYPE &&
       property!=BUFFER_PROP_ID
      )
     ) return false;
   return true; 
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| string property, otherwise return 'false'                        |
//+------------------------------------------------------------------+
bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE && property!=BUFFER_PROP_IND_NAME_SHORT)
      return false;
   return true;
  }
//+------------------------------------------------------------------+

Provided that the buffer object is a calculated buffer, then, if the method receives any of the properties not present in the enumerated list, the method returns false — the object does not support such a property, otherwise the object supports such a property and the method returns true.

Such changes (or similar ones in BufferCalculate.mqh) are implemented to all files of the descendant classes of the abstract buffer object: BufferArrow.mqh, BufferBars.mqh, BufferCalculate.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferSection.mqh, BufferZigZag.mqh and BufferLine.mqh, which I have already considered. Find the changes in the attached files.

In the previous article, I have created the methods for creating buffer objects for multi-symbol multi-period standard indicators displaying their data in the main chart subwindow. In the current article, I will supplement the library with the methods of creating standard indicators displaying their data in the main chart window — these methods are no different from those already created. They have been implemented for all standard indicators of the main window. I will consider such a method using Moving Average as an example:

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

Learn more about the method operation in the previous article. The only difference from the previously considered similar methods is setting a new property for the buffer object — the line type of a single-buffer indicator is set as Main. Such additions were made to all previously implemented methods of creating single-buffer standard indicators in a subwindow considered in the previous article, as well as to the methods of creating single-buffer standard indicators in the main window, which are already added to the buffer object collection class file BuffersCollection.mqh — these are CreateAMA(), CreateDEMA(), CreateFrAMA(), CreateMA(), CreateSAR(), CreateTEMA() and CreateVIDYA() methods.
This line value has already been set as a default one in the class constructor, but here I have added the unconditional setting of this property so that these methods correspond to the methods of creating multi-buffer standard indicators I am going to consider now.

Let's have a look at the methods of creating multi-buffer standard indicators using the method of creating the standard Average Directional Movement Index indicator object as an example:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period ADX                             |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iADX(symbol,timeframe,adx_period);
   int identifier=(id==WRONG_VALUE ? IND_ADX : id);
   color array_colors[1]={clrLightSeaGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary ADX line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);     // This is the main indicator line
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      buff.SetLabel(buff.IndicatorShortName());
      buff.SetColors(array_colors);
      
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary +DI line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_PLUS);  // This is a +DI line
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      buff.SetLabel("+DI");
      array_colors[0]=clrYellowGreen;
      buff.SetColors(array_colors);
      buff.SetStyle(STYLE_DOT);
      
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary -DI line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_MINUS); // This is a -DI line
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      buff.SetLabel("-DI");
      array_colors[0]=clrWheat;
      buff.SetColors(array_colors);
      buff.SetStyle(STYLE_DOT);
      
      //--- Create a calculated ADX line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary ADX line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetLabel("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      
      //--- Create a calculated +DI line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary +DI line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_PLUS);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetLabel("+DI");
      
      //--- Create a calculated -DI line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary -DI line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_MINUS);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetLabel("-DI");
     }
   return handle;
  }
//+------------------------------------------------------------------+

The logic here is completely identical to the previously created methods. The number of buffer objects depends on the number of lines drawn by each specific standard indicator. In order to set the affiliation of each created buffer object of an appropriate standard indicator line, set the line value from the ENUM_INDICATOR_LINE_MODE enumeration added above. Thus, each buffer object corresponds to the indicator line specified for it. Each indicator line features two buffer objects — drawn and calculated one. In the calculated buffer, write data from the handle of the created standard indicator, while the drawn buffer displays the calculated buffer data to the main chart. This is the entire method logic.
The remaining methods are almost identical, except for indicator names, their short names and names of each of the lines since each standard indicator line has its own name and description corresponding to how the indicator displays its line names in the data window.

The following methods for creating multi-buffer standard indicator objects have been implemented: CreateADX(), CreateADXWilder(), CreateMACD(), CreateRVI(), CreateStochastic(), CreateBands(), CreateEnvelopes() and CreateFractals(). The methods are identical to the one considered above. There is no point in describing them. You can find them in the attached files.

In the previous article, I have started creating the methods preparing the calculated buffer data, clearing the buffer data and setting the values to the drawn buffer. With the use of the switch operator, actions performed with the buffers are distributed according to the indicator type. As it turns out, only minor improvements are required in order to implement handling of standard indicator objects created today.

The method preparing the calculated buffer data of the specified standard indicator:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of the specified standard indicator                              |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list_ind==NULL || list_ind.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copied=WRONG_VALUE;
   int idx0=0,idx1=1,idx2=2;
   switch((int)std_ind)
     {
   //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
      
        buffer=list_ind.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copied;
      
   //--- Multi-buffer standard indicators
      case IND_ALLIGATOR   :
      case IND_GATOR       :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_BANDS :
        idx0=1;
        idx1=0;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
   //--- Multi-buffer standard indicators in a subwindow
      case IND_ADX   :
      case IND_ADXW  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;

      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

As you can see, the actions performed when handling buffer objects are placed to separate handling blocks. Each block features its own indicators handled in similar way. Only actions that are identical for various types of standard indicators but are different from other types of standard indicators, are sent to separate handling blocks. In case of the Bollinger Bands indicator, I had to swap the indices of its buffers because its buffer indexing (upper line, middle line and lower line) is different from the same indexing in the data window (upper line, lower line, middle line) for some reason. Therefore, I had to implement two additional variables idx0 and idx1 featuring real indices of standard indicator lines for each type. For all other types of indicators, their line indices go in a row: 0, 1 and 2, while in case of Bollinger Bands, the first two indices are reversed: 1, 0 and 2.

I have already considered the rest of the method logic in the previous article. Here I have simply added handling newly created standard indicator objects.

The method clearing buffer data of the specified standard indicator by the timeseries index:

//+------------------------------------------------------------------+
//| Clear buffer data of the specified standard indicator            |
//| by the timeseries index                                          |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
//--- Get the list of buffer objects by type and ID
   CArrayObj *list_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   if(list_ind==NULL || list_ind.Total()==0)
      return;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list_ind.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
   //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        buffer=list_ind.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
   //--- Multi-buffer standard indicators
      case IND_ALLIGATOR:
      case IND_GATOR    :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ADX   :
      case IND_ADXW  :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_BANDS    :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ENVELOPES   :
      case IND_FRACTALS    :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;

      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;

      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Just like in the previous method, here I have grouped together similar handling actions for different standard indicator objects.

If we take a closer look, we can see that many indicators located in separate groups have an indentical handling logic, and the whole difference lies only in the names of their line constants. I have mentioned this at the beginning of the article. After creating the objects of the three remaining standard indicators whose lines are initially displayed on a chart with a shift (Alligator, Gator and Ishimoku), I will optimize these methods by setting similar line index values for different constants that are identical in their purpose. This will reduce the method code.

The method setting the values for the current chart to the buffers of the specified standard indicator by the timeseries index:

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

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

This method is constructed identically to the two methods discussed above. Same-type handling actions of different standard indicators are grouped here as well. This method is also to be optimized after setting the values for the constants of same-type indicator lines.

The method returning the standard indicator buffer description by its type and ID has been previously implemented in the CEngine library main class file. Let's move its implementation to the buffer collection class. To do this, declare this method along with the other one, returning the indicator short name, at the very end of the class body:

//--- Return the standard indicator buffer description by type and ID
   string                  GetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id,const ENUM_INDICATOR_LINE_MODE line_mode=INDICATOR_LINE_MODE_MAIN);
//--- Return the standard indicator short name by type and ID
   string                  GetIndicatorShortNameByTypeID(const ENUM_INDICATOR ind_type,const int id);
   
//--- 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;  }
  };
//+------------------------------------------------------------------+

Let's write their implementation outside the class body:

//+------------------------------------------------------------------+
//| Return the standard indicator buffer description                 |
//| by type and ID                                                   |
//+------------------------------------------------------------------+
string CBuffersCollection::GetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id,const ENUM_INDICATOR_LINE_MODE line_mode=INDICATOR_LINE_MODE_MAIN)
  {
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_IND_LINE_MODE,line_mode,EQUAL);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.Label();
  }
//+------------------------------------------------------------------+
//| Return the standard indicator short name                         |
//| by type and ID                                                   |
//+------------------------------------------------------------------+
string CBuffersCollection::GetIndicatorShortNameByTypeID(const ENUM_INDICATOR ind_type,const int id)
  {
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.IndicatorShortName();
  }
//+------------------------------------------------------------------+

I have already considered the logic of such methods many times, and I believe everything is clear here. If you have any questions, feel free to ask them in the comments.

Now we need to implement access to all created methods in the CEngine library main class. Since I have already implemented all methods for creating standard indicator objects in the previous article, all I have to do now is change calling the methods returning the description of indicator lines and its short name:

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

//--- Return the standard indicator buffer description by type and ID
   string               BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id,const ENUM_INDICATOR_LINE_MODE line_mode)
                          { return this.m_buffers.GetLabelByTypeID(ind_type,id,line_mode);                        }
//--- Return the standard indicator short name by type and ID
   string               BufferGetIndicatorShortNameByTypeID(const ENUM_INDICATOR ind_type,const int id)
                          { return this.m_buffers.GetIndicatorShortNameByTypeID(ind_type,id);                     }

//--- Display short description of all indicator buffers of the buffer collection

The methods return the result of calling same-name methods of the buffer collection class.

Now I only need to remove the implementation of the method I have moved to the buffer collection class from CEngine class:

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

Currently, these are all the changes to be implemented to create objects of multi-buffer standard indicators.


Test

To perform a test, let's take the indicator from the previous article and use it to create two new ones — the first one is to display multi-symbol multi-period standard indicators in a subwindow, while the second one is to do the same in the main window of a symbol chart.

The logic of the indicators has not changed in any way in comparison with the already considered test indicator from the previous article. I am only going to add the call of the methods for creating the necessary indicators in the OnInit() handler.

Save the indicator from the previous article in \MQL5\Indicators\TestDoEasy\Part49\ as TestDoEasyPart49_1.mq5.
This indicator is to create and display standard indicators in the current symbol chart subwindow. Its OnInit() handler will look as follows:

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

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

//--- Create the button panel

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

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing a selected standard indicator
   bool success=false;
   switch(InpIndType)
     {
//--- Single-buffer standard indicators in a subwindow
      case IND_AC          :  success=engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);                                      break;
      case IND_AD          :  success=engine.BufferCreateAD(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                          break;
      case IND_AO          :  success=engine.BufferCreateAO(InpUsedSymbols,InpPeriod,1);                                      break;
      case IND_ATR         :  success=engine.BufferCreateATR(InpUsedSymbols,InpPeriod,14,1);                                  break;
      case IND_BEARS       :  success=engine.BufferCreateBearsPower(InpUsedSymbols,InpPeriod,13,1);                           break;
      case IND_BULLS       :  success=engine.BufferCreateBullsPower(InpUsedSymbols,InpPeriod,13,1);                           break;
      case IND_BWMFI       :  success=engine.BufferCreateBWMFI(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                       break;
      case IND_CHAIKIN     :  success=engine.BufferCreateChaikin(InpUsedSymbols,InpPeriod,3,10,MODE_EMA,VOLUME_TICK,1);       break;
      case IND_CCI         :  success=engine.BufferCreateCCI(InpUsedSymbols,InpPeriod,14,PRICE_TYPICAL,1);                    break;
      case IND_DEMARKER    :  success=engine.BufferCreateDeMarker(InpUsedSymbols,InpPeriod,14,1);                             break;
      case IND_FORCE       :  success=engine.BufferCreateForce(InpUsedSymbols,InpPeriod,13,MODE_SMA,VOLUME_TICK,1);           break;
      case IND_MOMENTUM    :  success=engine.BufferCreateMomentum(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                 break;
      case IND_MFI         :  success=engine.BufferCreateMFI(InpUsedSymbols,InpPeriod,14,VOLUME_TICK,1);                      break;
      case IND_OSMA        :  success=engine.BufferCreateOsMA(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);                break;
      case IND_OBV         :  success=engine.BufferCreateOBV(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                         break;
      case IND_RSI         :  success=engine.BufferCreateRSI(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                      break;
      case IND_STDDEV      :  success=engine.BufferCreateStdDev(InpUsedSymbols,InpPeriod,20,0,MODE_SMA,PRICE_CLOSE,1);        break;
      case IND_TRIX        :  success=engine.BufferCreateTriX(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                     break;
      case IND_WPR         :  success=engine.BufferCreateWPR(InpUsedSymbols,InpPeriod,14,1);                                  break;
      case IND_VOLUMES     :  success=engine.BufferCreateVolumes(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                     break;
      
//--- Multi-buffer standard indicators in a subwindow
      case IND_ADX         :  success=engine.BufferCreateADX(InpUsedSymbols,InpPeriod,14,1);                                  break;
      case IND_ADXW        :  success=engine.BufferCreateADXWilder(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_MACD        :  success=engine.BufferCreateMACD(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);                break;
      case IND_RVI         :  success=engine.BufferCreateRVI(InpUsedSymbols,InpPeriod,10,1);                                  break;
      case IND_STOCHASTIC  :  success=engine.BufferCreateStochastic(InpUsedSymbols,InpPeriod,5,3,3,MODE_SMA,STO_LOWHIGH,1);   break;
      
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Check the number of buffers specified in the 'properties' block
   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
//--- (commented out since the colors have already been set in the methods of creating default standard indicators)
//--- (we can always set necessary colors either for all indicators, like here, or for each of them individually)
   //color array_colors[]={clrGreen,clrRed,clrGray};
   //engine.BuffersSetColors(array_colors);

//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();

//--- Set levels where they are required and define the data decimal capacity
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    break;
      
      case IND_CCI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER    :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_RSI         :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Set the short name for the indicator and bit depth
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//---
   CArrayObj *list=engine.GetListBuffers();
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.Print();
     }
   //return INIT_FAILED;
//---
//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

The full indicator code is provided in the files attached below.

Set EURUSD and 4 Hours in the indicator settings, compile the indicator and launch it on EURUSD H1. Thus, the standard indicators from H4 selected in the settings are to be displayed on H1:


Now let's create the indicator displaying standard indicators in the main window of the symbol chart.

Save the indicator from the previous article in \MQL5\Indicators\TestDoEasy\Part49\ as TestDoEasyPart49_2.mq5.
Its OnInit() handler will look as follows:

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

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

//--- Create the button panel

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

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing a selected standard indicator
   bool success=false;
   switch(InpIndType)
     {
//--- Single-buffer standard indicators in the main window
      case IND_AMA         :  success=engine.BufferCreateAMA(InpUsedSymbols,InpPeriod,9,2,30,0,PRICE_CLOSE,1);                break;
      case IND_DEMA        :  success=engine.BufferCreateDEMA(InpUsedSymbols,InpPeriod,14,0,PRICE_CLOSE,1);                   break;
      case IND_FRAMA       :  success=engine.BufferCreateFrAMA(InpUsedSymbols,InpPeriod,14,0,PRICE_CLOSE,1);                  break;
      case IND_MA          :  success=engine.BufferCreateMA(InpUsedSymbols,InpPeriod,10,0,MODE_SMA,PRICE_CLOSE,1);            break;
      case IND_SAR         :  success=engine.BufferCreateSAR(InpUsedSymbols,InpPeriod,0.02,0.2,1);                            break;
      case IND_TEMA        :  success=engine.BufferCreateTEMA(InpUsedSymbols,InpPeriod,14,0,PRICE_CLOSE,1);                   break;
      case IND_VIDYA       :  success=engine.BufferCreateVIDYA(InpUsedSymbols,InpPeriod,9,12,0,PRICE_CLOSE,1);                break;
      
//--- Multi-buffer standard indicators in a subwindow
      case IND_BANDS       :  success=engine.BufferCreateBands(InpUsedSymbols,InpPeriod,20,0,2.0,PRICE_CLOSE,1);              break;
      case IND_ENVELOPES   :  success=engine.BufferCreateEnvelopes(InpUsedSymbols,InpPeriod,14,0,MODE_SMA,PRICE_CLOSE,0.1,1); break;
      case IND_FRACTALS    :  success=engine.BufferCreateFractals(InpUsedSymbols,InpPeriod,1);                                break;
      
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- 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
//--- (commented out since the colors have already been set in the methods of creating default standard indicators)
//--- (we can always set necessary colors either for all indicators, like here, or for each of them individually)
   //color array_colors[]={clrGreen,clrRed,clrGray};
   //engine.BuffersSetColors(array_colors);

//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();

//--- Set levels where they are required and define the data decimal capacity
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    break;
      
      case IND_CCI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER    :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_RSI         :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Set the short name for the indicator and bit depth
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//---
   CArrayObj *list=engine.GetListBuffers();
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.Print();
     }
   //return INIT_FAILED;
//---
//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

The full indicator code is provided in the files attached below.

Set EURUSD and 4 Hours in the indicator settings, compile the indicator and launch it on EURUSD H1. Thus, the standard indicators from H4 selected in the settings are to be displayed on H1:



What's next?

In the next article, I will continue improving the library class for creating multi-symbol multi-period standard indicators, optimize the methods and get rid of unnecessary code in the library-based main indicator program.

All files of the current version of the library are attached below together with the test indicator files for you to test and download.
Leave your questions, comments and suggestions in the comments.
Please keep in mind that here I have developed the MQL5 test indicator for MetaTrader 5.
The attached files are intended only for MetaTrader 5. The current library version has not been tested in MetaTrader 4.
After developing and testing the functionality for working with indicator buffers, I will try to implement some MQL5 features in MetaTrader 4.

Back to contents

Previous articles within the series:

Timeseries in DoEasy library (part 35): Bar object and symbol timeseries list
Timeseries in DoEasy library (part 36): Object of timeseries for all used symbol periods
Timeseries in DoEasy library (part 37): Timeseries collection - database of timeseries by symbols and periods
Timeseries in DoEasy library (part 38): Timeseries collection - real-time updates and accessing data from the program
Timeseries in DoEasy library (part 39): Library-based indicators - preparing data and timeseries events
Timeseries in DoEasy library (part 40): Library-based indicators - updating data in real time
Timeseries in DoEasy library (part 41): Sample multi-symbol multi-period indicator
Timeseries in DoEasy library (part 42): Abstract indicator buffer object class
Timeseries in DoEasy library (part 43): Classes of indicator buffer objects
Timeseries in DoEasy library (part 44): Collection class of indicator buffer objects
Timeseries in DoEasy library (part 45): Multi-period indicator buffers
Timeseries in DoEasy library (part 48): Multi-period multi-symbol indicators on one buffer in a subwindow