Русский 中文 Español Deutsch 日本語 Português
Timeseries in DoEasy library (part 43): Classes of indicator buffer objects

Timeseries in DoEasy library (part 43): Classes of indicator buffer objects

MetaTrader 5Examples | 21 August 2020, 17:07
31 177 2
Artyom Trishkin
Artyom Trishkin

Contents


Concept

In the previous article, we created the abstract indicator buffer object containing some common properties of indicator buffers and providing the methods of working with them. Here, we will create objects derived from the abstract buffer object class. Each object of that kind is an independent indicator buffer object with its own unique type of graphical construction.
As already mentioned earlier, all indicator buffers are colored. However, if a monochrome one is required, the buffers are initially created with one color. Alternatively, we are able to set the amount of colors used by the buffer right during the indicator operation.

First of all, let's add new library messages.
Open \MQL5\Include\DoEasy\Datas.mqh and add indices of new messages:

   MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ,                 // Failed to create the \"Bar\" object
   MSG_LIB_SYS_FAILED_SYNC_DATA,                      // Failed to synchronize data with the server
   MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE,           // Failed to change the array size of drawn buffers
   MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE,            // Failed to change the color array size

...

   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_STATUS_NAME,              // Indicator buffer with graphical construction type 
   MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF,    // Invalid number of indicator buffers in #property indicator_buffers

...

   MSG_LIB_TEXT_BUFFER_TEXT_TYPE_CALCULATE,           // Calculated buffer
   MSG_LIB_TEXT_BUFFER_TEXT_TYPE_DATA,                // Colored data buffer
   MSG_LIB_TEXT_BUFFER_TEXT_BUFFER,                   // Buffer
   
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_SOLID,              // Solid line
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASH,               // Dashed line
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DOT,                // Dotted line
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOT,            // Dot-dash line
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOTDOT,         // Dash - two dots
   
  };

Next, add the new text messages corresponding to the newly added indices:

   {"Не удалось создать объект \"Бар\"","Failed to create object \"Bar\""},
   {"Не удалось синхронизировать данные с сервером","Failed to sync data with server"},
   {"Не удалось изменить размер массива рисуемых буферов","Failed to resize drawing buffers array"},
   {"Не удалось изменить размер массива цветов","Failed to resize color array"},

...

   {"Толщина линии отрисовки","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"},
   {"Индикаторный буфер с типом графического построения","Indicator buffer with graphic plot type"},
   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers is incorrect (#property indicator_buffers)"},

...

   {"Расчётный буфер","Calculated buffer"},
   {"Цветной буфер данных","Colored Data buffer"},
   {"Буфер","Buffer"},
   
   {"Сплошная линия","Solid line"},
   {"Прерывистая линия","Broken line"},
   {"Пунктирная линия","Dotted line"},
   {"Штрих-пунктирная линия","Dash-dot line"},
   {"Штрих - две точки","Dash - two points"},
   
  };
//+---------------------------------------------------------------------+

In \MQL5\Include\DoEasy\Defines.mqh, add the macro specifying the maximum possible number of indicator buffer colors that can be set, to the macro substitution section:

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//--- Describe the function with the error line number
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Function description"
#define COUNTRY_LANG                   ("Russian")                // Country language
#define END_TIME                       (D'31.12.3000 23:59:59')   // End date for account history data requests
#define TIMER_FREQUENCY                (16)                       // Minimal frequency of the library timer in milliseconds
#define TOTAL_TRY                      (5)                        // Default number of trading attempts
#define IND_COLORS_TOTAL               (64)                       // Maximum possible number of indicator buffer colors
//--- Standard sounds

This is done to avoid implementing verification of the number of specified colors exceeding 64, especially considering they will possibly be increased someday.

Now let's slightly improve the abstract buffer object class.
The indicator buffers are currently located in the public section of the class in the arrays of the real data type for the data and color index buffers. Remove them to the protected section. Since we may have several indicator construction buffers, create the buffer structure containing a single array.
Why do we need this?
Since the indicator buffer can be represented only by a one-dimensional array of double data, we will implement the structure with a single array in order to create the buffer array (there may be more than one). In this case, we will have all data arrays (indicator buffers), required for constructing the indicator, in the array of such structures. They are to be accessed by the index of the required array.
To avoid going beyond the array by accidentally passing an incorrect index, create the method adjusting passed array indices in case the index points outside the array. In this case, the index is adjusted to specify the very last element of the structure array. In case of a single array, the index points to it.

In the file of the abstract buffer object \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh (in the private section), declare the method returning the adjusted buffer array index,
while in the protected section, declare the structure of buffer arrays, array object of buffers with the structure type, color buffer array and used color array to color the indicator constructions:

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

protected:
//--- Structure for storing buffer object buffer arrays
   struct SDataBuffer { double Array[]; };
//--- Array of (1) all indicator buffer object buffers, (2) color buffer, (3) for storing colors
   SDataBuffer       DataBuffer[];
   double            ColorBufferArray[];
   int               ArrayColors[];
   
public:

Remove the now unnecessary data and color arrays from the public class section:

public:
//--- Array of the (1) drawn indicator buffer and (2) color buffer
   double            DataArray[];
   double            ColorArray[];

In the previous article, I made the protected constructor of the abstract buffer object public so that we are able to access it from the indicator program. Now close it so that it is accessed from the descendant objects of the class when creating indicator buffers of the specified type. Simply uncomment the line and add several new variables for clarifying the created buffer properties to the protected constructor. These properties include the number of buffers used for construction, line width and buffer description in the data window:

protected:
//--- Protected parametric constructor
                     CBuffer(ENUM_BUFFER_STATUS status_buffer,
                             ENUM_BUFFER_TYPE buffer_type,
                             const uint index_plot,
                             const uint index_base_array,
                             const int num_datas,
                             const int width,
                             const string label);
public:  

Now we have the method for setting a single color of the SetColor() indicator buffers. However, since all our buffers are colored, we also need the ability to add a new color to the already existing buffers or set all colors used by the buffer at once.
Declare two additional methods for setting the color:

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

The first method adds a new color to the array of buffer object colors by the specified new color index, while the second method allows setting all colors used by the buffer object and passed to the method in the array.

In the block of the methods for returning buffer object property values, rename the NumberColors() method returning the number of colors used by the buffer and add the method returning the number of buffer arrays used to draw buffer object data:

//--- 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) buffer data period (timeframe) (6) buffer status,
//--- (7) buffer type, (8) buffer usage flag, (9) arrow code, (10) arrow shift for DRAW_ARROW style,
//--- (11) Number of initial bars that are not drawn and values in DataWindow, (12) graphical construction type,
//--- (13) flag of displaying construction values in DataWindow, (14) indicator graphical construction shift along the time axis,
//--- (15) drawing line style, (16) drawing line width, (17) number of colors, (18) drawing color, number of buffers for construction
//--- (19) set empty value, (20) buffer symbol and (21) name of the indicator graphical series displayed in DataWindow
   int               IndexPlot(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT);              }
   int               IndexBase(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE);              }
   int               IndexColor(void)                          const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR);             }
   int               IndexNextBuffer(void)                     const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT);              }
   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);                        }

In the block of methods returning description of buffer object properties, add the method returning the description of specified buffer drawing colors:

//--- Return descriptions of the (1) buffer status, (2) buffer type, (3) buffer usage flag, (4) flag of displaying construction values in DataWindow,
//--- (5) drawing line style, (6) set empty value, (7) graphical construction type and (8) used timeframe and (9) specified colors
   string            GetStatusDescription(bool draw_type=false)const;
   string            GetTypeBufferDescription(void)            const;
   string            GetActiveDescription(void)                const;
   string            GetShowDataDescription(void)              const;
   string            GetLineStyleDescription(void)             const;
   string            GetEmptyValueDescription(void)            const;
   string            GetDrawTypeDescription(void)              const;
   string            GetTimeframeDescription(void)             const;
   string            GetColorsDescription(void)                const;

At the very end of the class body, add the block of methods for working with the buffer object:

//--- Return the size of the data buffer array
   virtual int       GetDataTotal(const uint buffer_index=0)   const;
//--- Return the value from the specified index of the specified (1) data and (2) color buffer arrays
   virtual double    GetDataBufferValue(const uint buffer_index,const uint series_index) const;
   virtual color     GetColorBufferValue(const uint series_index)     const;
//--- Set the value to the specified index of the specified (1) data and (2) color buffer arrays
   virtual void      SetBufferValue(const uint buffer_index,const uint series_index,const double value);
   virtual void      SetBufferColorIndex(const uint series_index,const uchar color_index);
//--- Initialize all object buffers by (1) a specified value, (2) the empty value specified for the object
   virtual void      InitializeBuffers(const double value,const uchar color_index);
   virtual void      InitializeBuffers(void);
//--- Fill all data buffers with empty values in the specified timeseries index
   void              ClearData(const int series_index);
   
  };
//+------------------------------------------------------------------+

Implementation of the methods will be considered later.

The closed parametric class constructor has been changed:

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

//--- If failed to change the size of the drawn 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, display the appropriate message indicating the string
   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 drawn 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,INDICATOR_DATA);
      //--- Set indexation flag as in the timeseries to all buffer arrays
      ::ArraySetAsSeries(this.DataBuffer[i].Array,true);
     }
//--- Binding the color buffer with the array
   if(this.Status()!=BUFFER_STATUS_FILLING)
     {
      ::SetIndexBuffer((int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX);
      ::ArraySetAsSeries(this.ColorBufferArray,true);
     }

//--- Set integer buffer parameters
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_TYPE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_CODE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_BEGIN));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHOW_DATA,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHOW_DATA));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH));
   this.SetColor((color)this.GetProperty(BUFFER_PROP_COLOR));

//--- Set real buffer parameters
   ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE));
//--- Set string buffer parameters
   ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL));
  }
//+------------------------------------------------------------------+

Set the buffer type:

   ENUM_DRAW_TYPE type=
     (
      !this.TypeBuffer() || !this.Status() ? DRAW_NONE      : 
      this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING   : 
      ENUM_DRAW_TYPE(this.Status()+8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                     = type;

The SetDrawType() method described in detail in the previous article allows us to determine the drawing type:

All buffers are to be colored. To set the drawing style, check the buffer status and drawing type passed to the SetDrawType() method and, depending on them, either set no drawing, or set filling the space between the two levels with color, or shift the status enumeration index by 8 units so that the constant value corresponds to that of the color buffer from the drawing style enumeration.
Beyond the class body, implement the method:

//+------------------------------------------------------------------+
//| Set the graphical construction type                              |
//+------------------------------------------------------------------+
void CBuffer::SetDrawType(void)
  {
   ENUM_DRAW_TYPE type=(!this.TypeBuffer() || !this.Status() ? DRAW_NONE : this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING : ENUM_DRAW_TYPE(this.Status()+8));
   this.SetProperty(BUFFER_PROP_DRAW_TYPE,type);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,type);
  }
//+------------------------------------------------------------------+

If the buffer type is BUFFER_TYPE_CALCULATE (0) or BUFFER_STATUS_NONE (0), the drawing style is set to "No drawing". If the buffer status is BUFFER_STATUS_FILLING (filling with color), the appropriate drawing style is set. All the remaining values are simply increased by 8. This shift indicates the constant of the colored drawing style.

Here in the class constructor, we use the same method of calculating the buffer type.
The SetDrawType() method remains within the class for now. Most likely, it will be removed altogether since it is only necessary when creating an object. There is no need to change the drawing type further on.
It will stay temporarily in case some other way of using it is found soon.


The line width is now passed in the constructor parameters, therefore the value passed to the constructor is set as the default one while the number of default colors for all graphical construction types are set to one excluding DRAW_FILLING — only two colors are always used in this drawing type. Let's set them:

   this.m_long_prop[BUFFER_PROP_LINE_WIDTH]                    = width;
   this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]                 = (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2);

The number of drawing data buffers is now also passed in the constructor parameters. Let's add them:

   this.m_long_prop[BUFFER_PROP_NUM_DATAS]                     = num_datas;
   this.m_long_prop[BUFFER_PROP_INDEX_PLOT]                    = index_plot;
   this.m_long_prop[BUFFER_PROP_INDEX_BASE]                    = index_base_array;
   this.m_long_prop[BUFFER_PROP_INDEX_COLOR]                   = this.GetProperty(BUFFER_PROP_INDEX_BASE)+this.GetProperty(BUFFER_PROP_NUM_DATAS);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT]                    = this.GetProperty(BUFFER_PROP_INDEX_COLOR)+(this.Status()!=BUFFER_STATUS_FILLING ? 1 : 0);

In order to calculate the index of the next array to assign it as the first data buffer array in the next buffer object, we need to consider that the buffer of the DRAW_FILLING graphical construction type does not apply the color buffer. Therefore, the color buffer should be excluded from the buffer with such a drawing type which is exactly what I do here — for all drawing types, add one to the calculated color buffer index value, while in case of "Color filling" add — the index of the next buffer is already equal to the already calculated index of the color buffer not present in the object.

The remaining actions are described in the comments to the code.
Colors are now set using the appropriate method, since colors are set to object properties directly in the method. Besides, the color array subsequently used to obtain the desired color by index is filled:

//--- Set integer buffer parameters
   ::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));

Returning the width description and color line of the indicator has been changed in the method returning the description of integer properties:

//+------------------------------------------------------------------+
//| Return description of a buffer's integer property                |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property)
  {
   return
     (
   // ... Removed unnecessary strings
   // ...
   // ...

      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_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_COLOR         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetColorsDescription()
         )  :
      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)
         )  :
   
   // ...
   // ...
   // ... Removed unnecessary strings

      ""
     );
  }
//+------------------------------------------------------------------+

To return the line width description, the buffer status is checked. If this is an arrow indicator, there are no lines in it — the arrow size entry is returned.
To return the color, the GetColorsDescription() method is now used. The method returns the description of all used buffer colors. The buffer itself is discussed below.

The method of returning the description of a specified empty value now considers negative EMPTY_VALUE set as the buffer's "Empty value":

//+------------------------------------------------------------------+
//| Return description of the set empty value                        |
//+------------------------------------------------------------------+
string CBuffer::GetEmptyValueDescription(void) const
  {
   double value=fabs(this.EmptyValue());
   return(value<EMPTY_VALUE ? ::DoubleToString(this.EmptyValue(),(this.EmptyValue()==0 ? 1 : 8)) : (this.EmptyValue()>0 ? "EMPTY_VALUE" : "-EMPTY_VALUE"));
  }
//+------------------------------------------------------------------+

The method returning the description of colors set for the buffer object:

//+------------------------------------------------------------------+
//| Return description of specified colors                           |
//+------------------------------------------------------------------+
string CBuffer::GetColorsDescription(void) const
  {
   int total=this.ColorsTotal();
   if(total==1)
      return ::ColorToString(this.Color(),true);
   string res="";
   for(int i=0;i<total;i++)
     {
      res+=::ColorToString(this.ArrayColors[i],true)+(i<total-1 ? "," : "");
     }
   return res;
  }
//+------------------------------------------------------------------+

If only one color is set, the string description of the buffer object property storing the color value is returned,
if there are several colors contained in the color array, the string consisting of their descriptions is formed in a loop and the obtained value is returned.

The method returning the number of buffer object colors:

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

If more than the maximum possible amount is passed to the method, exit the method.
If the line drawing type is not DRAW_FILLING, the number is set equal to the passed one, otherwise, it is set to two.
Then change the size of the color array and set the new value for the buffer.

The method setting one drawing color for the buffer object:

//+------------------------------------------------------------------+
//| Set a single specified drawing color for the buffer              |
//+------------------------------------------------------------------+
void CBuffer::SetColor(const color colour)
  {
   if(this.Status()==BUFFER_STATUS_FILLING)
      return;
   this.SetColorNumbers(1);
   this.SetProperty(BUFFER_PROP_COLOR,colour);
   this.ArrayColors[0]=colour;
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_COLOR,0,this.ArrayColors[0]);
  }
//+------------------------------------------------------------------+

If the drawing style is DRAW_FILLING, exit the method — this drawing style always features two colors.
Next, set the number of colors equal to one, set the passed color to the buffer object property, write the passed color to the only color array cell and set the color for the indicator buffer.

To re-draw the indicator lines, set the color index (from the list of used colors) to the necessary timeseries index where the color should be changed.
For example, if we use three colors, they have indices 0,1,2. To color the indicator line in any of the three colors, simply write the number of the required color to the color buffer. If index 0 stores blue color, while index 1 stores red color, write 0 for blue or 1 for red to the indicator color buffer to re-draw the indicator line.
To be able to change colors set for the buffer object, we need the method writing a new color to the specified indicator buffer color index.

The method setting the drawing color for the specified color index:

//+------------------------------------------------------------------+
//| Set the drawing color to the specified color index               |
//+------------------------------------------------------------------+
void CBuffer::SetColor(const color colour,const uchar index)
  {
   if(index>IND_COLORS_TOTAL-1)
      return;
   if(index>this.ColorsTotal()-1)
      this.SetColorNumbers(index+1);
   this.ArrayColors[index]=colour;
   if(index==0)
      this.SetProperty(BUFFER_PROP_COLOR,(color)this.ArrayColors[0]);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_COLOR,index,this.ArrayColors[index]);
  }
//+------------------------------------------------------------------+

The method gets the color and the color index, by which it is written.
Exit if the passed color index exceeds the maximum available nuber of indicator colors.
If the index exceeds the already existing number of buffer object colors,set the new number of buffer object colors.
Next, write the passed color to the color array by the specified index. If the zero index is passed, set the color passed to the method to the buffer color property and finally set the new color value to the indicator buffer by the index passed to the method.

The method setting colors for the buffer object from the passed color array:

//+------------------------------------------------------------------+
//| Set drawing colors from the color array                          |
//+------------------------------------------------------------------+
void CBuffer::SetColors(const color &array_colors[])
  {
//--- Exit if the passed array is empty
   if(::ArraySize(array_colors)==0)
      return;
//--- Copy the passed array to the array of buffer object colors
   ::ArrayCopy(this.ArrayColors,array_colors,0,0,IND_COLORS_TOTAL);
//--- Exit if the color array was empty and not copied for some reason
   int total=::ArraySize(this.ArrayColors);
   if(total==0)
      return;
//--- If the drawing style is not DRAW_FILLING
   if(this.Status()!=BUFFER_STATUS_FILLING)
     {
      //--- if the new number of colors exceeds the currently set one, 
      //--- set the new value for the number of colors
      if(total>this.ColorsTotal())
         this.SetColorNumbers(total);
     }
   //--- If the drawing style is DRAW_FILLING, set the number of colors equal to 2
   else
      total=2;
//--- Set the very first color from the color array (for a single color) to the buffer object color property
   this.SetProperty(BUFFER_PROP_COLOR,(color)this.ArrayColors[0]);
//--- Set the new number of colors for the indicator buffer
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_COLOR_INDEXES,total);
//--- In the loop by the new number of colors, set all colors by their indices for the indicator buffer
   for(int i=0;i<total;i++)
      ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_COLOR,i,this.ArrayColors[i]);
  }
//+------------------------------------------------------------------+

All method strings are described in comments in detail.

The method returning the adjusted index of the required buffer array:

//+------------------------------------------------------------------+
//| Return the adjusted buffer array index                           |
//+------------------------------------------------------------------+
int CBuffer::GetCorrectIndexBuffer(const uint buffer_index) const
  {
   return int(buffer_index<this.GetProperty(BUFFER_PROP_NUM_DATAS) ? buffer_index : this.GetProperty(BUFFER_PROP_NUM_DATAS)-1);
  }
//+------------------------------------------------------------------+

Since all data arrays for indicator buffers are now located in the DataBuffer[] array with the SDataBuffer structure type, we will use the method adjusting an incorrectly passed array index to avoid going beyond the array limits.
If the passed index is less than the total number of buffers used for construction, the passed index is returned.
In any other case, the index of the very last array element is returned.
In case of a single array, the very last element is equal to the very first one.

The method returning the size of the specified data buffer array:

//+------------------------------------------------------------------+
//| Return the array size of the specified data buffer               |
//+------------------------------------------------------------------+
int CBuffer::GetDataTotal(const uint buffer_index=0) const
  {
    int index=this.GetCorrectIndexBuffer(buffer_index);
    return(index>WRONG_VALUE ? ::ArraySize(this.DataBuffer[index].Array) : 0);
  }
//+------------------------------------------------------------------+

The method is rather applied for debugging and internal use.
Return the size of the array assigned as the indicator buffer by its index in the DataBuffer[] array.
OnInit() always receives zero. OnCalculate() receives rates_total.

The method returning the specified data buffer value by the specified timeseries index:

//+------------------------------------------------------------------+
//| Return the value from the specified timeseries index             |
//| of the specified data buffer array                               |
//+------------------------------------------------------------------+
double CBuffer::GetDataBufferValue(const uint buffer_index,const uint series_index) const
  {
   int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index);
   int data_total=this.GetDataTotal(correct_buff_index);
   if(data_total==0)
      return this.EmptyValue();
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   return this.DataBuffer[correct_buff_index].Array[data_index];
  }
//+------------------------------------------------------------------+

The method is used to get and receive data by the timeseries index from the specified indicator buffer.
First, adjust the obtained index of the required buffer, check the amount of data in the buffer, and if the data is absent, return the empty value set for the buffer object. Next, check if the timeseries index goes beyond the amount of data in the buffer. If the timeseries index goes beyond the array limits, return the very last buffer element, otherwise return data from the specified buffer by the timeseries index.

The method returning the color buffer value by the specified timeseries index:

//+------------------------------------------------------------------+
//| Return the value from the specified timeseries index             |
//| of the specified color buffer array                              |
//+------------------------------------------------------------------+
color CBuffer::GetColorBufferValue(const uint series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0)
      return clrNONE;
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   int color_index=(this.ColorsTotal()==1 ? 0 : (int)this.ColorBufferArray[data_index]);
   return (color)this.ArrayColors[color_index];
  }
//+------------------------------------------------------------------+

The method is almost identical to the one just considered, except that we always have one color buffer, and we do not need to get and adjust the index of the desired buffer. Here we immediately check the timeseries index and return data from the buffer by the adjusted index (if an invalid index has been passed).

The method setting the value to the specified data buffer by the specified timeseries index:

//+------------------------------------------------------------------+
//| Set the value to the specified timeseries index                  |
//| for the specified data buffer array                              |
//+------------------------------------------------------------------+
void CBuffer::SetBufferValue(const uint buffer_index,const uint series_index,const double value)
  {
   if(this.GetDataTotal(buffer_index)==0)
      return;
   int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index);
   int data_total=this.GetDataTotal(buffer_index);
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   this.DataBuffer[correct_buff_index].Array[data_index]=value;
  }
//+------------------------------------------------------------------+

The method is identical to the method of obtaining data from the specified buffer by the timeseries index considered above. However, instead of returning the value, the method writes the passed value to the specified data buffer by the specified timeseries index.

The method setting the value for the color buffer by the specified timeseries index:

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

The method is identical to the one for obtaining the color index from the specified timeseries index. However, instead of returning the value, the method writes the passed color index value to the color buffer by the specified timeseries index.
Besides, if the data array has no appropriate data (it is empty), or the passed color index exceeds the color array, or the buffer object drawing style is DRAW_FILLING (no color buffer), leave the method.
If the color buffer size is zero, the values set in #property indicator_buffers have most probably been set incorrectly — the appropriate message is displayed followed by the error of exiting beyond the array limits which is not handled deliberately since the journal entries allow us to set the number of buffers in the indicator properties correctly.

The method of initializing all buffer object buffers by the specified data and color buffer values:

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

Initialize each next array with the specified value in the loop by the full list of all data buffers.
If the buffer object drawing style is not DRAW_FILLING, initialize the color buffer with the specified value adjusted in case of an exit beyond the color array limits.

The method of initializing all buffer object buffer arrays by the "empty" value specified for the buffer object:

//+------------------------------------------------------------------+
//| Initialize all object buffers                                    |
//| by the empty value set for the object                            |
//+------------------------------------------------------------------+
void CBuffer::InitializeBuffers(void)
  {
   for(int i=0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++)
      ::ArrayInitialize(this.DataBuffer[i].Array,this.EmptyValue());
   if(this.Status()!=BUFFER_STATUS_FILLING)
      ::ArrayInitialize(this.ColorBufferArray,0);
  }
//+------------------------------------------------------------------+

The method is identical to the one considered above except that the initialization value is taken from the "empty" value set for the object. Also, the very first color index is used for initialization.

It is often required to clear the values of all buffers in indicators before filling them to avoid graphical artifacts.
The next method clears all buffers in the specified timeseries index.
The method filling all indicator buffers of the buffer object with the empty value:

//+------------------------------------------------------------------+
//| Fill all data buffers with the empty value                       |
//| in the specified timeseries index                                |
//+------------------------------------------------------------------+
void CBuffer::ClearData(const int series_index)
  {
   for(int i=0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++)
      this.SetBufferValue(i,series_index,this.EmptyValue());
   this.SetBufferColorIndex(series_index,0);
  }
//+------------------------------------------------------------------+

In the loop by all existing buffer arrays of the buffer object, write the empty value set for the buffer object by the specified timeseries index using the SetBufferValue() method considered above. The SetBufferColorIndex() method, which has also been mentioned above, allows us to set the color index to the specified timeseries index.

This concludes the changes in the abstract buffer object class aimed at creating and working with descendant objects of the class.

Buffer indicator objects — descendants of the abstract buffer object

As is often the case, almost all library objects have a similar structure: there is a general abstract object with properties and methods shared by its descendants, while each descendant object specifies the abstract object data. I have considered the concept of library objects in the first (abstract object) and second articles (descendant objects and their collection).

Buffer objects are not exception. Each such object is an indicator buffer with a drawing style specific to it. Objects are named based on their drawing styles.

The first buffer object has the Arrows drawing style and is named accordingly.

In \MQL5\Include\DoEasy\Objects\Indicators\, create BufferArrow.mqh file of the CBufferArrow class whose base class is the object of the CBuffer abstract buffer:

//+------------------------------------------------------------------+
//|                                                  BufferArrow.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Buffer.mqh"
//+------------------------------------------------------------------+
//| Buffer with the "Drawing with arrows" drawing style              |
//+------------------------------------------------------------------+
class CBufferArrow : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferArrow(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_ARROW,BUFFER_TYPE_DATA,index_plot,index_base_array,1,1,"Arrows") {}
//--- Supported integer properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property);
//--- Supported real properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property);
//--- Supported string properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property);
//--- Display a short buffer description in the journal
   virtual void      PrintShort(void);
  };
//+------------------------------------------------------------------+

As we can see, the class listing contains nothing extra.
The virtual methods returning the flags of object supporting integer, real and string properties, as well as the virtual method displaying the short buffer description are declared.
The class constructor receives the index of the created drawn indicator buffer (the buffer serial number in the list of all indicator buffers) and the index of the base buffer array the first double array of the created new buffer is actually connected to.
The constructor initialization list features the initialization of the protected constructor of the parent class:

CBufferArrow(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_ARROW,BUFFER_TYPE_DATA,index_plot,index_base_array,1,1,"Arrows") {}

where the first parameter is the "Buffer of the "Drawing with arrows" style" buffer status, the second one is the "Color data buffer" buffer type. Also, pass the values of the drawn buffer index and the base array, the number of buffers for construction, the line width (here the parameter indicates the arrow size) and the name of the graphical series displayed in the data window. Thus, when creating a new buffer object, we inform the base class of the type of the created buffer and its parameters.

The method returning the flag indicating the buffer support of integer properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CBufferArrow::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if(property==BUFFER_PROP_LINE_STYLE || (this.TypeBuffer()==BUFFER_TYPE_CALCULATE && property!=BUFFER_PROP_TYPE && property!=BUFFER_PROP_INDEX_NEXT))
      return false;
   return true;
  }
//+------------------------------------------------------------------+

If the "Line style" property is passed to the method or this is a calculated buffer which is not the "buffer type" or "next drawn buffer index" property, it is not supported and false is returned. In other cases, true is returned.

The method returning the flag indicating the buffer support for real properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CBufferArrow::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return false;
   return true;
  }
//+------------------------------------------------------------------+

If this is a calculated buffer, no real properties are supported (there is only one real property: "Empty value for plotting where nothing will be drawn") — return false. In other cases, return true.

The method returning the flag indicating the buffer support for string properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| string property, otherwise return 'false'                        |
//+------------------------------------------------------------------+
bool CBufferArrow::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return false;
   return true;
  }
//+------------------------------------------------------------------+

If this is a calculated buffer, no string properties are supported — return false. In other cases, return true.

The method for displaying a short buffer object description to the journal:

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

The method creates the string from the Buffer header specifying the index of the drawn buffer (buffer serial number in the DataWindow window) describing the buffer status (drawing style), symbol and timeframe.
For example, in the form of:

Buffer(0): Drawing with arrows EURUSD H1

This is the entire buffer object class of the "Drawing with arrows" type.


Create the new descendant class of the CBufferLine abstract buffer object of the Line drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferLine.mqh:

//+------------------------------------------------------------------+
//|                                                   BufferLine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Buffer.mqh"
//+------------------------------------------------------------------+
//| Buffer of the Line drawing style                                 |
//+------------------------------------------------------------------+
class CBufferLine : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferLine(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_LINE,BUFFER_TYPE_DATA,index_plot,index_base_array,1,1,"Line") {}
//--- Supported integer properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property);
//--- Supported real properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property);
//--- Supported string properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property);
//--- Display a short buffer description in the journal
   virtual void      PrintShort(void);
  };
//+------------------------------------------------------------------+
//| 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)
     ) 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)
      return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Display short buffer description in the journal                  |
//+------------------------------------------------------------------+
void CBufferLine::PrintShort(void)
  {
   ::Print
     (
      CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_BUFFER),"(",(string)this.IndexPlot(),"): ",
      this.GetStatusDescription(true)," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())
     );
  }
//+------------------------------------------------------------------+

In comparison with the deal buffer class, in the class constructor, the Line status and Line graphical series name are passed to the base buffer object constructor in the initialization list. The rest of the parameters remain the same as for arrow buffer object:

CBufferLine(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_LINE,BUFFER_TYPE_DATA,index_plot,index_base_array,1,1,"Line") {}

The method returning the flag indicating the buffer support of integer properties has been changed as well:

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

If this is the "arrow code" or "vertical arrow shift" property, or a calculated buffer which is not the "buffer type" or "next drawn buffer index" property, it is not supported and false is returned. In other cases, true is returned.


Create the new descendant class of the CBufferSection abstract buffer object of the Section drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferSection.mqh.
Only constructors of new classes and methods of supporting integer properties are considered further, since everything else is identical to the two described classes.

In the class constructor, the Section status and Section graphical series name are passed to the base buffer object constructor in the initialization list.

CBufferSection(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_SECTION,BUFFER_TYPE_DATA,index_plot,index_base_array,1,1,"Section") {}

The method returning the flag indicating the buffer support of integer properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+ 
bool CBufferSection::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)
     ) return false;
   return true; 
  }
//+------------------------------------------------------------------+

If this is the "arrow code" or "vertical arrow shift" property, or a calculated buffer which is not the "buffer type" or "next drawn buffer index" property, it is not supported and false is returned. In other cases, true is returned.


Create the new descendant class of the CBufferHistogram abstract buffer object of the "Histogram from the zero line" drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram.mqh.

In the class constructor, the "Histogram from the zero line" status and Histogram graphical series name are passed to the base buffer object constructor in the initialization list, while the histogram line width is equal to 2 by default.

CBufferHistogram(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_HISTOGRAM,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,"Histogram") {}

The remaining methods are identical to the Section buffer object class.


Create the new descendant class of the CBufferHistogram2 abstract buffer object of the "Histogram on two indicator buffers" drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram2.mqh.

In the class constructor, the "Histogram on two indicator buffers" status, "Histogram2 0;Histogram2 1" graphical series name and two construction buffers are passed to the base buffer object constructor in the initialization list. The histogram line width is equal to 8 by default.

CBufferHistogram2(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_HISTOGRAM2,BUFFER_TYPE_DATA,index_plot,index_base_array,2,8,"Histogram2 0;Histogram2 1") {}

Why is the histogram line width equal to 8?
In MQL5, namely, in the histogram indicators based on two buffers, this is the maximum histogram column width size depending on the horizontal chart scale:


At the maximum chart scale (5), the histogram column width has the maximum size. If you need a smaller size, set the new width value using the SetWidth() method after creating the buffer.

Why does the "Histogram2 0;Histogram2 1" graphical series have such a name?
In indicators applying several buffers for their construction, the name of each buffer is specified in the graphical series name. If you need to set a unique name for each buffer, separate them by semicolons (;) when specifying the graphical series name.
Here, the name "Histogram2 0" is set for the first buffer (zero specifies the first buffer index), while the second buffer is named "Histogram2 1" (one indicates the second buffer index).
This looks as follows in DataWindow:


If necessary, new buffer names can be set using the SetLabel() method after creating the buffer.

The remaining methods are identical to the Section buffer object class.


Create the new descendant class of the CBufferZigZag abstract buffer object of the Zigzag drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferZigZag.mqh.

In the class constructor, the Zigzag status, "ZigZag 0;ZigZag 1" graphical series name and two construction buffers are passed to the base buffer object constructor in the initialization list. The zigzag line width is equal to 1 by default.

CBufferZigZag(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_ZIGZAG,BUFFER_TYPE_DATA,index_plot,index_base_array,2,1,"ZigZag 0;ZigZag 1") {}

The remaining methods are identical to the Section buffer object class.


Create the new descendant class of the CBufferFilling abstract buffer object of the "Color filling between two levels" drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferFilling.mqh.

In the class constructor, the "Color filling between two levels" status, "Filling 0;Filling 1" graphical series name and two construction buffers are passed to the base buffer object constructor in the initialization list. The line width is equal to 1 by default (there are no lines in this drawing style, so this property is irrelevant).

CBufferFilling(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_FILLING,BUFFER_TYPE_DATA,index_plot,index_base_array,2,1,"Filling 0;Filling 1") {}

The method returning the flag indicating the buffer support of integer properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+ 
bool CBufferFilling::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if((property==BUFFER_PROP_ARROW_CODE || property==BUFFER_PROP_ARROW_SHIFT) || property==BUFFER_PROP_LINE_STYLE || 
       property==BUFFER_PROP_LINE_WIDTH || property==BUFFER_PROP_INDEX_COLOR ||
      (this.TypeBuffer()==BUFFER_TYPE_CALCULATE && property!=BUFFER_PROP_TYPE && property!=BUFFER_PROP_INDEX_NEXT)
     ) return false;
   return true; 
  }
//+------------------------------------------------------------------+

If this is the "arrow code", "vertical arrow shift", "line style", "line width" property, color buffer index or a calculated buffer which is not the "buffer type" or "next drawn buffer index" property, it is not supported and false is returned. In other cases, true is returned.

The remaining methods are identical to the Section buffer object class.


Create the new descendant class of the CBufferBars abstract buffer object of the "Display as bars" drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferBars.mqh.

In the class constructor, the "Display as bars" status, "Bar Open;Bar High;Bar Low;Bar Close" graphical series name and four construction buffers are passed to the base buffer object constructor in the initialization list. The bar width is equal to 2 by default.

CBufferBars(const uint index_plot,const uint index_base_array) :
   CBuffer(BUFFER_STATUS_BARS,BUFFER_TYPE_DATA,index_plot,index_base_array,4,2,"Bar Open;Bar High;Bar Low;Bar Close") {}

The method returning the flag indicating the buffer support of integer properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+ 
bool CBufferBars::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if((property==BUFFER_PROP_ARROW_CODE || property==BUFFER_PROP_ARROW_SHIFT) || property==BUFFER_PROP_LINE_STYLE ||
      (this.TypeBuffer()==BUFFER_TYPE_CALCULATE && property!=BUFFER_PROP_TYPE && property!=BUFFER_PROP_INDEX_NEXT)
     ) return false;
   return true; 
  }
//+------------------------------------------------------------------+

If this is the "arrow code", "vertical arrow shift" or "line style" property, or a calculated buffer which is not the "buffer type" or "next drawn buffer index" property, it is not supported and false is returned. In other cases, true is returned.

The remaining methods are identical to the Section buffer object class.


Create the new descendant class of the CBufferCandles abstract buffer object of the "Display as candles" drawing style in \MQL5\Include\DoEasy\Objects\Indicators\BufferCandles.mqh.

In the class constructor, the ""Display as candles" status and "Candle Open;Candle High;Candle Low;Candle Close" graphical session name and four construction buffers are passed to the base buffer object constructor in the initialization list. The candle width is equal to 1 by default (the parameter does not affect the real drawing width, as the candle width is always equal to the chart candle width depending on its horizontal scale).

CBufferCandles(const uint index_plot,const uint index_base_array) : 
   CBuffer(BUFFER_STATUS_CANDLES,BUFFER_TYPE_DATA,index_plot,index_base_array,4,1,"Candle Open;Candle High;Candle Low;Candle Close") {}

The method returning the flag indicating the buffer support of integer properties:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+ 
bool CBufferCandles::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if((property==BUFFER_PROP_ARROW_CODE || property==BUFFER_PROP_ARROW_SHIFT) ||  
       property==BUFFER_PROP_LINE_STYLE || property==BUFFER_PROP_LINE_WIDTH   ||
      (this.TypeBuffer()==BUFFER_TYPE_CALCULATE && property!=BUFFER_PROP_TYPE && property!=BUFFER_PROP_INDEX_NEXT)
     ) return false;
   return true; 
  }
//+------------------------------------------------------------------+

If this is the "arrow code", "vertical arrow shift", "line style" or "line width" property, or this is a calculated buffer which is not the "buffer type" or "next drawn buffer index" property, it is not supported and false is returned. In other cases, true is returned.

The remaining methods are identical to the Section buffer object class.

All buffer object classes of the abstract buffer object descendants have been created.
Find the full listing of the classes in the attached files below.

Rather than introducing buttons for managing the display of buffer lines on the chart, let's set the necessary graphical constructions in the indicator settings instead. We are able to select, which buffers to display, and launch the indicator.

Testing

To be able to select the necessary graphical constructions, add new enumerations to the input enumeration file \MQL5\Include\DoEasy\InpDatas.mqh for English and Russian compilation languages:

//+------------------------------------------------------------------+
//|                                                     InpDatas.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define COMPILE_EN // Comment out the string for compilation in Russian 
//+------------------------------------------------------------------+
//| Input enumerations                                               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| English language inputs                                          |
//+------------------------------------------------------------------+
#ifdef COMPILE_EN
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work only with the current Symbol
   SYMBOLS_MODE_DEFINES,                              // Work with a given list of Symbols
   SYMBOLS_MODE_MARKET_WATCH,                         // Working with Symbols from the "Market Watch" window
   SYMBOLS_MODE_ALL                                   // Work with a complete list of Symbols
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Work only with the current timeframe
   TIMEFRAMES_MODE_LIST,                              // Work with a given list of timeframes
   TIMEFRAMES_MODE_ALL                                // Work with a complete list of timeframes
  };
//+------------------------------------------------------------------+
//| "Yes"/"No"                                                       |
//+------------------------------------------------------------------+
enum ENUM_INPUT_YES_NO
  {
   INPUT_NO  = 0,                                     // No
   INPUT_YES = 1                                      // Yes
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Russian language inputs                                          |
//+------------------------------------------------------------------+
#else  
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Работа только с текущим символом
   SYMBOLS_MODE_DEFINES,                              // Работа с заданным списком символов
   SYMBOLS_MODE_MARKET_WATCH,                         // Работа с символами из окна "Обзор рынка"
   SYMBOLS_MODE_ALL                                   // Работа с полным списком символов
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Работа только с текущим таймфреймом
   TIMEFRAMES_MODE_LIST,                              // Работа с заданным списком таймфреймов
   TIMEFRAMES_MODE_ALL                                // Работа с полным списком таймфреймов
  };
//+------------------------------------------------------------------+
//| "Yes"/"No"                                                       |
//+------------------------------------------------------------------+
enum ENUM_INPUT_YES_NO
  {
   INPUT_NO  = 0,                                     // Нет
   INPUT_YES = 1                                      // Да
  };
//+------------------------------------------------------------------+
#endif 
//+------------------------------------------------------------------+

This provides the choice in the settings:


To perform the test, we will use the indicator from the previous article and save it in \MQL5\Indicators\TestDoEasy\Part43\ under the name TestDoEasyPart43.mq5.

Include the files of all created buffers to the indicator file, set the number of used buffers to 27, while the number of drawn buffers is set to 9:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart43.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <DoEasy\Objects\Indicators\BufferArrow.mqh>        // 1 construction buffer + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferLine.mqh>         // 1 construction buffer + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferSection.mqh>      // 1 construction buffer + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferHistogram.mqh>    // 1 construction buffer + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferHistogram2.mqh>   // 2 construction buffers + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferZigZag.mqh>       // 2 construction buffers + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferFilling.mqh>      // 2 construction buffers + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferBars.mqh>         // 4 construction buffer + 1 color buffer
#include <DoEasy\Objects\Indicators\BufferCandles.mqh>      // 4 construction buffer + 1 color buffer
//--- In total: 18 construction buffers + 9 color buffers:       27 indicator buffers, of which 9 are drawn ones
//--- properties
#property indicator_chart_window
#property indicator_buffers 27
#property indicator_plots   9 

In the inputs block, comment out sinput modifiers for selecting the mode of working with symbols (the current one is used by default), hide the list of used symbols from the settings, set the current working timeframe selection mode by default, hide the list of used timeframes from the settings and add the ability to select, which buffers should be displayed on the chart in the settings:

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
/*sinput*/ ENUM_SYMBOLS_MODE  InpModeUsedSymbols=  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
/*sinput*/   string               InpUsedSymbols    =  "EURUSD,AUDUSD,EURAUD,EURGBP,EURCAD,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
/*sinput*/   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_CURRENT;            // Mode of used timeframes list
/*sinput*/   string               InpUsedTFs        =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   ENUM_INPUT_YES_NO    InpDrawArrow      =  INPUT_YES;  // Draw Arrow
sinput   ENUM_INPUT_YES_NO    InpDrawLine       =  INPUT_NO;   // Draw Line
sinput   ENUM_INPUT_YES_NO    InpDrawSection    =  INPUT_NO;   // Draw Section
sinput   ENUM_INPUT_YES_NO    InpDrawHistogram  =  INPUT_NO;   // Draw Histogram
sinput   ENUM_INPUT_YES_NO    InpDrawHistogram2 =  INPUT_NO;   // Draw Histogram2
sinput   ENUM_INPUT_YES_NO    InpDrawZigZag     =  INPUT_NO;   // Draw ZigZag
sinput   ENUM_INPUT_YES_NO    InpDrawFilling    =  INPUT_NO;   // Draw Filling
sinput   ENUM_INPUT_YES_NO    InpDrawBars       =  INPUT_NO;   // Draw Bars
sinput   ENUM_INPUT_YES_NO    InpDrawCandles    =  INPUT_YES;  // Draw Candles
 
sinput   bool                 InpUseSounds      =  true; // Use sounds

Instead of declaring double arrays of indicator buffers, add declaration of the dynamic array of pointers to CObject instances. The list is to store indicator buffer objects created for testing:

//--- indicator buffers
CArrayObj      list_buffers;                    // Temporary list for storing buffer objects
//--- global variables
CEngine        engine;                          // CEngine library main object
string         prefix;                          // Prefix of graphical object names
int            min_bars;                        // The minimum number of bars for the indicator calculation
int            used_symbols_mode;               // Mode of working with symbols
string         array_used_symbols[];            // The array for passing used symbols to the library
string         array_used_periods[];            // The array for passing used timeframes to the library
//+------------------------------------------------------------------+

In the OnInit() handler of the indicator, create all buffer objects, add them to the list, set non-default colors (three colors) and display the data on each created buffer object in the journal:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialize DoEasy library
   OnInitDoEasy();

//--- Set indicator global variables
   prefix=engine.Name()+"_";
   //--- Get the index of the maximum used timeframe in the array,
   //--- 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 index=ArrayMaximum(ArrayUsedTimeframes);
   int num_bars=NumberBarsInTimeframe(ArrayUsedTimeframes[index]);
   min_bars=(index>WRONG_VALUE ? (num_bars>2 ? num_bars : 2) : 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 buffer objects
   CBuffer *buffer0=new CBufferArrow(0,0);
   CBuffer *buffer1=new CBufferLine(1,buffer0.IndexNextBuffer());
   CBuffer *buffer2=new CBufferSection(2,buffer1.IndexNextBuffer());
   CBuffer *buffer3=new CBufferHistogram(3,buffer2.IndexNextBuffer());
   CBuffer *buffer4=new CBufferHistogram2(4,buffer3.IndexNextBuffer());
   CBuffer *buffer5=new CBufferZigZag(5,buffer4.IndexNextBuffer());
   CBuffer *buffer6=new CBufferFilling(6,buffer5.IndexNextBuffer());
   CBuffer *buffer7=new CBufferBars(7,buffer6.IndexNextBuffer());
   CBuffer *buffer8=new CBufferCandles(8,buffer7.IndexNextBuffer());
//--- Add buffers to the list of indicator buffers
   list_buffers.Add(buffer0);
   list_buffers.Add(buffer1);
   list_buffers.Add(buffer2);
   list_buffers.Add(buffer3);
   list_buffers.Add(buffer4);
   list_buffers.Add(buffer5);
   list_buffers.Add(buffer6);
   list_buffers.Add(buffer7);
   list_buffers.Add(buffer8);
//--- Create color array
   color array_colors[]={clrDodgerBlue,clrRed,clrGray};
//--- Set non-default buffer color values
   for(int i=0;i<list_buffers.Total();i++)
     {
      CBuffer *buff=list_buffers.At(i);
      buff.SetColors(array_colors);
      //--- Print data on the next buffer
      buff.Print();
     }
//--- Set the label size for the arrow buffer and the line width for ZigZag
   buffer0.SetWidth(2);
   buffer5.SetWidth(2);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

In the OnCalculate() handler
When initializing or changing historical data, initialize all created buffers and display their short names
.
On a new bar or the current tick, write price values to the buffers:
a single buffer
— Close price,
two buffers
— Open for the first one, and Close for the second one
four
buffers write appropriate OHLC prices to each buffer:

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

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
   
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the library timer
      EventsHandling();             // Working with library events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Set OnCalculate arrays as timeseries
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);

//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- limit > 1 means the first launch or changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      //--- In a loop by the number of buffers in the list
      for(int i=0;i<list_buffers.Total();i++)
        {
         //--- get the next buffer and display the type of its graphical construction to the journal
         //--- together with the double array assigned to the buffer (if all is correct, the size is equal to rates_total)
         CBuffer *buff=list_buffers.At(i);
         buff.PrintShort();
         buff.InitializeBuffers();
        }
      limit=rates_total-1;
     }
//--- Prepare data

//--- Calculate the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- In a loop by the number of buffers in the list
      for(int j=0;j<list_buffers.Total();j++)
        {
         //--- get the next buffer and
         CBuffer *buff=list_buffers.At(j);
         //--- clear its current data
         buff.ClearData(0);
         //--- If drawing is not used, move on to the next one
         if(!IsUse(buff.Status()))
            continue;
         //--- Depending on the number of buffers, fill them with price data
         //--- one buffer
         if(buff.BuffersTotal()==1)
            buff.SetBufferValue(0,i,close[i]);
         //--- two buffers - the first one is to store the bar open price, while the second one is to store the bar close price
         else if(buff.BuffersTotal()==2)
           {
            buff.SetBufferValue(0,i,open[i]);
            buff.SetBufferValue(1,i,close[i]);
           }
         //--- four buffers - each buffer is to store OHLC bar prices
         else if(buff.BuffersTotal()==4)
           {
            buff.SetBufferValue(0,i,open[i]);
            buff.SetBufferValue(1,i,high[i]);
            buff.SetBufferValue(2,i,low[i]);
            buff.SetBufferValue(3,i,close[i]);
           }
         //--- Set the buffer color depending on the candle direction
         if(open[i]<close[i])
            buff.SetBufferColorIndex(i,0);
         else if(open[i]>close[i])
            buff.SetBufferColorIndex(i,1);
         else
            buff.SetBufferColorIndex(i,2);
        }
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Let's use the following function to define if drawing the indicator lines is enabled:

//+---------------------------------------------------------------------+
//| Return the flag indicating the appropriate drawing style is enabled |
//+---------------------------------------------------------------------+
bool IsUse(const ENUM_BUFFER_STATUS status)
  {
   switch(status)
     {
      case BUFFER_STATUS_FILLING    : return (bool)InpDrawFilling;
      case BUFFER_STATUS_LINE       : return (bool)InpDrawLine;
      case BUFFER_STATUS_HISTOGRAM  : return (bool)InpDrawHistogram;
      case BUFFER_STATUS_ARROW      : return (bool)InpDrawArrow;
      case BUFFER_STATUS_SECTION    : return (bool)InpDrawSection;
      case BUFFER_STATUS_HISTOGRAM2 : return (bool)InpDrawHistogram2;
      case BUFFER_STATUS_ZIGZAG     : return (bool)InpDrawZigZag;
      case BUFFER_STATUS_BARS       : return (bool)InpDrawBars;
      case BUFFER_STATUS_CANDLES    : return (bool)InpDrawCandles;
      default: return false;
     }
  }
//+------------------------------------------------------------------+

The function receives the buffer status and returns the input value corresponding to the status passed to the function.

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

Compile the indicator and launch it on the chart.
During the initialization, the journal displays all properties of created nine indicator buffers:

Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10425.23 USD, 1:100, Hedge, MetaTrader 5 demo
--- Initializing "DoEasy" library ---
Working with the current symbol only: "EURUSD"
Working with the current timeframe only: H1
EURUSD symbol timeseries: 
- Timeseries "EURUSD" H1: Requested: 1000, Actual: 0, Created: 0, On the server: 0
Library initialization time: 00:00:00.156

============= Parameter list start: Colored data buffer[0] "Drawing with arrows" ==================
Plotted buffer serial number: 0
Buffer status: Indicator buffer with graphical construction type "Drawing with arrows"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Drawing with arrows
Arrow code: 159
The vertical shift of the arrows: 0
Arrow size: 1
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 1
Base data buffer index: 0
Color buffer index: 1
Index of the array to be assigned as the next indicator buffer: 2
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Arrows
================== Parameter list end: Colored data buffer[0] "Drawing with arrows" ==================

============= Parameter list start: Colored data buffer[1] "Line" ==================
Plotted buffer serial number: 1
Buffer status: Indicator buffer with graphical construction type "Line"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Line
Line style: Solid line
Line width: 1
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 1
Base data buffer index: 2
Color buffer index: 3
Index of the array to be assigned as the next indicator buffer: 4
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Line
================== Parameter list end: Colored data buffer[1] "Line" ==================

============= Parameter list start: Colored data buffer[2] "Section" ==================
Plotted buffer serial number: 2
Buffer status: Indicator buffer with graphical construction type "Section"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Section
Line style: Solid line
Line width: 1
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 1
Base data buffer index: 4
Color buffer index: 5
Index of the array to be assigned as the next indicator buffer: 6
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Section
================== Parameter list end: Colored data buffer[2] "Section" ==================

============= Parameter list start: Colored data buffer[3] "Histogram from the zero line" ==================
Plotted buffer serial number: 3
Buffer status: Indicator buffer with graphical construction type "Histogram from the zero line"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Histogram from the zero line
Line style: Solid line
Line width: 2
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 1
Base data buffer index: 6
Color buffer index: 7
Index of the array to be assigned as the next indicator buffer: 8
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Histogram
================== Parameter list end: Colored data buffer[3] "Histogram from the zero line" ==================

============= Parameter list start: Colored data buffer[4] "Histogram on two indicator buffers" ==================
Plotted buffer serial number: 4
Buffer status: Indicator buffer with graphical construction type "Histogram on two indicator buffers"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Histogram on two indicator buffers
Line style: Solid line
Line width: 8
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 2
Base data buffer index: 8
Color buffer index: 10
Index of the array to be assigned as the next indicator buffer: 11
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Histogram2 0;Histogram2 1
================== Parameter list end: Colored data buffer[4] "Histogram on two indicator buffers" ==================

============= Parameter list start: Colored data buffer[5] "Zigzag" ==================
Plotted buffer serial number: 5
Buffer status: Indicator buffer with graphical construction type "Zigzag"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Zigzag
Line style: Solid line
Line width: 1
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 2
Base data buffer index: 11
Color buffer index: 13
Index of the array to be assigned as the next indicator buffer: 14
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: ZigZag 0;ZigZag 1
================== Parameter list end: Colored data buffer[5] "Zigzag" ==================

============= Parameter list start: Colored data buffer[6] "Color filling between two levels" ==================
Plotted buffer serial number: 6
Buffer status: Indicator buffer with graphical construction type "Color filling between two levels"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Color filling between two levels
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 2
Drawing color: clrDodgerBlue,clrRed
Number of data buffers: 2
Base data buffer index: 14
Index of the array to be assigned as the next indicator buffer: 16
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Filling 0;Filling 1
================== Parameter list end: Colored data buffer[6] "Color filling between two levels" ==================

============= Parameter list start: Colored data buffer[7] "Display as bars" ==================
Plotted buffer serial number: 7
Buffer status: Indicator buffer with graphical construction type "Display as bars"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Display as bars
Line width: 2
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 4
Base data buffer index: 16
Color buffer index: 20
Index of the array to be assigned as the next indicator buffer: 21
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Bar Open;Bar High;Bar Low;Bar Close
================== Parameter list end: Colored data buffer[7] "Display as bars" ==================

============= Parameter list start: Colored data buffer[8] "Display as candles" ==================
Plotted buffer serial number: 8
Buffer status: Indicator buffer with graphical construction type "Display as candles"
Buffer type: Colored data buffer
Buffer data period (timeframe): Current chart period (H1)
Active: Yes
Graphical construction type: Display as candles
The number of initial bars that are not drawn and values in DataWindow: 0
Display construction values in DataWindow: Yes
Indicator graphical construction shift by time axis in bars: 0
Number of colors: 3
Drawing color: clrDodgerBlue,clrRed,clrGray
Number of data buffers: 4
Base data buffer index: 21
Color buffer index: 25
Index of the array to be assigned as the next indicator buffer: 26
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Buffer symbol: EURUSD
Name of the graphical indicator series displayed in DataWindow: Candle Open;Candle High;Candle Low;Candle Close
================== Parameter list end: Colored data buffer[8] "Display as candles" ==================


After the first launch, when initializing the library and all indicator buffers, the following entries are made in the journal:

"EURUSD" H1 timeseries created successfully:
- Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6230
Buffer(0): Drawing with arrows EURUSD H1
Buffer(1): EURUSD H1 line
Buffer(2): EURUSD H1 sections
Buffer(3): Histogram from the zero line EURUSD H1
Buffer(4): Histogram on two indicator buffers EURUSD H1
Buffer(5): EURUSD H1 zigzag
Buffer(6): Color filling between two levels EURUSD H1
Buffer(7): Display as EURUSD H1 bars
Buffer(8): Display as EURUSD H1 candles

Let's switch permissions to display indicator lines in the settings:


What's next?

In the next article, we will start creating the collection class of indicator buffers. This class provides the greatest flexibility and convenience when creating indicator buffers and using them on any symbols and chart periods while developing custom indicator programs using the library.

All files of the current version of the library are attached below together with the test EA 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 creating and testing the indicator buffer collection, 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



Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/7868

Attached files |
MQL5.zip (3722.34 KB)
Last comments | Go to discussion (2)
Hossein Gougounani
Hossein Gougounani | 29 Aug 2020 at 17:36

interesting, it was so useful.

Thanks for putting the time to share the knowledge :-)

Artyom Trishkin
Artyom Trishkin | 30 Aug 2020 at 00:11
Hossein Gougounani :

interesting, it was so useful.

Thanks for putting the time to share the knowledge :-)

You are welcome :)

MQL as a Markup Tool for the Graphical Interface of MQL Programs (Part 3). Form Designer MQL as a Markup Tool for the Graphical Interface of MQL Programs (Part 3). Form Designer
In this paper, we are completing the description of our concept of building the window interface of MQL programs, using the structures of MQL. Specialized graphical editor will allow to interactively set up the layout that consists of the basic classes of the GUI elements and then export it into the MQL description to use it in your MQL project. The paper presents the internal design of the editor and a user guide. Source codes are attached.
Multicurrency monitoring of trading signals (Part 5): Composite signals Multicurrency monitoring of trading signals (Part 5): Composite signals
In the fifth article related to the creation of a trading signal monitor, we will consider composite signals and will implement the necessary functionality. In earlier versions, we used simple signals, such as RSI, WPR and CCI, and we also introduced the possibility to use custom indicators.
Timeseries in DoEasy library (part 44): Collection class of indicator buffer objects Timeseries in DoEasy library (part 44): Collection class of indicator buffer objects
The article deals with creating a collection class of indicator buffer objects. I am going to test the ability to create and work with any number of buffers for indicators (the maximum number of buffers that can be created in MQL indicators is 512).
Native Twitter Client: Part 2 Native Twitter Client: Part 2
A Twitter client implemented as MQL class to allow you to send tweets with photos. All you need is to include a single self contained include file and off you go to tweet all your wonderful charts and signals.