Русский 中文 Deutsch 日本語 Português
preview
Creating multi-symbol, multi-period indicators

Creating multi-symbol, multi-period indicators

MetaTrader 5Indicators | 4 March 2024, 09:46
3 270 24
Artyom Trishkin
Artyom Trishkin

Contents


Introduction

Continuing the topic of creating templates for technical indicators in Expert Advisors and indicators, which started in the article related to the creation of a dashboard and then developed in three more articles, in which we considered oscillators, volume indicators and Bill Williams, and trend indicators, today we will begin the topic of creating multi-symbol, multi-period indicators. These include indicators which run on the current chart but are calculated using the data from other symbols and (or) other chart timeframes. We will create one base multi-indicator class and a set of classes based on the types of all standard indicators. We will also create a class for the custom indicator, with which it will be possible to "turn" any indicator into a multi-symbol and multi-period one. For all indicator classes, we will create one common collection class. All indicators created in the program will be placed into this collection. Also, it will be possible to access any of the created indicators using the methods of the collection to obtain the relevant indicator data. The ultimate goal is to produce a convenient tool for creating multi-indicators and working with their data.


Basic principles

To properly understand the logic of indicator operations, let's try to understand how it all works. The indicator has two parts: calculation and plotting. Each of these parts knows nothing about the other. When creating an indicator, the terminal subsystem looks for the presence of such an indicator on the chart. In this case, it looks for an indicator with the same name and parameters. If such an indicator is already running on the chart or has been created programmatically for this chart, then the terminal will use the handle of the existing indicator rather than creating a new one. The plotting part of the indicator receives the required data from the calculation part using its handle. There may be a situation where several plotting parts simultaneously access one calculation part.

The buffer of the calculation part stores the data of the calculated indicator in an array with the data arranged from present to past. The data at index 0 of the buffer array corresponds to the current chart data:

Each cell of the array stores the data of one bar, corresponding to the time series bar of the symbol/period for which the indicator is calculated. Accordingly, in order to obtain data from the buffer of the indicator's calculation part and display it on the chart of another symbol/timeframe, you need to calculate the bar number on the chart corresponding to the time of the bar in the buffer array of the calculation part. The obtained data should be written into the plotting part buffer so that all bars of the current chart that match the opening of the bar in the calculation part buffer are added to the corresponding cells of the plotting buffer.

For example, one bar on a five-minute chart period corresponds to five bars on a one-minute chart. All these five bars of the one-minute chart must be filled with the value of the five-minute bar that corresponds to them in time. A similar algorithm is used to render data from lower periods on a higher-timeframe chart. In this case, all bars from the cells of the calculation part buffer, corresponding to the time of the bar on the higher TF chart, are plotted on one bar of the plotting buffer.

Of course, the readings might not be precisely accurate, since eventually this bar will represent only the data from the last lower TF bar, which matches the time of the corresponding higher TF bar. Everything here depends on the direction of receiving data from the calculation part buffer of the lower timeframe, that the data received last will be plotted on the bar of the higher-timeframe chart.

The CopyBuffer() function obtains the data from the buffer of the calculated indicator:

The function receives the data of the specified indicator buffer in the specified quantity into the 'buffer' array.

The elements of the copied data (indicator buffer with index buffer_num) are counted from the starting position, from the present to the past, that is, the starting position equal to 0 means the current bar (the indicator value for the current bar).

If the amount of data to be copied is not known in advance, it is advisable to use a dynamic array as the destination array buffer[], since CopyBuffer() tries to distribute the size of the receiving array to the amount of the copied data. If the receiving array buffer[] is an indicator buffer (an array previously assigned by the SetIndexBufer() function for storing indicator values), then partial copying is allowed.

If you need to partially copying the indicator values into another array (not the indicator buffer), you should use an intermediate array into which the required quantity is copied. From this intermediate array, copy the required number of values member by member, to the required places of the receiving array.

If you need to copy a predetermined amount of data, it is recommended to use a statically allocated buffer to avoid unnecessary memory reallocation.

The property of the receiving array, i.e. as_series=true or as_series=false, will be ignored: during copying, the oldest element will be copied to the beginning of the physical memory allocated for the array. There are three function options.

Access by the initial position and the number of required elements

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // indicator buffer index
   int       start_pos,            // starting point 
   int       count,                // amount to copy
   double    buffer[]              // array the data to be copied to
   );

Access by the initial date and the number of required elements

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // indicator buffer index
   datetime  start_time,           // starting date
   int       count,                // amount to copy
   double    buffer[]              // array the data to be copied to
   );

Access by the initial and final dates of the required time interval

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // indicator buffer index
   datetime  start_time,           // starting date
   datetime  stop_time,            // end date
   double    buffer[]              // array the data to be copied to
   );

Parameters

indicator_handle

[in] Indicator handle obtained by the relevant indicator function.

buffer_num

[in]  Number of the indicator buffer.

start_pos

[in]  Index of the first copied element.

count

[in]  Number of copied elements.

start_time

[in]  Bar time corresponding to the first element.

stop_time

[in]  Bar time corresponding to the last element.

buffer[]

[out]  Array of double type.

Return Value

The number of the copied array elements or -1 in case of an error.

Note

When requesting data from an indicator, the function immediately returns -1 if requested timeseries are not constructed yet or they should be downloaded from the server. In this case, the downloading or construction of the required data is initiated.

When requesting data from an EA or a script, download from the server is initiated if the terminal does not have the appropriate data locally. Optionally, construction of the necessary timeseries starts if the data can be constructed from the local history but it is not ready yet. The function returns the amount that will be ready by the time the timeout expires.

We will use the first version of the function, that is, access based on the starting position (y the loop index) and the number of required elements.

The object structure will be as follows:

  1. The base class of a multi-symbol, multi-period indicator containing the main functionality common to all indicators;
  2. Classes derived from the base object, which describe each indicator by its type;
  3. A collection of indicators – you can use this class to create any indicators and add them to the collection. The class will provide the user with all the tools for creating indicators and receiving data from them.

When working with data from a non-current chart, to avoid the release of the timeseries, you should access this timeseries at least once every two minutes. In this case, the timeseries will be "retained" which will speed up access to it (there will be no need to wait for data synchronization each time). In the class constructor, we will perform the first call of the timeseries on which the indicator is built. This will allow us to start downloading the timeseries (if there is no access to it locally). Then, once every 90 seconds, in the base class timer, we will access the timeseries to hold it.


Resource-efficient computations

To calculate and display the indicator, we need a resource-efficient computations. The indicator will be calculated for all historical data upon the first launch, and will then be calculated for one 0r two bars for all subsequent ticks.

The OnCalculate() handler has predefined constant variables which store the input data size (of a timeseries or array) and the amount of data calculated during the previous OnCalculate() call:

Calculation based on data array

int  OnCalculate(
   const int        rates_total,       // price[] array size
   const int        prev_calculated,   // number of processed bars at the previous call
   const int        begin,             // index number in the price[] array meaningful data starts from
   const double&    price[]            // array of values for calculation
   );

Calculations based on the current timeframe timeseries

int  OnCalculate(
   const int        rates_total,       // size of input timeseries
   const int        prev_calculated,   // number of processed bars at the previous call
   const datetime&  time{},            // Time array
   const double&    open[],            // Open array
   const double&    high[],            // High array
   const double&    low[],             // Low array
   const double&    close[],           // Close array
   const long&      tick_volume[],     // Tick Volume array
   const long&      volume[],          // Real Volume array
   const int&       spread[]           // Spread array
   );

The presence of such data enables a quick, efficient calculation of the indicator. For example, let's consider the calculation for the 'limit' value:

//--- Number of bars for calculation
   int limit=rates_total-prev_calculated;
//--- If limit > 1, then this is the first calculation or change in the history
   if(limit>1)
     {
      //--- specify all the available history for calculation
      limit=rates_total-1;
      //--- If the indicator has any buffers, initialize them here with empty values set for these buffers
     }

When the indicator is launched for the first time, we have the timeseries size (rates_total) and the amount of calculated data on the last call (prev_calculated). The value of previously calculated bars at the first launch is zero as the indicator has not yet been calculated. Thus, the 'limit' value will be greater than 1 (it will be equal to the number of available bars minus zero). With this value, we specify 'limit' equal to rates_total-1 - the entire available history for calculation. In this case, we additionally need to first remove all previously plotted data from the indicator buffers, for example:

ArrayInitialize(Buffer,EMPTY_VALUE);

After this, the entire history will be calculated in the main loop, which is run from 'limit' to zero inclusive:

   for(int i=limit;i>=0;i--)
     {
      // Calculating indicator at each bar of the loop (i)
     }  

Please note that with this calculation method, all arrays used in calculations and the indicator buffer itself must have the flag of indexing as timeseries:

ArraySetAsSeries(array,true);

If the calculated 'limit' is equal to 1, then this means the opening of a new bar on the chart: the indicator will calculate the first and zero bars of the timeseries in a loop from 1 to 0 inclusive.

If the calculated 'limit' is equal to 0, then this means operation on the current tick: the indicator will only calculate the zero bar of the timeseries in a loop from 0 to 0 inclusive.

If you need to calculate from the zero bar deep into the historical data (so as not to expand time series arrays and buffers), then the loop will be reversed:

   int i=fmax(0, prev_calculated-1);
   for(;i<rates_total;i++)
     {
      // Calculating indicator at each bar of the loop (i)
     }   

It is quite easy to run resource-efficient computations on the current chart. But what if you need data not from the current chart? When will you copy the entire data array from the calculation part, and when only the last one or two bars?

Here we will use functions Bars() and BarsCalculated(). These are analogues of the predefined constant indicator variables rates_total and prev_calculated. They return the number of bars for the specified symbol/period and the amount of data calculated by the indicator. Since the indicator is constructed for the symbol/period specified at its creation moment, the amount of calculated data also refers to this symbol/period. We get the indicator by its handle.

Based on the fact that we can calculate how many bars we need to copy for any symbol/period (so as not to copy the entire array at each tick), we will create in the indicator base class exactly the same construction as for the current symbol/period:

limit=rates_total-prev_calculated;

But variables 'limit', 'rates_total' and 'prev_calculated' will be private members of the class and will receive value form the Bars() and BarsCalculated() functions. The 'limit' value will be calculated on each tick, and if it is zero, then only the data of the last two data bars (current and previous bars) will be copied. If 'limit' is equal to 1, then this means the opening of a new bar on the indicator symbol/period, and you need to increase the array by 1 and then copy the data from the indicator buffer - also two. When 'limit' is greater than one, the entire array is copied from the indicator's calculation part buffer to the receiving array buffer of the class, since it is considered that this is either the first launch or a change in history.

This logic applies to the trading day, when ticks arrive.

A holiday requires a different approach. This is an isolated case. There are no ticks here, the Bars() function very often returns zero, and the number of calculated indicator bars is not recorded, i.e., is also zero. If there is any error in the source data used for calculation, the indicator should return zero. This means that it waits until the next tick and tries to calculate again. But on a holiday, there is no tick except the first launch.

On the first launch, the indicator will first clear the buffer arrays and then calculate. But the calculation may fail due to insufficient data for calculation, or simply because prev_calculated will return a zero value. And the indicator will exit OnCalculate(), returning zero again. Accordingly, if you update the chart via a right-click menu, , which is considered as a tick emulation, the indicator will again see that it was calculated with an error, and will again initialize the buffers, considering that this is the first launch. This behavior will continue, and the plotting buffer data just rendered on the chart will be constantly erased... There is a solution though.

If at the first launch it was not possible to immediately calculate the indicator, then you can wait 20 - 30 seconds for the next tick and emulate the tick programmatically. This can be done using the ChartSetSymbolPeriod() function. If you call it and specify the current chart symbol/period, this will cause a recalculation of indicators running on the chart. Thus, you can calculate the indicator on the chart even if there are no ticks.

A twenty-second wait is quite enough to load the required history for the indicator symbol/period and calculate it. But we need a flag that the indicator has already been calculated (since prev_calculated returns zero), and if we do not set the calculation success flag, the indicator will again clear its buffers. Therefore, we can understand that the indicator has been successfully calculated by simply seeing that the number of calculated bars for the indicator object is equal to the number of bars on its symbol/period. If Bars() returns zero, then we can find out in another way the available number of bars of the desired symbol/period (do not forget that we are talking about multi-symbol, multi-period indicators that are calculated in another indicator or Expert Advisor that runs on the current chart). In the SeriesInfoInteger() function, we can get the start and end dates of the available history of the symbol/period:

SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE);      // The very first date for a period and symbol at the moment
SeriesInfoInteger(symbol,period,SERIES_LASTBAR_DATE);   // Time of opening the last bar for the period and symbol

From these two dates and the time series chart period, we can easily calculate the number of available bars even if Bars(symbol,period) or SeriesInfoInteger(symbol,period,SERIES_BARS_COUNT) returned zero.

After all the data has been received and the indicator has been calculated correctly, the calculation success flag is set. This flag is checked under the condition when limit > 1 (in this case, it is necessary to initialize the indicator buffer arrays with an "empty" value). At the first launch, the success flag is reset, the arrays are initialized and an attempt is made to calculate the indicator on a non-current chart symbol/period. If calculation failed, wait 20 seconds (according to a timer) for the next tick.

If it is a weekend, then there will be no tick, and after 20 seconds a command is sent to set the current symbol and period for the chart to emulate a tick. When restarting the indicator in the timer (after 20 seconds), the data should already be loaded and the indicator should be calculated without errors. The calculation success flag is set in this case. The next time the timer is activated, this flag is checked and, if it is set, the tick is not emulated. There are three such attempts to render indicator buffers. After three attempts, the success flag is forcibly set, and attempts to emulate the tick are stopped. If the indicator could not calculate for three emulated ticks, then only manually action is possible: either refresh using the right-click menu or switch the chart timeframe back and forth to go through the entire tick emulation process with data loading again.

That's the theory. Let's proceed to practice: creating classes of multi-indicators.


Base class of the MSTF indicator

In the terminal folder \MQL5\Include\IndMSTF\, let's create a new file IndMSTF.mqh for the CIndMSTF class. The class must be inherited from the Standard Library base object CObject. The base object file must be connected to the created new class file:

//+------------------------------------------------------------------+
//|                                                      IndMSTF.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Base class of the multi-symbol multi-period indicator            |
//+------------------------------------------------------------------+
class CIndMSTF : public CObject
  {
private:

protected:

public:
//--- Constructor/destructor
                     CIndMSTF();
                    ~CIndMSTF();
  };

Before we add methods to various sections of the class, let's add some macro substitutions, enumerations and the structure of the indicator buffer.

We will need a second timer that will track two periods of time:

  • 90 seconds, after which we will turn to timeseries of indicators calculated not on the current symbol/period;
  • 20 seconds, after which we will emulate a tick to plot the indicator on weekends.
Let's enter macro substitutions to set the waiting time for these two periods of time:
//--- includes
#include <Object.mqh>
//--- defines
#define TIMER_COUNT_1   (90)  // Timer 1 size. Must not be more than two minutes (120)
#define TIMER_COUNT_2   (20)  // Timer 2 size. Too small values quickly trigger tick emulation, which is not desirable in an active market

Different standard indicators in the client terminal belong to different categories. In order for us to sort the created indicators by category or create lists of indicators related to any category, let's write an enumeration of different indicator categories:

//--- defines
#define TIMER_COUNT_1   (90)  // Timer 1 size. Must not be more than two minutes (120)
#define TIMER_COUNT_2   (20)  // Timer 2 size. Too small values quickly trigger tick emulation, which is not desirable in an active market
//--- enums
enum ENUM_IND_CATEGORY        // Indicator categories
  {
   IND_CATEGORY_NONE,         // Not set
   IND_CATEGORY_TREND,        // Trend
   IND_CATEGORY_OSCILLATOR,   // Oscillators
   IND_CATEGORY_VOLUME,       // Volume
   IND_CATEGORY_WILLIAMS,     // Bill Williams
   IND_CATEGORY_CUSTOM,       // Custom
  };

About sorting, searching and filtering

Each indicator object will have properties that can be used to find the desired indicator. To understand that indicators are identocal, we need to compare their key properties: symbol, chart period and values of all input parameters. If at least one value of the properties being compared is different, then the indicators are not identical. If the indicators are equal, a new one will not be created, but a pointer to the previously created one with the same parameters will be returned. This concerns the collection of indicators. Regarding properties, we need to create an enumeration that will contain constants of some properties that can be set to a successfully created indicator and can then be used to find that indicator:

enum ENUM_COMPARE_MODE        // Comparison mode
  {
   // By default, the comparison mode is set to zero, which compares all properties
   COMPARE_MODE_HANDLE=1,     // Compare by handle
   COMPARE_MODE_SYMBOL,       // Compare by symbol
   COMPARE_MODE_TIMEFRAME,    // Compare by chart period
   COMPARE_MODE_ID,           // Compare by ID
   COMPARE_MODE_DESCRIPTION,  // Compare by custom description
   COMPARE_MODE_CATEGORY,     // Compare by category
  };

Each successfully created indicator has a handle by which it can be accessed. This is a unique number assigned to the calculation part of the indicator. The handle values of the created indicators start from 10 and increase by 1 for each subsequent value.

The remaining properties are not unique and may be inherent in different indicators. Searching using these values is provided here for convenience only. For example, you can set a unique description for an indicator, and then refer to it by this description.

Descriptions of indicator line states were discussed earlier in previous articles on indicators. Here we will also use this enumeration:

enum ENUM_LINE_STATE          // Indicator line state
  {
   LINE_STATE_NONE,           // Undefined
   LINE_STATE_UP,             // Upward
   LINE_STATE_DOWN,           // Downward
   LINE_STATE_TURN_UP,        // Upward reversal
   LINE_STATE_TURN_DOWN,      // Downward reversal
   LINE_STATE_STOP_UP,        // Upward stop
   LINE_STATE_STOP_DOWN,      // Downward stop
   LINE_STATE_ABOVE,          // Above value
   LINE_STATE_BELOW,          // Below value
   LINE_STATE_CROSS_UP,       // Upward value crossing
   LINE_STATE_CROSS_DOWN,     // Downward value crossing
   LINE_STATE_TOUCH_BELOW,    // Touching value from below
   LINE_STATE_TOUCH_ABOVE,    // Touching value from above
   LINE_STATE_EQUALS,         // Equal to value
  };

For furthre details please refer to the article on oscillator indicators, in the ATR indicator parameters section.

Each indicator will signal the result of its calculation using error code:

enum ENUM_ERR_TYPE            // Indicator calculation error type
  {
   ERR_TYPE_NO_ERROR,         // No error
   ERR_TYPE_NO_CYNC,          // Data not synchronized
   ERR_TYPE_NO_DATA,          // Data not loaded
   ERR_TYPE_NO_CALC,          // Calculation not completed
  };

Using this code, it will be possible to determine from the outside what action is required to handle the error.

Indicator buffers

Here we need to decide where do buffers belong.

  1. Buffer of the calculation part. When we create an indicator, it is created in memory. This is the calculation part of the indicator. It has its own buffers, which are managed by the terminal subsystem. You can access the calculation part using the handle, which is returned after successful creation of the indicator. The buffer of a successfully created and calculated indicator always contains the data that corresponds to the timeseries on which the indicator was calculated. The data in this buffer is located so that the zero index corresponds to the current bar of the timeseries on which the indicator is calculated.
    To copy data from the buffer of the calculation part of the indicator, we use the CopyBuffer() function.

  2. Buffer of the indicator object. Each of the created multi-indicator class objects will have its own buffer arrays according to the number of buffers of this indicator. Data from the calculation part buffer will be placed in these arrays. Buffers of the indicator object will be managed inside the class object, in which they will be initialized, increased in size in accordance with the size of the timeseries on which the indicator is created, and updated on each new tick. When copying data into the indicator object array from the calculation part buffer using CopyBufer(), the data will be arranged so that the current bar will be located at the end of the array (ArraySize()-1).

  3. Buffer of the indicator plotting part. Each indicator object can be created both in an Expert Advisor and in a custom indicator. When creating multi-indicators in Expert Advisors, to calculate the indicators, we call the method that calculates the indicator, and to obtain the calculated data, we access the desired buffer index of the indicator object. In the case of a custom indicator, we also need to plot data from the multi-indicators on the chart. That is why here we also have a plotting buffer. This buffer is assigned assigned as plotting in a custom indicator. It will display data stored in the buffers of indicator objects. To display lines on a chart, it will be enough to simply call the method of the indicator collection class from the custom indicator, which calculates the indicators, and then, if the calculation is successful, to call the method that will place the buffer data of the indicator object in the plotting buffer of the custom indicator.

In the indicator object, one buffer will be represented by a structure containing both the dynamic array itself and the controls for this array:

//--- struct
struct SBuffer                // Structure of the indicator buffer
  {
   double            array[];    // Indicator buffer array
   double            init_value; // Initializing value
   int               shift;      // Horizontal shift of the buffer
   string            descript;   // Buffer description
   //--- (1) Sets, (2) returns the initializing value,
   void              SetInitValue(const double value) { init_value=value;                             }
   double            InitValue(void)                  { return init_value;                            }
   //--- (1) Sets, (2) returns the buffer offset
   void              SetShift(const int value)        { shift=value;                                  }
   int               Shift(void)                      { return shift;                                 }
//--- (1) Resizes the buffer array, (2) returns the size of the buffer array,
//--- (3) initializes the array with the set "empty" value
   bool              BuffResize(const int new_size)   { return(ArrayResize(array,new_size)==new_size);}
   uint              BufferSize(void)                 { return array.Size();                          }
   int               InitBuffer(void)                 { return ArrayInitialize(array,init_value);     }
  };

Some values set externally, for example, when creating an indicator, need to be saved somewhere in order to later know what these values were. The easiest way is to write them directly into the structure. This is what we do here: the "empty" buffer value set from the calling program is saved in the buffer structure in the init_value variable. The shift of the indicator line, which is set when creating the calculation part of the indicator, can also be saved here, so that later you can know it inside the class object; it is saved in the 'shift' variable. The description of the buffer is also saved here. This description is set automatically when creating the calculation part of the indicator, corresponding to the name of the buffer for the same standard indicator. This description can be changed later.

Functions returning descriptions of the indicator line state and errors when calculating the indicator are necessary only for logging the states of indicator lines and errors during their initialization and calculation or displaying them on the information panel:

//+------------------------------------------------------------------+
//| Return the indicator line state description                      |
//+------------------------------------------------------------------+
string BufferLineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_NONE       :  return "None";
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_BELOW      :  return "Below level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }
//+------------------------------------------------------------------+
//| Return error description in indicator calculation                |
//+------------------------------------------------------------------+
string TypeErrorcDescription(ENUM_ERR_TYPE error_type)
  {
   switch(error_type)
     {
      case ERR_TYPE_NO_CYNC   :  return "Data is not synchronized";
      case ERR_TYPE_NO_DATA   :  return "Data not loaded";
      case ERR_TYPE_NO_CALC   :  return "Calculation not completed";
      default                 :  return "No error";
     }
  }

The required preparatory work has been done. Let's proceed to the object class of a multi-symbol multi-period indicator.

Let's write in the body of the class all the private, protected and public variables and methods necessary for the class to work, and then consider their purpose and implementation:

//+------------------------------------------------------------------+
//| Base class of the multi-symbol multi-period indicator            |
//+------------------------------------------------------------------+
class CIndMSTF : public CObject
  {
private:
   ENUM_PROGRAM_TYPE m_program;           // Program type
   ENUM_INDICATOR    m_type;              // Indicator type
   ENUM_TIMEFRAMES   m_timeframe;         // Chart timeframe
   string            m_symbol;            // Chart symbol
   int               m_handle;            // Indicator handle
   int               m_id;                // Identifier
   bool              m_success;           // Successful calculation flag
   ENUM_ERR_TYPE     m_type_err;          // Calculation error type
   string            m_description;       // Custom description of the indicator
   string            m_name;              // Indicator name
   string            m_parameters;        // Description of indicator parameters

protected:
   ENUM_IND_CATEGORY m_category;          // Indicator category
   MqlParam          m_param[];           // Array of indicator parameters
   string            m_title;             // Title (indicator name + description of parameters)
   SBuffer           m_buffers[];         // Indicator buffers
   int               m_digits;            // Digits in indicator values
   int               m_limit;             // Number of bars required to calculate the indicator on the current tick
   int               m_rates_total;       // Number of available bars for indicator calculation
   int               m_prev_calculated;   // Number of calculated bars on the previous indicator call
   
//--- (1) Sets indicator name, (2) description of parameters
   void              SetName(const string name)                      { this.m_name=name;           }
   void              SetParameters(const string str)                 { this.m_parameters=str;      }
   
//--- Resizes the (1) specified, (2) all indicator buffers
   bool              BufferResize(const uint buffer_num,const int new_buff_size);
   bool              BuffersResize(const int new_buff_size);
//--- Initializes the (1) specified, (2) all indicator buffers
   bool              BufferInitialize(const uint buffer_num,const int new_buff_size);
   bool              BuffersInitialize(const int new_buff_size);
   
//--- Returns the flag indicating equality of the structure of one parameter of two objects
   bool              IsEqualParameters(const MqlParam &this_param,const MqlParam &compared_param) const
                       {
                        if(this_param.type==compared_param.type                     && 
                           this_param.integer_value==compared_param.integer_value   && 
                           this_param.string_value==compared_param.string_value     && 
                           ::NormalizeDouble(this_param.double_value-compared_param.double_value,8)==0
                          ) return true;
                        return false;
                       }
//--- Return the result of comparison on one parameter of two objects
   int               CompareParams(const MqlParam &this_param,const MqlParam &compared_param)
                       {
                        if(this.IsEqualParameters(this_param,compared_param))
                           return 0;
                        else if(this_param.type>compared_param.type                 || 
                           this_param.integer_value>compared_param.integer_value    || 
                           this_param.string_value>compared_param.string_value      || 
                           this_param.double_value>compared_param.double_value
                          ) return 1;
                        else if(this_param.type<compared_param.type                 || 
                           this_param.integer_value<compared_param.integer_value    || 
                           this_param.string_value<compared_param.string_value      || 
                           this_param.double_value<compared_param.double_value
                          ) return -1;
                        else
                           return -1;
                       }
   
public:
//--- Creates the calculation part of the indicator, returns the handle
   int               CreateIndicator(void);
//--- (1) Calculates the indicator, (2) fills the passed buffer array (taking into account the chart period/symbol) with data from the indicator calculation buffer of this class
   bool              Calculate(void);
   bool              DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int limit,double &buffer[]);

//--- (1) Sets (2) returns the initializing value for the specified buffer
   void              SetBufferInitValue(const uint buffer_num,const double value);
   double            BufferInitValue(const uint buffer_num) const;

//--- (1) Sets (2) returns the offset value for the specified buffer
   void              SetBufferShift(const uint buffer_num,const int value);
   double            BufferShift(const uint buffer_num) const;

//--- Returns data of the specified buffer (1) as is, (2) relative to the specified symbol/timeframe,
//--- (3) amount of data in the specified buffer, (4) the state of the indicator line as it is in the calculation part buffer,
//--- (5) indicator line state taking into account the chart symbol/period, description of the line state (6) as is in the buffer (7) taking into account the chart symbol/period
   double            GetData(const uint buffer_num,const int index)           const;
   double            GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int index) const;
   uint              DataTotal(const uint buffer_num)                         const;
   ENUM_LINE_STATE   BufferLineState(const uint buffer_num,const int index)   const;
   ENUM_LINE_STATE   BufferLineState(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const uint buffer_num,const int index) const;
   ENUM_LINE_STATE   BufferLineStateRelative(const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);
   ENUM_LINE_STATE   BufferLineStateRelative(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);

//--- Returns (1) success flag, (2) calculation error type
   bool              IsSuccess(void)                           const { return this.m_success;               }
   ENUM_ERR_TYPE     TypeError(void)                           const { return this.m_type_err;              }
   
//--- Sets (1) identifier, (2) Digits, (3) custom description, (4) description of the specified buffer
   void              SetID(const int id)                             { this.m_id=id;                        }
   void              SetDigits(const uint digits)                    { this.m_digits=(int)digits;           }
   void              SetDescription(const string descr)              { this.m_description=descr;            }
   void              SetBufferDescription(const uint buffer_num,const string descr);

//--- Sets the indexing of buffer arrays of the calculation part not as in the timeseries
   void              SetAsSeriesOff(void);
//--- Returns flag of whether the buffer is set as series, (2) historical data for symbol/period is synchronized
   bool              IsSeries(const uint buffer_num) const;
   bool              IsSynchronized(void) const
                       {
                        return (bool)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED);
                       }
   
//--- Returns (1) timeframe, (2) symbol, (3) name, (4) list of parameters, (5) handle, (6) Digits
//--- number of (7) buffers, (8) bars, (9) identifier, (10) description, (11) title, (12) category,
//--- (13) number of parameters, (14) program type, description of (15) category, (16) indicator buffer
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_timeframe;             }
   string            Symbol(void)                              const { return this.m_symbol;                }
   string            Name(void)                                const { return this.m_name;                  }
   string            Parameters(void)                          const { return this.m_parameters;            }
   int               Handle(void)                              const { return this.m_handle;                }
   int               Digits(void)                              const { return this.m_digits;                }
   uint              BuffersTotal(void)                        const { return this.m_buffers.Size();        }
   uint              RatesTotal(void)                          const { return this.m_rates_total;           }
   int               ID(void)                                  const { return this.m_id;                    }
   string            Description(void)                         const { return this.m_description;           }
   string            Title(void)                               const { return this.m_title;                 }
   ENUM_IND_CATEGORY Category(void)                            const { return this.m_category;              }
   uint              ParamsTotal(void)                         const { return this.m_param.Size();          }
   ENUM_PROGRAM_TYPE Program(void)                             const { return this.m_program;               }
   string            CategoryDescription(void);
   string            BufferDescription(const uint buffer_num);

//--- Returns (1) structure of parameters by index from array, (2) flag of indicator program, (3) timeframe description
   MqlParam          GetMqlParam(const int index)              const { return this.m_param[index];          }
   bool              IsIndicator()                 const { return(this.Program()==PROGRAM_INDICATOR);       }
   string            TimeframeDescription(void)    const
                       {
                        return ::StringSubstr(::EnumToString(this.m_timeframe),7);
                       }
//--- Returns amount of calculated data
   int               Calculated(void)  const { return ::BarsCalculated(this.m_handle);                      }
   
//--- Virtual method returning the type of object (indicator)
   virtual int       Type(void)                                const { return this.m_type;                  }
//--- Virtual method for comparing two objects
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CIndMSTF *compared=node;
                        switch(mode)
                          {
                           case COMPARE_MODE_ID          : return(this.ID()>compared.ID()                   ? 1 : this.ID()<compared.ID()                   ? -1 : 0);
                           case COMPARE_MODE_HANDLE      : return(this.Handle()>compared.Handle()           ? 1 : this.Handle()<compared.Handle()           ? -1 : 0);
                           case COMPARE_MODE_CATEGORY    : return(this.Category()>compared.Category()       ? 1 : this.Category()<compared.Category()       ? -1 : 0);
                           case COMPARE_MODE_SYMBOL      : return(this.Symbol()>compared.Symbol()           ? 1 : this.Symbol()<compared.Symbol()           ? -1 : 0);
                           case COMPARE_MODE_TIMEFRAME   : return(this.Timeframe()>compared.Timeframe()     ? 1 : this.Timeframe()<compared.Timeframe()     ? -1 : 0);
                           case COMPARE_MODE_DESCRIPTION : return(this.Description()>compared.Description() ? 1 : this.Description()<compared.Description() ? -1 : 0);
                           //--- Equality of all object parameters
                           default                       : return(this.IsEqualIndicators(compared) ? 0 : -1);
                          }
                       }
//--- Returns the flag of equality of parameters of two indicator objects
   bool              IsEqualIndicators(const CIndMSTF *compared) const
                       {
                        if(this.Type()!=compared.Type() || this.ParamsTotal()!=compared.ParamsTotal())
                           return false;
                        bool res=true;
                        int total=(int)this.ParamsTotal();
                        for(int i=0;i<total;i++)
                           res &=this.IsEqualParameters(this.m_param[i],compared.GetMqlParam(i));
                        res &=(this.Timeframe()==compared.Timeframe());
                        res &=(this.Symbol()==compared.Symbol());
                        return res;
                       }
//--- Timer
   void OnTimer(void);
   
//--- Constructor/destructor
                     CIndMSTF(){}
                     CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe);
                    ~CIndMSTF();
  };

The purpose of each variable and methods is commented here in the class code. Let's consider the implementation of methods.

Class constructor:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Start the timer
   ::ResetLastError();
   if(!::EventSetTimer(1))
      ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError());
//--- Set properties to the values passed to the constructor or to default values
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_type=type;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   this.m_handle=INVALID_HANDLE;
   this.m_digits=::Digits();
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
//--- Set the size of the buffer structure array to the number of indicator buffers
   ::ResetLastError();
   if(::ArrayResize(this.m_buffers,buffers)!=buffers)
      ::PrintFormat("%s: Buffers ArrayResize failed. Error  %lu"__FUNCTION__,::GetLastError());
//--- Set the "empty" value for each buffer by default (can be changed it later)
   for(int i=0;i<(int)this.m_buffers.Size();i++)
      this.SetBufferInitValue(i,EMPTY_VALUE);
//--- Set initial values of variables involved in resource-efficient calculation of the indicator
   this.m_prev_calculated=0;
   this.m_limit=0;
   this.m_rates_total=::Bars(this.m_symbol,this.m_timeframe);
//--- If the indicator is calculated on non-current chart, request data from the required chart
//--- (the first access to data starts data pumping)
   datetime array[];
   if(symbol!=::Symbol() || timeframe!=::Period())
      ::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_rates_total,array);
  }

The type of indicator, the number of its buffers, the name of the symbol and the chart timefame are passed to the class constructor. Next, the variables are set to default values, the size of the buffer array is set, and the buffer arrays are given a default initializing value as EMPTY_VALUE. If the indicator object is calculated on the non-current chart, then at the end of the constructor we call a function that starts downloading from the server the timeseries data on which the indicator is calculated.

The class destructor:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CIndMSTF::~CIndMSTF()
  {
//--- Delete timer
   ::EventKillTimer();
//--- Release indicator handle
   ::ResetLastError();
   if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle))
      ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError());
//--- Free up the memory of buffer arrays
   for(int i=0;i<(int)this.BuffersTotal();i++)
      ::ArrayFree(this.m_buffers[i].array);
  }

In the class destructor, we destroy the timer, release the indicator handle and free the memory of the buffer arrays.

Timer:

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CIndMSTF::OnTimer(void)
  {
//--- If the indicator symbol and timeframe match those of the current chart, exit
   if(this.Symbol()==::Symbol() && this.Timeframe()==::Period())
      return;
//--- Declare time counter variable
   static int count1=0;
   static int count2=0;
//--- If the counter of timer 1 has reached the specified value,
   if(count1>=TIMER_COUNT_1)
     {
      //--- call the CopyTime function (timeseries hold) and reset the counter
      datetime array[1];
      ::CopyTime(this.m_symbol,this.m_timeframe,0,1,array);
      count1=0;
     }
//--- If the counter of timer 2 has reached the specified value
   if(count2>=TIMER_COUNT_2)
     {
      static int count=0;
      //--- if the previous indicator calculation failed, emulate a tick and print the relevant log
      if(!this.m_success)
        {
         if(::ChartSetSymbolPeriod(0,::Symbol(),::Period()))
           {
            count++;
            ::PrintFormat("%s::%s: Tick emulation. Attempt %ld of 3 ...",__FUNCTION__,this.Title(),count);
            if(count>2)
              {
               count=0;
               this.m_success=true;
              }
           }
        }
      //--- reset the counter
      count2=0;
     }
//--- Increase timer counters
   count1++;
   count2++;
  }

The class timer contains two counters: one for maintaining the indicator's timeseries, and the second for emulating ticks on weekends. If the indicator is calculated using data of the current chart symbol/timeframe, the timer is not used.

The indicator object itself is not an indicator. It is just a wrapper over the calculation part of the indicator, allowing you to manage it, receive data from it and pass data to the program. In the multi-indicator object, we need to create the indicator itself. To do this, we use the method for creating the calculation part of the indicator, which returns the handle received during creation:

//+------------------------------------------------------------------+
//| Creates an indicator, returns a handle                           |
//+------------------------------------------------------------------+
int CIndMSTF::CreateIndicator(void)
  {
//--- Create indicator name to print to logs
   string name=::StringFormat("%s(%s,%s)",::StringSubstr(::EnumToString(this.m_type),4),this.m_symbol,this.TimeframeDescription());
//--- Create the calculation part of the indicator
   ::ResetLastError();
   this.m_handle=::IndicatorCreate(this.m_symbol,this.m_timeframe,this.m_type,this.m_param.Size(),this.m_param);
//--- If the calculation part is not created, log a message about the failed creation of the indicator
   if(this.m_handle==INVALID_HANDLE)
      ::PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,name,::GetLastError());
//--- If the indicator is created, set non-timeseries indexing of indicator arrays
   else
      this.SetAsSeriesOff();
//--- Return the handle of the created indicator or -1 if unsuccessful
   return this.m_handle;
  }

Buffers of an indicator object are dynamic arrays inside the buffer structure we discussed above. Their size should be changed to accommodate the ever-growing amount of new data. There are two methods for this: a method for changing the size of the specified buffer array and for changing the size of all buffers of the indicator object at once.

Method that resizes the specified indicator buffer:

//+------------------------------------------------------------------+
//| Resize the specified indicator buffer                            |
//+------------------------------------------------------------------+
bool CIndMSTF::BufferResize(const uint buffer_num,const int new_buff_size)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return 'false'
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return false;
     }
//--- Resize the buffer
   ::ResetLastError();
   bool res=this.m_buffers[buffer_num].BuffResize(new_buff_size);
//--- If unsuccessful, print a message to the log
   if(!res)
      ::PrintFormat("%s::%s: Buffer(%lu) resize failed. Error %lu",__FUNCTION__,this.Title(),buffer_num,::GetLastError());
//--- Return the result of resizing the buffer array
   return res;
  }

The method receives in parameter the number of the buffer whose array should be resized by the value also passed to the method. If the buffer number is specified incorrectly, a relevant message is logged and the method returns false.
If array resizing failed, a message is printed in the log. The buffer array resizing result is finally returned.

Method that resizes all indicator buffer:

//+------------------------------------------------------------------+
//| Resize all indicator buffers                                     |
//+------------------------------------------------------------------+
bool CIndMSTF::BuffersResize(const int new_buff_size)
  {
//--- In a loop through all indicator buffers, add to the 'res' variable the resizing result of each next buffer
   bool res=true;
   int total=(int)this.BuffersTotal();
   for(int i=0;i<total;i++)
      res &=this.m_buffers[i].BuffResize(new_buff_size);
//--- Return the result of resizing all arrays of indicator buffers
   return res;
  }

Here, in a loop through all buffers of the indicator object, the result of resizing the next buffer array is added to the 'res' variable, the value of which is ultimately returned from the method.

Methods for initializing arrays of indicator object buffers are organized in a similar way.

Method that initializes the specified indicator buffer:

//+------------------------------------------------------------------+
//| Initialize the specified indicator buffer                        |
//+------------------------------------------------------------------+
bool CIndMSTF::BufferInitialize(const uint buffer_num,const int new_buff_size)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return 'false'
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return false;
     }
//--- resizing the buffer array
   bool res=this.BufferResize(buffer_num,new_buff_size);
//--- If successfully resized, initialize the buffer with the set initializing value
   if(res)
      this.m_buffers[buffer_num].InitBuffer();
//--- Return the result
   return res;
  }

The correctness of the number of the initialized buffer is also checked here. The buffer is then set to a new size and, if resizing is successful, the array is initialized to the value set for this buffer.

Method that initializes all indicator buffers:

//+------------------------------------------------------------------+
//| Initialize all indicator buffers                                 |
//+------------------------------------------------------------------+
bool CIndMSTF::BuffersInitialize(const int new_buff_size)
  {
//--- In a loop through all indicator buffers, add to the 'res' variable the resizing result of each next buffer
//--- If successfully resized, initialize the buffer with the set initializing value
   bool res=true;
   int total=(int)this.BuffersTotal();
   for(int i=0;i<total;i++)
     {
      res &=this.m_buffers[i].BuffResize(new_buff_size);
      if(res)
         this.m_buffers[i].InitBuffer();
     }
//--- Return the overall result
   return res;
  }

In a loop through all buffers of the indicator object, the result of resizing of the next buffer array is added to the 'res' variable. Upon successful resizing, the buffer is initialized to the initialization value set for it. The method returns the final state of the 'res' variable, which will have the false value if at least one of the buffers failed to initialize.

The remaining methods for setting and returning buffer values are identical to those discussed above:

//+------------------------------------------------------------------+
//| Set the initializing value for the specified buffer              |
//+------------------------------------------------------------------+
void CIndMSTF::SetBufferInitValue(const uint buffer_num,const double value)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return;
     }
//--- Set a new initializing value for the specified buffer
   this.m_buffers[buffer_num].SetInitValue(value);
  }
//+------------------------------------------------------------------+
//| Return the initialization value of the specified buffer          |
//+------------------------------------------------------------------+
double CIndMSTF::BufferInitValue(const uint buffer_num) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      //--- If the indicator has buffers, return the initializing value of the first one, otherwise EMPTY_VALUE
      return(this.BuffersTotal()>0 ? this.BufferInitValue(0) : EMPTY_VALUE);
     }
//--- Return the initializing value of the requested buffer
   return this.m_buffers[buffer_num].InitValue();
  }
//+------------------------------------------------------------------+
//| Sets the offset value for the specified buffer                   |
//+------------------------------------------------------------------+
void CIndMSTF::SetBufferShift(const uint buffer_num,const int value)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return;
     }
//--- Set the offset value for the buffer
   this.m_buffers[buffer_num].SetShift(value);
  }
//+------------------------------------------------------------------+
//| Return the offset value of the specified buffer                  |
//+------------------------------------------------------------------+
double CIndMSTF::BufferShift(const uint buffer_num) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      //--- If the indicator has buffers, return the shift value of the very first one, otherwise 0
      return(this.BuffersTotal()>0 ? this.m_buffers[0].Shift() : 0);
     }
//--- Return the offset value of the requested buffer
   return this.m_buffers[buffer_num].Shift();
  }

The logic of the methods is described in detail in the comments to the code.

Method for calculating indicator data. The calculated indicator (its calculated part) has a buffer containing all calculated indicator data. We need to copy data from the calculation part buffer into the buffer arrays of the indicator object. For this operation, we need to organize a resource-efficient calculation: we will copy the entire buffer only at the first start or when there is a change in historical data.

We discussed such a calculation above, and it should be organized in the calculation method of the indicator object. In case of errors, the method will return false, and the calling program will respond bu exiting the handler (OnTick in an Expert Advisor and OnCalculate in an indicator) before the next tick arrives. Errors that require a return from the method will be considered the beginning of downloading historical data, incomplete history downloading, incomplete indicator calculation, and errors in copying data from the calculation part buffer to the indicator object buffer. The method will write the error code to a variable so that the caller can read it and process it correctly.

Method that fills the indicator object buffers with data from the calculation part buffer:

//+------------------------------------------------------------------+
//| Fill object buffers with data from the calculation part buffer   |
//+------------------------------------------------------------------+
bool CIndMSTF::Calculate(void)
  {
//--- Set the success flag to true, and the error type to no error
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
//--- If the data is not yet synchronized with the trade server,
   if(!this.IsSynchronized())
     {
      //--- Log a message about non-synchronized data,
      ::PrintFormat("%s::%s: Waiting for data to sync...",__FUNCTION__,this.Title());
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_CYNC;
      this.m_success &=false;
      return false;
     }
//--- If the Calculated method returned -1, this means the start of data downloading
   if(this.Calculated()==WRONG_VALUE)
     {
      //--- Log a message about the start of data downloading,
      ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_DATA;
      this.m_success &=false;
      return false;
     }
//--- If the Calculated method returned 0, this means that the indicator has not yet been calculated
   if(this.Calculated()==0)
     {
      //--- Log a message about waiting for the indicator to be calculated,
      ::PrintFormat("%s::%s: Waiting for a new tick and when the indicator will be calculated...",__FUNCTION__,this.Title());
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_CALC;
      this.m_success &=false;
      return false;
     }
//--- Get the number of data bars for the indicator symbol/period
   int bars=::Bars(this.m_symbol,this.m_timeframe);
//--- If the Bars function returned a zero value, which often happens on weekends, calculate the available number of bars
   if(bars==0)
     {
      //--- Get the date of the very first available bar in history for the symbol/period
      datetime firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
      //--- Get the date of the last (current) bar in history for the symbol/period
      datetime lastdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
      //--- Calculate the number of bars between the first and last dates of history
      int sec=::PeriodSeconds(this.m_timeframe);
      ulong date_bars=(((ulong)lastdate-(ulong)firstdate)/(sec>0 ? sec : 1))+1;
      //--- Write to the 'bars' variable the smaller value of the calculated number of bars and the maximum number of bars available in the terminal
      bars=(int)fmin(date_bars,::TerminalInfoInteger(TERMINAL_MAXBARS));
     }
//--- Write the resulting number of available bars to m_rates_total
   if(this.m_rates_total!=bars)
      this.m_rates_total=bars;
//--- If the number of available bars is received, and it is 2 or less,
   if(this.m_rates_total>=0 && this.m_rates_total<3)
     {
      //--- Log a message about the number of available bars being too small
      ::PrintFormat("%s::%s: Not enough data for calculation: %ld bars. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_rates_total);
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_DATA;
      this.m_success &=false;
      return false;
     }

//--- Calculate the number of bars required to calculate the indicator
//--- Either the entire available history, or 1 when a new bar opens, or 0 on the current tick
   this.m_limit=this.m_rates_total-this.m_prev_calculated;
   this.m_prev_calculated=this.Calculated();

//--- Declare an array of size 2 to receive data into it from the indicator's calculation part buffer
//--- We always get two bars: previous and current
   double array[2];
//--- Get the number of indicator buffers
   int total=(int)this.BuffersTotal();
//--- If the calculated m_limit is greater than 1, it means either the first launch or changes in historical data
//--- In this case, a complete recalculation of the indicator is necessary
   if(this.m_limit>1)
     {
      //--- In a loop over the number of indicator buffers
      for(int i=0;i<total;i++)
        {
         //--- resize the indicator buffer array and initialize it to the "empty" value set for this buffer
         this.BufferInitialize(i,this.m_rates_total);
         ::ResetLastError();
         //--- Copy all available historical data from indicator's calculation part array to buffer array of indicator object
         int copied=::CopyBuffer(this.m_handle,i,-this.m_buffers[i].Shift(),this.m_rates_total,this.m_buffers[i].array);
         //--- If not all data is copied
         if(copied!=this.m_rates_total)
           {
            //--- If CopyBuffer returned -1, this means the start of historical data downloading
            //--- print a message about this to the log
            if(copied==WRONG_VALUE)
               ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
            //--- In any other case, not all data has been copied yet
            //--- print a message about this to the log
            else
               ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied);
            //--- Write the absence of data to the error type
            this.m_type_err=ERR_TYPE_NO_DATA;
            //--- Add 'false' to the result and return 'false' to exit the method and wait for the next tick
            this.m_success &=false;
            return false;
           }
        }
      //--- If we exited the loop of copying all indicator buffers, then everything was successful - return 'true'
      return true;
     }
//--- If calculated m_limit is less than or equal to 1, this means either opening of a new bar (m_limit==1) or current tick (m_limit==0)
//--- In this case, it is necessary to calculate two bars - the first and the current
   if(this.m_limit<=1)
     {
      //--- In a loop over the number of indicator buffers
      for(int i=0;i<total;i++)
        {
         //--- If this is the opening of a new bar and resizing the indicator buffer failed,
         if(this.m_limit==1 && !this.BufferResize(i,this.m_rates_total))
           {
            //--- add 'false' to the m_success variable and return 'false'
            //--- Here, an error message will be printed to log from the BufferResize method
            this.m_success &=false;
            return false;
           }
         //--- If failed to copy two bars from the indicator's calculation part buffer,
         ::ResetLastError();
         if(::CopyBuffer(this.m_handle,i,-this.m_buffers[i].Shift(),2,array)!=2)
           {
            //--- report this via the log, add 'false' to the m_success variable and return 'false'
            ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError());
            this.m_success &=false;
            return false;
           }
         //--- If got here, it means copying was successful -
         //--- copy data from array[], into which the last two bars were copied, to the indicator object buffer
         this.m_buffers[i].array[this.DataTotal(i)-1]=array[1];
         this.m_buffers[i].array[this.DataTotal(i)-2]=array[0];
        }
      //--- Success
      return true;
     }
//--- Undefined 'limit' option - return 'false'
   return false;
  }

The entire logic of the method is described in detail in the comments for each code block. The method is called from the program and, if it returns false, exit OnTick or OnCalculate before the next tick.

If the method completes without errors, then the indicator object's buffer will contain data ready for use. This data can be accessed using methods that will be discussed below.

Indicators have special methods for outputting data from the indicator object buffer filled by this method into the indicator plotting buffers. The methods output data to a drawing buffer either in the same form in which the data is written in the indicator object buffer, or taking into account the chart symbol/period. These methods can be used to display calculated data in an indicator object on the current chart.

Method that fills the passed array with data from the class buffer:

//+------------------------------------------------------------------+
//| Fill the passed array with data from the class buffer            |
//+------------------------------------------------------------------+
bool CIndMSTF::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int limit,double &buffer[])
  {
//--- Set the success flag
   this.m_success=true;
//--- Get the indexing direction of the buffer array passed to the method and,
//--- if non-timeseries indexing, set timeseries indexing
   bool as_series=::ArrayGetAsSeries(buffer);
   if(!as_series)
      ::ArraySetAsSeries(buffer,true);
//--- Set the symbol name and timeframe value passed to the method
   string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to);
   ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to);
   datetime array[2];
//--- If this is the first launch or history changes, initialize the buffer array passed to the method
   if(limit>1 && this.m_limit>1)
     {
      ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
      ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num));
     }
//--- Set the value of the loop counter (no more than the maximum number of bars in the terminal on the chart)
   int count=(limit<=1 ? 2 : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit));
//--- In a loop from the zero bar to the value of the loop counter
   for(int i=0;i<count;i++)
     {
      //--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array
      if(timeframe==::Period() && this.m_timeframe==::Period())
         buffer[i]=this.GetData(buffer_num,i);
      //--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object
      else
        {
         //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
         ::ResetLastError();
         if(::CopyTime(symbol,timeframe,i,2,array)!=2)
           {
            //--- If there is no data in the terminal, move on
            if(::GetLastError()==4401)
               continue;
            //--- Error in obtaining existing data - return false
            this.m_success &=false;
            return false;
           }
         //--- Using time of bar of current chart timeframe, find corresponding index of bar of class object's chart period
         ::ResetLastError();
         int bar=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
         if(bar==WRONG_VALUE)
           {
            this.m_success &=false;
            continue;
           }
         //--- If this is historical data (not the first or zero bar) -
         //--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer
         if(i>1)
            buffer[i]=this.GetData(buffer_num,bar);
         //--- If this is the current (zero) or previous (first) bar
         else
           {
            //--- Get the time of bars 0 and 1 by symbol/timeframe of the class object
            if(::CopyTime(this.m_symbol,this.m_timeframe,0,2,array)!=2)
              {
               this.m_success &=false;
               return false;
              }
            //--- Using time, get indexes of current and previous bars on the chart whose symbol/period was passed to method
            int bar0=::iBarShift(symbol,timeframe,array[1]);
            int bar1=::iBarShift(symbol,timeframe,array[0]);
            if(bar0==WRONG_VALUE || bar1==WRONG_VALUE)
              {
               this.m_success &=false;
               return false;
              }
            //--- If the chart timeframe is lower than the timeframe of the class object,
            if(timeframe<this.m_timeframe)
              {
               //--- in a loop from bar with smaller time to current chart bar, fill the buffer with data from the last 2 cells of the indicator buffer array
               for(int j=bar1;j>=0;j--)
                  buffer[j]=this.GetData(buffer_num,(j>bar0 ? 1 : 0));
              }
            //--- If the chart timeframe is higher than the timeframe of the class object,
            else
              {
               //--- Get the time of the current and previous bars by symbol/timeframe of the current chart
               if(::CopyTime(symbol,timeframe,0,2,array)!=2)
                 {
                  this.m_success &=false;
                  return false;
                 }
               //--- Using time, get indexes of bars in indicator's calculation part buffer, corresponding to time of current and previous bars on the chart
               int bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[1]);
               int bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
               //--- Write into indicator buffer, at indexes 1 and 0, values from corresponding indexes of calculation part buffer
               buffer[1]=this.GetData(buffer_num,bar1);
               buffer[0]=this.GetData(buffer_num,bar0);
              } 
           }
        }
     }
//--- Set initial indexing of the buffer array passed to the method
   ::ArraySetAsSeries(buffer,as_series);
//--- Successful
   return true;
  }

The method logic is described in its listing in detail. The idea of the method is in the correctly calculation of current chart's bars that need to be filled with data from the indicator buffer array calculated on a different timeframe. The last parameter passed to the method is an array of a custom indicator plotting buffer, which should render the indicator calculated on a different symbol/period.

A method that returns the data of the specified buffer as is:

//+------------------------------------------------------------------+
//| Return the data of the specified buffer as is                    |
//+------------------------------------------------------------------+
double CIndMSTF::GetData(const uint buffer_num,const int index) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      //--- If the indicator has buffers, return "empty" value of the first one, otherwise EMPTY_VALUE
      return(this.BuffersTotal()>0 ? this.BufferInitValue(0) : EMPTY_VALUE);
     }
//--- If an incorrect index is specified, return the "empty" value of the specified buffer
   if(index<0 || index>(int)this.DataTotal(buffer_num)-1)
      return this.BufferInitValue(buffer_num);
//--- Calculate the real index in the buffer array and return the value at this index
   int n=int(this.DataTotal(buffer_num)-1-index);
   return this.m_buffers[buffer_num].array[n];
  }

The method simply returns data from the indicator object buffer at the specified index.

Method that returns data of the specified buffer for the specified symbol/timeframe:

//+-------------------------------------------------------------------+
//| Returns data from specified buffer for specified symbol/timeframe |
//+-------------------------------------------------------------------+
double CIndMSTF::GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int index) const
  {
//--- If current symbol/period of the chart is specified
   if(timeframe_to==::Period() && this.m_timeframe==::Period() && symbol_to==::Symbol() && this.m_symbol==::Symbol())
      return this.GetData(buffer_num,index);
//--- Find out which time of this class the current chart timeframe's bar, corresponding to the loop index, belongs to
   datetime array[];
   if(::CopyTime(symbol_to,timeframe_to,index,1,array)!=1)
      return this.BufferInitValue(buffer_num);
//--- Using time of bar of current chart timeframe, find corresponding bar index of bar this class chart period
   int bar=iBarShift(this.m_symbol,this.m_timeframe,array[0]);
//--- If the bar is not found, return the "empty" value set for the buffer
   if(bar==WRONG_VALUE)
      return this.BufferInitValue(buffer_num);
//--- Return value from the indicator object buffer at the found index
   return this.GetData(buffer_num,bar);
  }

The method finds the bar index of the timeseries on which the indicator, corresponding to the chart symbol/period passed to the method, is calculated and returns data from the indicator object buffer at the found index.

The method that returns the state of the indicator line:

//+------------------------------------------------------------------+
//| Return the state of the indicator line as is                     |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineState(const uint buffer_num,const int index) const
  {
//--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index
   const double value0=this.GetData(buffer_num,index);
   const double value1=this.GetData(buffer_num,index+1);
   const double value2=this.GetData(buffer_num,index+2);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Line upward reversal (value2>value1 && value0>value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)>0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Line upward direction (value2<=value1 && value0>value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_UP;
//--- Line upward stop (value2<=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Line downward reversal (value2<value1 && value0<value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)<0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Line downward direction (value2>=value1 && value0<value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_DOWN;
//--- Line downward stop (value2>=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Undefined state
   return LINE_STATE_NONE;
  }

The method determines the line state of the indicator object based on the data in its buffer and returns the found value.

Method that returns the state of the indicator for the specific symbol/period:

//+------------------------------------------------------------------+
//| Return indicator line state for the specific symbol/period       |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineState(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const uint buffer_num,const int index) const
  {
//--- Determine the chart symbol/period passed to the method
   string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from);
   ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from);
//--- If we get data from symbol/period equal to current chart, return state from the buffer "as is"
   if(symbol==::Symbol() && symbol==this.m_symbol && timeframe==::Period() && timeframe==this.m_timeframe)
      return this.BufferLineState(buffer_num,index);
//--- Declare variables to search for the required bars on the current chart
   datetime array[1];
   int      bar0=WRONG_VALUE;
   int      bar1=WRONG_VALUE;
   int      bar2=WRONG_VALUE;

//--- Get the time of the first bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index,1,array)!=1)
     {
      ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),7),index,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get index of the first bar in indicator object buffer based on bar opening time on the chart
   bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
   if(bar0==WRONG_VALUE)
     {
      ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),string(array[0]),::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get the time of the second bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index+1,1,array)!=1)
     {
      ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),7),index+1,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get index of the second bar in indicator object buffer based on bar opening time on the chart
   bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
   if(bar1==WRONG_VALUE)
     {
      ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),string(array[0]),::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get the time of the third bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index+2,1,array)!=1)
     {
      ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),7),index+2,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get index of the third bar in indicator object buffer based on bar opening time on the chart
   bar2=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
   if(bar2==WRONG_VALUE)
     {
      ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),string(array[0]),::GetLastError());
      return LINE_STATE_NONE;
     }
     
//--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index
   const double value0=this.GetData(buffer_num,bar0);
   const double value1=this.GetData(buffer_num,bar1);
   const double value2=this.GetData(buffer_num,bar2);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Line upward reversal (value2>value1 && value0>value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)>0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Line upward direction (value2<=value1 && value0>value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_UP;
//--- Line upward stop (value2<=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Line downward reversal (value2<value1 && value0<value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)<0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Line downward direction (value2>=value1 && value0<value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_DOWN;
//--- Line downward stop (value2>=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Undefined state
   return LINE_STATE_NONE;
  }

The method is used to obtain the state of the indicator object line relative to the current chart. If the indicator object is calculated using data from a higher timeframe relative to the current chart, then its line is "stretched" across the bars of the current chart. The method allows you to correctly obtain the state of the indicator line on the specified bar of the current chart.

The method that returns the line state relative to the specified level:

//+------------------------------------------------------------------+
//| Return the state of the line relative to the specified level     |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Get the values of the indicator line with the shift (0,1) relative to the passed index
   const double value0=this.GetData(buffer_num,index);
   const double value1=this.GetData(buffer_num,index+1);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Define the second level to compare
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- The line is below the level (value1<level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_BELOW;
//--- The line is above the level (value1>level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_ABOVE;
//--- The line crossed the level upwards (value1<=level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<=0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- The line crossed the level downwards (value1>=level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>=0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- The line touched the level from below (value1<level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- The line touched the level from above (value1>level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Line is equal to the level value (value1==level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)==0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_EQUALS;
//--- Undefined state
   return LINE_STATE_NONE;
  }

The method that returns the line state relative to the specified level on the specified chart symbol/period:

//+------------------------------------------------------------------+
//| Return the state of the line relative to the specified level     |
//| on the specified chart symbol/period                             |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Determine the chart symbol/period passed to the method
   string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from);
   ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from);
//--- Get the values of the indicator line with the shift (0,1) relative to the passed index
   const double value0=this.GetDataTo(symbol,timeframe,buffer_num,index);
   const double value1=this.GetDataTo(symbol,timeframe,buffer_num,index+1);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Define the second level to compare
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- The line is below the level (value1<level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_BELOW;
//--- The line is above the level (value1>level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_ABOVE;
//--- The line crossed the level upwards (value1<=level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<=0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- The line crossed the level downwards (value1>=level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>=0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- The line touched the level from below (value1<level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- The line touched the level from above (value1>level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Line is equal to the level value (value1==level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)==0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_EQUALS;
//--- Undefined state
   return LINE_STATE_NONE;
  }

You can read more about methods for getting indicator lines states in the article about connecting oscillators to EAs.

Other class methods:

//+------------------------------------------------------------------+
//| Return category description                                      |
//+------------------------------------------------------------------+
string CIndMSTF::CategoryDescription(void)
  {
//--- Create a category name from ENUM_IND_CATEGORY and return the resulting text
   string category=::StringSubstr(::EnumToString(this.m_category),13);
   if(category.Lower())
      category.SetChar(0,ushort(category.GetChar(0)-0x20));
   return category;
  }
//+------------------------------------------------------------------+
//| Return the description of the indicator buffer                   |
//+------------------------------------------------------------------+
string CIndMSTF::BufferDescription(const uint buffer_num)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return empty string
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return "";
     }
//--- If indicator has buffers, return description of the specified buffer, otherwise description of the indicator
   return(this.BuffersTotal()>0 ? this.m_buffers[buffer_num].descript : this.m_title);
  }
//+------------------------------------------------------------------+
//| Set indicator buffer description                                 |
//+------------------------------------------------------------------+
void CIndMSTF::SetBufferDescription(const uint buffer_num,const string descr)
  {
//--- If the indicator has no buffers, exit
   if(this.BuffersTotal()==0)
      return;
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return;
     }
//--- Write the description passed to the method into the specified buffer
   this.m_buffers[buffer_num].descript=descr;
  }
//+------------------------------------------------------------------+
//| Disable timeseries indexing of buffer arrays                     |
//+------------------------------------------------------------------+
void CIndMSTF::SetAsSeriesOff(void)
  {
//--- In a loop through all indicator buffers, disable the array as timeseries flag
   for(int i=0;i<(int)this.BuffersTotal();i++)
      ::ArraySetAsSeries(this.m_buffers[i].array,false);
  }
//+------------------------------------------------------------------+
//| Returns the timeseries flag of the given buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::IsSeries(const uint buffer_num) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return 'false'
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return false;
     }
//--- Return the timeseries flag of the array of the specified buffer
   return (bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].array);
  }
//+------------------------------------------------------------------+
//| Returns the amount of data in the specified buffer               |
//+------------------------------------------------------------------+
uint CIndMSTF::DataTotal(const uint buffer_num) const
  {
//--- Validate the buffer number passed to method and, if number is incorrect, print a message to log and return zero
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return 0;
     }
//--- Return the array size of the specified buffer
   return this.m_buffers[buffer_num].array.Size();
  }

The base class of the multi-symbol multi-period indicator object is ready. The class contains all the necessary capabilities for working with indicators constructed using on timeseries data that does not belong to the current chart.

To create different types of technical indicators, create derived classes from the newly created base one. In the constructors of derived classes, indicate those parameters and properties that are inherent to the specific type of indicator.


Indicator classes by type

Classes derived from the base one will be the simplest, and will contain only a constructor. The class constructor will be passed the symbol/period of the chart on which the indicator is calculated, and the input parameters specific to this type of indicator. In the constructor initialization line, parameters will be passed to the constructor of the parent class.

An example of a class creating an indicator object that does not contain any parameters:

//+------------------------------------------------------------------+
//| Accelerator Oscillator indicator class                           |
//+------------------------------------------------------------------+
class CIndAC : public CIndMSTF
  {
public:
//--- Constructor
   CIndAC(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AC,1,symbol,timeframe)
     {
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("AC");
      this.SetDescription("Accelerator Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };

The indicator type, number of buffers, chart symbol and chart period, on which the indicator should be calculated, are passed to the parent class in the initialization string. A string with the description of indicator parameters is created in the class body. In this case, if the indicator is created on the data of the current chart, the parameter string will be empty. Otherwise, it will contain the symbol and period of the chart in the form, for example, "(EURUSD,H1)". Next, all parameters inherent to this type of indicator (here it is the Accelerator Oscillator indicator) are set in the body of the constructor.

For each indicator, it is possible to set the number of decimal places displayed in the data window and on the symbol chart. This class constructor does not have the Digits setting since this value, equal to the Digits of the symbol on which the indicator is calculated, is set in the constructor of the parent class. If it is necessary to set a different Digits value for an indicator, this should either done in the constructors of the classes of those indicators where Digits differs from that of the symbol, or it can be changed after creating the indicator object using the SetDigits() method.

An indicator class with parameters:

//+------------------------------------------------------------------+
//| Accumulation/Distribution indicator class                        |
//+------------------------------------------------------------------+
class CIndAD : public CIndMSTF
  {
public:
//--- Constructor
   CIndAD(const string symbol,const ENUM_TIMEFRAMES timeframe,
          const ENUM_APPLIED_VOLUME applied_volume // used volume
         ) : CIndMSTF(IND_AD,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("A/D");
      this.SetDescription("Accumulation/Distribution");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };

Here we have parameters, which should be registered in the array structure of the indicator input parameters MqlParam. Here we also set the indicator's Digits value, which is set for the standard Accumulation/Distribution indicator.

A complete list of all classes derived from the base class of the multi-symbol multi-period indicator:

//+------------------------------------------------------------------+
//| Accelerator Oscillator indicator class                           |
//+------------------------------------------------------------------+
class CIndAC : public CIndMSTF
  {
public:
//--- Constructor
   CIndAC(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AC,1,symbol,timeframe)
     {
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("AC");
      this.SetDescription("Accelerator Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Accumulation/Distribution indicator class                        |
//+------------------------------------------------------------------+
class CIndAD : public CIndMSTF
  {
public:
//--- Constructor
   CIndAD(const string symbol,const ENUM_TIMEFRAMES timeframe,
          const ENUM_APPLIED_VOLUME applied_volume // used volume
         ) : CIndMSTF(IND_AD,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("A/D");
      this.SetDescription("Accumulation/Distribution");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Average Directional Movement Index indicator class               |
//+------------------------------------------------------------------+
class CIndADX : public CIndMSTF
  {
public:
//--- Constructor
   CIndADX(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int adx_period                    // averaging period
          ) : CIndMSTF(IND_ADX,3,symbol,timeframe)
     {
      // Номера буферов: 0 - MAIN_LINE, 1 - PLUSDI_LINE, 2 - MINUSDI_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(adx_period<1 ? 14 : adx_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),adx_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("ADX");
      this.SetDescription("Average Directional Movement Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=2;
      //--- write descriptions of MAIN_LINE, PLUSDI_LINE and MINUSDI_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(PLUSDI_LINE,"+DI");
      this.SetBufferDescription(MINUSDI_LINE,"-DI");
     }
  };
//+------------------------------------------------------------------+
//| Average Directional Movement Index Wilder indicator class        |
//+------------------------------------------------------------------+
class CIndADXW : public CIndMSTF
  {
public:
//--- Constructor
   CIndADXW(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int adx_period                      // averaging period
           ) : CIndMSTF(IND_ADXW,3,symbol,timeframe)
     {
      // Номера буферов: 0 - MAIN_LINE, 1 - PLUSDI_LINE, 2 - MINUSDI_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(adx_period<1 ? 14 : adx_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),adx_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("ADX Wilder");
      this.SetDescription("Average Directional Movement Index Wilder");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=2;
      //--- write descriptions of MAIN_LINE, PLUSDI_LINE and MINUSDI_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(PLUSDI_LINE,"+DI");
      this.SetBufferDescription(MINUSDI_LINE,"-DI");
     }
  };
//+------------------------------------------------------------------+
//| Alligator indicator class                                        |
//+------------------------------------------------------------------+
class CIndAlligator : public CIndMSTF
  {
public:
//--- Constructor
   CIndAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                 const int jaw_period,                   // period for calculating jaws
                 const int jaw_shift,                    // horizontal shift of jaws
                 const int teeth_period,                 // period for calculating teeth
                 const int teeth_shift,                  // horizontal shift of teeth
                 const int lips_period,                  // period for calculating lips
                 const int lips_shift,                   // horizontal shift of lips
                 const ENUM_MA_METHOD ma_method,         // smoothing type
                 const ENUM_APPLIED_PRICE applied_price  // price type or handle
                ) : CIndMSTF(IND_ALLIGATOR,3,symbol,timeframe)
     {
      // Buffer indexes: 0 - GATORJAW_LINE, 1 - GATORTEETH_LINE, 2 - GATORLIPS_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,8)==8)
        {
         ::ZeroMemory(this.m_param);
         //--- period for jaw line calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(jaw_period<1 ? 13 : jaw_period);
         //--- horizontal shift of the jaw line
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=jaw_shift;
         //--- period for teeth line calculation
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(teeth_period<1 ? 8 : teeth_period);
         //--- horizontal shift of teeth line
         this.m_param[3].type=TYPE_INT;
         this.m_param[3].integer_value=teeth_shift;
         //--- period for lip line calculation
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=(lips_period<1 ? 5 : lips_period);
         //--- horizontal shift of lips line
         this.m_param[5].type=TYPE_INT;
         this.m_param[5].integer_value=lips_shift;
         //--- smoothing type
         this.m_param[6].type=TYPE_UINT;
         this.m_param[6].integer_value=ma_method;
         //--- price type or handle
         this.m_param[7].type=TYPE_UINT;
         this.m_param[7].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),jaw_period,teeth_period,lips_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("Alligator");
      this.SetDescription("Alligator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write descriptions of GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE line buffers
      this.SetBufferDescription(GATORJAW_LINE,::StringFormat("Jaws(%s%lu)", (current ? "" : symbol_period+":"),jaw_period));
      this.SetBufferDescription(GATORTEETH_LINE,::StringFormat("Teeth(%s%lu)",(current ? "" : symbol_period+":"),teeth_period));
      this.SetBufferDescription(GATORLIPS_LINE,::StringFormat("Lips(%s%lu)", (current ? "" : symbol_period+":"),lips_period));
      //--- Write offsets to buffers GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE
      this.SetBufferShift(GATORJAW_LINE,jaw_shift);
      this.SetBufferShift(GATORTEETH_LINE,teeth_shift);
      this.SetBufferShift(GATORLIPS_LINE,lips_shift);
     }
  };
//+------------------------------------------------------------------+
//| Adaptive Moving Average indicator class                          |
//+------------------------------------------------------------------+
class CIndAMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ama_period,                   // AMA period
           const int fast_ma_period,               // fast MA period
           const int slow_ma_period,               // slow MA period
           const int ama_shift,                    // horizontal shift of the indicator
           const ENUM_APPLIED_PRICE applied_price  // price type or handle
          ) : CIndMSTF(IND_AMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,5)==5)
        {
         ::ZeroMemory(this.m_param);
         //--- AMA period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ama_period<1 ? 9 : ama_period);
         //--- fast MA period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(fast_ma_period<1 ? 2 : fast_ma_period);
         //--- slow MA period
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(slow_ma_period<1 ? 30 : slow_ma_period);
         //--- horizontal shift of the indicator
         this.m_param[3].type=TYPE_INT;
         this.m_param[3].integer_value=ama_shift;
         //--- price type or handle
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),ama_period,fast_ma_period,slow_ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("AMA");
      this.SetDescription("Adaptive Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ama_shift);
     }
  };
//+------------------------------------------------------------------+
//| Awesome Oscillator indicator class                               |
//+------------------------------------------------------------------+
class CIndAO : public CIndMSTF
  {
public:
//--- Constructor
   CIndAO(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AO,1,symbol,timeframe)
     {
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("AO");
      this.SetDescription("Awesome Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Average True Range indicator class                               |
//+------------------------------------------------------------------+
class CIndATR : public CIndMSTF
  {
public:
//--- Constructor
   CIndATR(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period                     // averaging period
          ) : CIndMSTF(IND_ATR,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("ATR");
      this.SetDescription("Average True Range");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Bears Power indicator class                                      |
//+------------------------------------------------------------------+
class CIndBears : public CIndMSTF
  {
public:
//--- Constructor
   CIndBears(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int ma_period                      // averaging period
            ) : CIndMSTF(IND_BEARS,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 13 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Bears");
      this.SetDescription("Bears Power");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Bulls Power indicator class                                      |
//+------------------------------------------------------------------+
class CIndBulls : public CIndMSTF
  {
public:
//--- Constructor
   CIndBulls(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int ma_period                      // averaging period
            ) : CIndMSTF(IND_BULLS,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 13 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Bulls");
      this.SetDescription("Bulls Power");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Bollinger Bands® indicator class                                 |
//+------------------------------------------------------------------+
class CIndBands : public CIndMSTF
  {
public:
//--- Constructor
   CIndBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int bands_period,                  // central line calculation period
             const int bands_shift,                   // horizontal shift of the indicator
             const double deviation,                  // number of standard deviations
             const ENUM_APPLIED_PRICE applied_price   // price type or handle
            ) : CIndMSTF(IND_BANDS,3,symbol,timeframe)
     {
      // Buffer indexes: 0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- central line calculation period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(bands_period<1 ? 20 : bands_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=bands_shift;
         //--- number of standard deviations
         this.m_param[2].type=TYPE_DOUBLE;
         this.m_param[2].double_value=deviation;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),bands_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Bands");
      this.SetDescription("Bollinger Bands");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Description of line buffers BASE_LINE, UPPER_BAND and LOWER_BAND
      this.SetBufferDescription(BASE_LINE,this.m_title+" Middle");
      this.SetBufferDescription(UPPER_BAND,this.m_title+" Upper");
      this.SetBufferDescription(LOWER_BAND,this.m_title+" Lower");
      //--- Write offsets to the BASE_LINE, UPPER_BAND and LOWER_BAND buffers
      this.SetBufferShift(BASE_LINE,bands_shift);
      this.SetBufferShift(UPPER_BAND,bands_shift);
      this.SetBufferShift(LOWER_BAND,bands_shift);
     }
  };
//+------------------------------------------------------------------+
//| Commodity Channel Index indicator class                          |
//+------------------------------------------------------------------+
class CIndCCI : public CIndMSTF
  {
public:
//--- Constructor
   CIndCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period,                    // averaging period
           const ENUM_APPLIED_PRICE applied_price  // price type or handle
          ) : CIndMSTF(IND_CCI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period<2 ? 2 : ma_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("CCI");
      this.SetDescription("Commodity Channel Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Chaikin Oscillator indicator class                               |
//+------------------------------------------------------------------+
class CIndCHO : public CIndMSTF
  {
public:
//--- Constructor
   CIndCHO(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int fast_ma_period,                  // fast period
           const int slow_ma_period,                  // slow period
           const ENUM_MA_METHOD ma_method,            // smoothing type
           const ENUM_APPLIED_VOLUME applied_volume   // used volume
          ) : CIndMSTF(IND_CHAIKIN,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- fast period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(fast_ma_period<1 ? 3 : fast_ma_period);
         //--- slow period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(slow_ma_period<1 ? 10 : slow_ma_period);
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- used volume
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),slow_ma_period,fast_ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("CHO");
      this.SetDescription("Chaikin Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Double Exponential Moving Average indicator class                |
//+------------------------------------------------------------------+
class CIndDEMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int ma_period,                      // averaging period
            const int ma_shift,                       // horizontal indicator shift
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
          ) : CIndMSTF(IND_DEMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("DEMA");
      this.SetDescription("Double Exponential Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| DeMarker indicator class                                         |
//+------------------------------------------------------------------+
class CIndDeM : public CIndMSTF
  {
public:
//--- Constructor
   CIndDeM(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period                     // averaging period
          ) : CIndMSTF(IND_DEMARKER,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("DeM");
      this.SetDescription("DeMarker");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=3;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Envelopes indicator class                                        |
//+------------------------------------------------------------------+
class CIndEnvelopes : public CIndMSTF
  {
public:
//--- Constructor
   CIndEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                 const int ma_period,                    // middle line calculation period
                 const int ma_shift,                     // horizontal shift of the indicator
                 const ENUM_MA_METHOD ma_method,         // smoothing type
                 const ENUM_APPLIED_PRICE applied_price, // price type or handle
                 const double deviation                  // deviation of envelope borders from the middle line
          ) : CIndMSTF(IND_ENVELOPES,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,5)==5)
        {
         ::ZeroMemory(this.m_param);
         //--- central line calculation period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
         //--- deviation of envelope borders from the muddle line
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].double_value=deviation;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Envelopes");
      this.SetDescription("Envelopes");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Description of UPPER_LINE and LOWER_LINE line buffers
      this.SetBufferDescription(UPPER_LINE,this.m_title+" Upper");
      this.SetBufferDescription(LOWER_LINE,this.m_title+" Lower");
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Force Index indicator class                                      |
//+------------------------------------------------------------------+
class CIndForce : public CIndMSTF
  {
public:
//--- Constructor
   CIndForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int                 ma_period,     // averaging period
             const ENUM_MA_METHOD      ma_method,     // smoothing type
             const ENUM_APPLIED_VOLUME applied_volume // volume type for calculation
            ) : CIndMSTF(IND_FORCE,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 13 : ma_period);
         //--- smoothing type
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=ma_method;
         //--- volume type for calculation
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Force");
      this.SetDescription("Force Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Fractals indicator class                                         |
//+------------------------------------------------------------------+
class CIndFractals : public CIndMSTF
  {
public:
//--- Constructor
   CIndFractals(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("Fractals");
      this.SetDescription("Fractals");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Description of UPPER_LINE and LOWER_LINE line buffers
      this.SetBufferDescription(UPPER_LINE,this.m_title+" Up");
      this.SetBufferDescription(LOWER_LINE,this.m_title+" Down");
     }
  };
//+------------------------------------------------------------------+
//| Fractal Adaptive Moving Average indicator class                  |
//+------------------------------------------------------------------+
class CIndFrAMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int ma_period,                     // averaging period
             const int ma_shift,                      // horizontal shift of the indicator
             const ENUM_APPLIED_PRICE applied_price   // price type or handle
            ) : CIndMSTF(IND_FRAMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("FRAMA");
      this.SetDescription("Fractal Adaptive Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Gator Oscillator indicator class                                 |
//+------------------------------------------------------------------+
class CIndGator : public CIndMSTF
  {
public:
//--- Constructor
   CIndGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int jaw_period,                    // period for jaw line calculation
             const int jaw_shift,                     // horizontal shift of jaw line
             const int teeth_period,                  // period for calculating teeth line
             const int teeth_shift,                   // horizontal shift of teeth line
             const int lips_period,                   // period for calculating lip line
             const int lips_shift,                    // horizontal shift of lip line
             const ENUM_MA_METHOD ma_method,          // smoothing type
             const ENUM_APPLIED_PRICE applied_price   // price type or handle
            ) : CIndMSTF(IND_GATOR,4,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_HISTOGRAM, 1 - color buffer of the upper histogram, 2 - LOWER_HISTOGRAM, 3 - color buffer of the lower histogram
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,8)==8)
        {
         ::ZeroMemory(this.m_param);
         //--- period for jaw line calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(jaw_period<1 ? 13 : jaw_period);
         //--- horizontal shift of the jaw line
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=jaw_shift;
         //--- period for teeth line calculation
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(teeth_period<1 ? 8 : teeth_period);
         //--- horizontal shift of teeth line
         this.m_param[3].type=TYPE_INT;
         this.m_param[3].integer_value=teeth_shift;
         //--- period for lip line calculation
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=(lips_period<1 ? 5 : lips_period);
         //--- horizontal shift of lips line
         this.m_param[5].type=TYPE_INT;
         this.m_param[5].integer_value=lips_shift;
         //--- smoothing type
         this.m_param[6].type=TYPE_UINT;
         this.m_param[6].integer_value=ma_method;
         //--- price type or handle
         this.m_param[7].type=TYPE_UINT;
         this.m_param[7].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),jaw_period,teeth_period,lips_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Gator");
      this.SetDescription("Gator Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      this.m_digits=::Digits()+1;
      //--- Description of line buffers UPPER_HISTOGRAM, upper histogram color buffer, LOWER_HISTOGRAM and lower histogram color buffer
      this.SetBufferDescription(UPPER_HISTOGRAM,this.m_title+" Up");
      this.SetBufferDescription(1,this.m_title+" Colors Up");
      this.SetBufferDescription(LOWER_HISTOGRAM,this.m_title+" Down");
      this.SetBufferDescription(3,this.m_title+" Colors Down");
      //--- Записываем смещения в буферы UPPER_HISTOGRAM, 1, LOWER_HISTOGRAM и 2
      this.SetBufferShift(UPPER_HISTOGRAM,teeth_shift);
      this.SetBufferShift(1,teeth_shift);
      this.SetBufferShift(LOWER_HISTOGRAM,lips_shift);
      this.SetBufferShift(3,lips_shift);
     }
  };
//+------------------------------------------------------------------+
//| Ichimoku Kinko Hyo indicator class                               |
//+------------------------------------------------------------------+
class CIndIchimoku : public CIndMSTF
  {
public:
//--- Constructor
   CIndIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                const int tenkan_sen,                    // period of Tenkan-sen
                const int kijun_sen,                     // period of Kijun-sen
                const int senkou_span_b                  // period of Senkou Span B
               ) : CIndMSTF(IND_ICHIMOKU,5,symbol,timeframe)
     {
      // Buffer indexes: 0 - TENKANSEN_LINE, 1 - KIJUNSEN_LINE, 2 - SENKOUSPANA_LINE, 3 - SENKOUSPANB_LINE, 4 - CHIKOUSPAN_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- period of Tenkan-sen
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(tenkan_sen<1 ? 9 : tenkan_sen);
         //--- period of Kijun-sen
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(kijun_sen<1 ? 26 : kijun_sen);
         //--- period of Senkou Span B
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(senkou_span_b<1 ? 52 : senkou_span_b);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),tenkan_sen,kijun_sen,senkou_span_b);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Ichimoku");
      this.SetDescription("Ichimoku Kinko Hyo");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Description of line buffers TENKANSEN_LINE, KIJUNSEN_LINE, SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE
      this.SetBufferDescription(TENKANSEN_LINE,::StringFormat("Tenkan-sen(%lu)",tenkan_sen));
      this.SetBufferDescription(KIJUNSEN_LINE,::StringFormat("Kijun-sen(%lu)",kijun_sen));
      this.SetBufferDescription(SENKOUSPANA_LINE,"Senkou Span A");
      this.SetBufferDescription(SENKOUSPANB_LINE,::StringFormat("Senkou Span B(%lu)",senkou_span_b));
      this.SetBufferDescription(CHIKOUSPAN_LINE,"Chikou Span");
      //--- Write shifts to buffers SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE
      //this.SetBufferShift(SENKOUSPANA_LINE,kijun_sen);
      //this.SetBufferShift(SENKOUSPANB_LINE,kijun_sen);
      //this.SetBufferShift(CHIKOUSPAN_LINE,kijun_sen-senkou_span_b);
     }
  };
//+------------------------------------------------------------------+
//| Market Facilitation Index indicator class                        |
//+------------------------------------------------------------------+
class CIndBWMFI : public CIndMSTF
  {
public:
//--- Constructor
   CIndBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const ENUM_APPLIED_VOLUME applied_volume // volume type for calculation
            ) : CIndMSTF(IND_BWMFI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- volume type for calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("BW MFI");
      this.SetDescription("Market Facilitation Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Momentum indicator class                                         |
//+------------------------------------------------------------------+
class CIndMomentum : public CIndMSTF
  {
public:
//--- Constructor
   CIndMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                const int                 mom_period,    // averaging period
                const ENUM_APPLIED_PRICE  applied_price  // price type or handle
               ) : CIndMSTF(IND_MOMENTUM,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(mom_period<1 ? 14 : mom_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),mom_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Momentum");
      this.SetDescription("Momentum");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Money Flow Index indicator class                                 |
//+------------------------------------------------------------------+
class CIndMFI : public CIndMSTF
  {
public:
//--- Constructor
   CIndMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int                 ma_period,       // averaging period
           const ENUM_APPLIED_VOLUME applied_volume   // volume type for calculation
          ) : CIndMSTF(IND_MFI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- volume type for calculation
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("MFI");
      this.SetDescription("Money Flow Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Moving Average indicator class                                   |
//+------------------------------------------------------------------+
class CIndMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
          const int                 ma_period,     // averaging period
          const int                 ma_shift,      // horizontal shift of the indicator
          const ENUM_MA_METHOD      ma_method,     // smoothing type
          const ENUM_APPLIED_PRICE  applied_price  // price type or handle
         ) : CIndMSTF(IND_MA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 10 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("MA");
      this.SetDescription("Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Moving Average of Oscillator indicator class                     |
//+------------------------------------------------------------------+
class CIndOsMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                fast_ema_period, // fast MA period
            const int                slow_ema_period, // slow MA period
            const int                signal_period,   // difference averaging period
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
           ) : CIndMSTF(IND_OSMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- fast MA period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(fast_ema_period<1 ? 12 : fast_ema_period);
         //--- slow MA period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(slow_ema_period<1 ? 26 : slow_ema_period);
         //--- difference averaging period
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(signal_period<1 ? 9 : signal_period<2 ? 2 : signal_period);
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("OsMA");
      this.SetDescription("Moving Average of Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Moving Averages Convergence/Divergence indicator class           |
//+------------------------------------------------------------------+
class CIndMACD : public CIndMSTF
  {
public:
//--- Constructor
   CIndMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                fast_ema_period, // fast MA period
            const int                slow_ema_period, // slow MA period
            const int                signal_period,   // difference averaging period
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
           ) : CIndMSTF(IND_MACD,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - SIGNAL_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- fast MA period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(fast_ema_period<1 ? 12 : fast_ema_period);
         //--- slow MA period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(slow_ema_period<1 ? 26 : slow_ema_period);
         //--- difference averaging period
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(signal_period<1 ? 9 : signal_period);
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("MACD");
      this.SetDescription("Moving Averages Convergence/Divergence");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Description of MAIN_LINE and SIGNAL_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(SIGNAL_LINE,"Signal");
     }
  };
//+------------------------------------------------------------------+
//| On Balance Volume indicator class                                |
//+------------------------------------------------------------------+
class CIndOBV : public CIndMSTF
  {
public:
//--- Constructor
   CIndOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const ENUM_APPLIED_VOLUME applied_volume   // volume type for calculation
          ) : CIndMSTF(IND_OBV,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- volume type for calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("OBV");
      this.SetDescription("On Balance Volume");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Parabolic Stop and Reverse system indicator class                |
//+------------------------------------------------------------------+
class CIndSAR : public CIndMSTF
  {
public:
//--- Constructor
   CIndSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const double step,                      // price change step — acceleration factor
           const double maximum                    // maximum step
          ) : CIndMSTF(IND_SAR,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- price change step — acceleration factor
         this.m_param[0].type=TYPE_DOUBLE;
         this.m_param[0].double_value=step;
         //--- maximum step
         this.m_param[1].type=TYPE_DOUBLE;
         this.m_param[1].double_value=maximum;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%.2f,%.2f)",symbol_period,(current ? "" : ":"),step,maximum);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("SAR");
      this.SetDescription("Parabolic SAR");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Класс индикатора Relative Strength Index                         |
//+------------------------------------------------------------------+
class CIndRSI : public CIndMSTF
  {
public:
//--- Constructor
   CIndRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int                ma_period,     // averaging period
           const ENUM_APPLIED_PRICE applied_price  // price type or handle
          ) : CIndMSTF(IND_RSI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period<2 ? 2 : ma_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("RSI");
      this.SetDescription("Relative Strength Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Relative Vigor Index indicator class                             |
//+------------------------------------------------------------------+
class CIndRVI : public CIndMSTF
  {
public:
//--- Constructor
   CIndRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period                     // averaging period
          ) : CIndMSTF(IND_RVI,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - SIGNAL_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 10 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("RVI");
      this.SetDescription("Relative Vigor Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=3;
      //--- Description of MAIN_LINE and SIGNAL_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(SIGNAL_LINE,"Signal");
     }
  };
//+------------------------------------------------------------------+
//| Standard Deviation indicator class                               |
//+------------------------------------------------------------------+
class CIndStdDev : public CIndMSTF
  {
public:
//--- Constructor
   CIndStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
              const int                ma_period,        // averaging period
              const int                ma_shift,         // horizontal shift of the indicator
              const ENUM_MA_METHOD     ma_method,        // smoothing type
              const ENUM_APPLIED_PRICE applied_price     // price type or handle
             ) : CIndMSTF(IND_STDDEV,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 20 : ma_period<2 ? 2 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("StdDev");
      this.SetDescription("Standard Deviation");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Stochastic Oscillator indicator class                            |
//+------------------------------------------------------------------+
class CIndStoch : public CIndMSTF
  {
public:
//--- Constructor
   CIndStoch(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int              Kperiod,          // K-period (number of bars for calculations)
             const int              Dperiod,          // D-period (primary smoothing period)
             const int              slowing,          // final smoothing
             const ENUM_MA_METHOD   ma_method,        // smoothing type
             const ENUM_STO_PRICE   price_field       // Stochastic calculation method
            ) : CIndMSTF(IND_STOCHASTIC,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - SIGNAL_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,5)==5)
        {
         ::ZeroMemory(this.m_param);
         //--- K period (number of bars for calculation)
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(Kperiod<1 ? 5 : Kperiod);
         //--- D period (primary smoothing period)
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(Dperiod<1 ? 3 : Dperiod);
         //--- final smoothing
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(slowing<1 ? 3 : slowing);
         //--- smoothing type
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=ma_method;
         //--- Stochastic calculation method
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=price_field;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),Kperiod,Dperiod,slowing);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Stoch");
      this.SetDescription("Stochastic Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Description of MAIN_LINE and SIGNAL_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(SIGNAL_LINE,"Signal");
     }
  };
//+------------------------------------------------------------------+
//| Triple Exponential Moving Average indicator class                |
//+------------------------------------------------------------------+
class CIndTEMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                  ma_period,     // averaging period
            const int                  ma_shift,      // horizontal shift of the indicator
            const ENUM_APPLIED_PRICE   applied_price  // price type or handle
           ) : CIndMSTF(IND_TEMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("TEMA");
      this.SetDescription("Triple Exponential Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Triple Exponential Moving Averages Oscillator indicator class    |
//+------------------------------------------------------------------+
class CIndTriX : public CIndMSTF
  {
public:
//--- Constructor
   CIndTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                ma_period,       // averaging period
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
           ) : CIndMSTF(IND_TRIX,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period<2 ? 2 : ma_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("TRIX");
      this.SetDescription("Triple Exponential Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Larry Williams' Percent Range indicator class                    |
//+------------------------------------------------------------------+
class CIndWPR : public CIndMSTF
  {
public:
//--- Constructor
   CIndWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int calc_period                   // averaging period
          ) : CIndMSTF(IND_WPR,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(calc_period<1 ? 14 : calc_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),calc_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("%R");
      this.SetDescription("Williams' Percent Range");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Variable Index Dynamic Average indicator class                   |
//+------------------------------------------------------------------+
class CIndVIDyA : public CIndMSTF
  {
public:
//--- Constructor
   CIndVIDyA(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int                 cmo_period,    // the Chande Momentum period
             const int                 ema_period,    // period of the smoothing factor
             const int                 ma_shift,      // horizontal shift of the indicator
             const ENUM_APPLIED_PRICE  applied_price  // price type or handle
            ) : CIndMSTF(IND_VIDYA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- Chande Momentum period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(cmo_period<1 ? 9 : cmo_period);
         //--- smoothing factor period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(ema_period<1 ? 12 : ema_period);
         //--- horizontal shift of the indicator
         this.m_param[2].type=TYPE_INT;
         this.m_param[2].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),cmo_period,ema_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("VIDYA");
      this.SetDescription("Variable Index Dynamic Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Volumes indicator class                                          |
//+------------------------------------------------------------------+
class CIndVolumes : public CIndMSTF
  {
public:
//--- Constructor
   CIndVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,
               const ENUM_APPLIED_VOLUME applied_volume  // volume type
              ) : CIndMSTF(IND_VOLUMES,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- volume type
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Volumes");
      this.SetDescription("Volumes");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Custom indicator class                                           |
//+------------------------------------------------------------------+
class CIndCustom : public CIndMSTF
  {
public:
//--- Constructor
   CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,
              const string path,                      // path to the indicator (for example, "Examples\\MACD.ex5")
              const string name,                      // name of the custom indicator
              const uint   buffers,                   // number of indicator buffers
              const MqlParam &param[]                 // array of custom indicator parameters
             ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe)
     {
      //--- If an empty array of parameters is passed, print this to log
      int total=(int)param.Size();
      if(total==0)
         ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__);
      //--- If the array is not empty and its size is increased by 1 (the first string parameter must contain the indicator name)
      ResetLastError();
      if(total>0 && ::ArrayResize(this.m_param,total+1)==total+1)
        {
         //--- Reset data in the array and enter name (path to file and name of .ex5 file)
         ::ZeroMemory(this.m_param);
         //--- name of the custom indicator
         this.m_param[0].type=TYPE_STRING;
         this.m_param[0].string_value=path;
         //--- fill the array of indicator parameters
         for(int i=0;i<total;i++)
           {
            this.m_param[i+1].type=param[i].type;
            this.m_param[i+1].double_value=param[i].double_value;
            this.m_param[i+1].integer_value=param[i].integer_value;
            this.m_param[i+1].string_value=param[i].string_value;
           }
         //--- Create description of parameters
         //--- If non-current chart symbol or period, their descriptions are added to parameters
         bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
         string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
         string param=(current ? "" : StringFormat("(%s)",symbol_period));
         //--- Write description of parameters, indicator name, its description, title and category
         this.SetParameters(param);
         this.SetName(name);
         this.SetDescription(name);
         this.m_title=this.Name()+this.Parameters();
         this.m_category=IND_CATEGORY_CUSTOM;
         //--- Write a description of the first line buffer
         this.SetBufferDescription(0,this.m_title);
        }
     }
  };

The presented list contains all the classes for creating all the technical indicators available in the client terminal and additionally provides a class for creating a custom indicator. All classes are identical and their main points are commented in the class code.

Please note that testing covered the creation and operation of some of the above indicators. Namely, only indicators based on moving averages that plot one line on the main chart were tested. In the next articles, we will write templates for creating multi-symbol, multi-period versions of all types of technical indicators.

In fact, everything is already ready to create multi-indicators. But we will go beyond this idea and will create a convenient tool for automating the creation and use of multi-symbol, multi-period indicators. This will be a collection class of indicators, allowing you to easily create any indicators based on standard or custom ones, turning them into multi-symbol, multi-period indicators.


Indicator collection class

The indicator collection class is essentially a regular list of pointers to objects. To this list, we will add methods for conveniently creating indicators and adding newly created ones to the collection. It will also be possible to easily get a pointer to the desired indicator and use the necessary data from it. When adding a new indicator to a collection, the presence of exactly the same indicator in the collection will first be checked. If the same indicator already exists in the collection, the newly created object will be deleted and a pointer to the one present in the collection will be returned. This avoids the situation where two different objects refer to the same calculation part.

The structure of the collection class will repeat the structure of the basic multi-symbol multi-period indicator class. The only difference is that most methods must first find the required indicator by its handle and then call the relevant method to set or get its value or action result.

We will use the same file \MQL5\Include\IndMSTF\IndMSTF.mqh and will proceed with the addition of a new class. For its operation, you will need to include the file of the class with the dynamic array of pointers to CObject class instances and its derived classes CArrayObj:

//+------------------------------------------------------------------+
//| Indicator collection class                                       |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
class CMSTFIndicators
  {
private:

public:
  
  }

In the class body, add the list object and methods for class operations:

//+------------------------------------------------------------------+
//| Indicator collection class                                       |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
class CMSTFIndicators
  {
private:
   CArrayObj         m_list;
//--- Creates an indicator for the passed object
   bool              CreateIndicator(CIndMSTF *ind_obj);
//--- Adds the specified indicator to the collection
   int               AddNewIndicator(CIndMSTF *ind_obj,const string source);

public:
//--- Returns (1) indicator object by handle, (2) number of indicators in the collection
   CIndMSTF         *GetIndicatorObj(const int ind_handle,const string source) const;
   uint              IndicatorsTotal(void)                  const { return this.m_list.Total(); }

//--- Populates buffers of (1) the indicator by handle, (2) all indicators in the collection
   bool              Calculate(const int ind_handle);
   bool              Calculate(void);
//--- Sets the (1) specified, (2) default description of the indicator buffer line
   void              SetPlotLabel(const uint plot_index,const string descript);
   void              SetPlotLabelFromBuffer(const uint plot_index,const int ind_handle,const uint buffer_num);
//--- Sets the shift to the specified plotting buffer
   void              SetPlotShift(const uint plot_index,const int shift);
//--- (1) Sets (2) returns the initializing value of the given buffer specified by the indicator handle
   void              SetBufferInitValue(const int ind_handle,const uint buffer_num,const double value);
   double            BufferInitValue(const int ind_handle,const uint buffer_num) const;

//--- Returns indicator data by handle from the specified buffer at index (1) as is, (2) for the specified symbol/timeframe
   double            GetData(const int ind_handle,const uint buffer_num,const uint index);
   double            GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const uint index);

//--- (1) Copies data of the specified calculation part buffer of the indicator by handle into the indicator buffer, taking into account chart symbol/period,
//--- (2) returns the amount of data in the specified buffer of the indicator by handle
   bool              DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const int limit,double &buffer[]);
   uint              DataTotal(const int ind_handle,const uint buffer_num) const;

   //--- Returns (1) buffer description, (2) state of the line data of given buffer of indicator specified by handle on the specified bar
   //--- (3) indicator line state for the specific chart symbol/period, (4) indicator line state relation to the specified level,
   //--- (5)  state of relation of indicator line with specified level for certain chart symbol/period, (6) indicator category description
   string            BufferDescription(const int ind_handle,const uint buffer_num);
   ENUM_LINE_STATE   BufferLineState(const int ind_handle,const uint buffer_num,const int index);
   ENUM_LINE_STATE   BufferLineState(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const uint buffer_num,const int index);
   ENUM_LINE_STATE   BufferLineStateRelative(const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);
   ENUM_LINE_STATE   BufferLineStateRelative(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);
   string            CategoryDescription(const int ind_handle);
   
//--- Sets (1) identifier, (2) Digits, (3) user description, (4) buffer description
   void              SetID(const int ind_handle,const int id);
   void              SetDigits(const int ind_handle,const int digits);
   void              SetDescription(const int ind_handle,const string descr);
   void              SetBufferDescription(const int ind_handle,const uint buffer_num,const string descr);

//--- Returns flag of whether the buffer is set as series, (2) historical data for symbol/period is synchronized
   bool              IsSeries(const int ind_handle,const uint buffer_num) const;
   bool              IsSynchronized(const int ind_handle) const;
   
//--- Returns (1) timeframe, (2) symbol, (3) name, (4) list of parameters, (5) handle, (6) Digits
//--- number of (7) buffers, (8) bars, (9) identifier, (10) description, (11) title, (12) category,
//--- (13) number of parameters, (14) program type, description of (15) category, (16) indicator buffer
   ENUM_TIMEFRAMES   Timeframe(const int ind_handle) const;
   string            Symbol(const int ind_handle) const;
   string            Name(const int ind_handle) const;
   string            Parameters(const int ind_handle) const;
   int               Digits(const int ind_handle) const;
   uint              BuffersTotal(const int ind_handle) const;
   uint              RatesTotal(const int ind_handle) const;
   int               ID(const int ind_handle) const;
   string            Description(const int ind_handle) const;
   string            Title(const int ind_handle) const;
   ENUM_IND_CATEGORY Category(const int ind_handle) const;
   uint              ParamsTotal(const int ind_handle) const;
//--- Returns (1) structure of parameters by index from array, (2) timeframe description
   MqlParam          GetMqlParam(const int ind_handle,const int index) const;
   string            TimeframeDescription(const int ind_handle)    const;
//--- Returns amount of calculated data
   int               Calculated(const int ind_handle) const;
   
//--- Virtual method returning the type of object (indicator)
      ENUM_INDICATOR    Type(const int ind_handle) const;
   
//--- Methods for adding indicators to the collection
   int               AddNewAC(const string symbol,const ENUM_TIMEFRAMES timeframe);
   int               AddNewAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14);
   int               AddNewADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14);
   int               AddNewAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,const int jaw_period=13,
                                                                                         const int jaw_shift=8,
                                                                                         const int teeth_period=8,
                                                                                         const int teeth_shift=5,
                                                                                         const int lips_period=5,
                                                                                         const int lips_shift=3,
                                                                                         const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                                                                         const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN);
   int               AddNewAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ama_period=9,
                                                                                   const int fast_ma_period=2,
                                                                                   const int slow_ma_period=30,
                                                                                   const int ama_shift=0,
                                                                                   const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewAO(const string symbol,const ENUM_TIMEFRAMES timeframe);
   int               AddNewATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14);
   int               AddNewBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13);
   int               AddNewBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13);
   int               AddNewBands(const string symbol,const ENUM_TIMEFRAMES timeframe,const int bands_period=20,
                                                                                     const int bands_shift=0,
                                                                                     const double deviation=2.0,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                   const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL);
   int               AddNewChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,const int fast_ma_period=3,
                                                                                       const int slow_ma_period=10,
                                                                                       const ENUM_MA_METHOD ma_method=MODE_EMA,
                                                                                       const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                    const int ma_shift=0,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14);
   int               AddNewEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                         const int ma_shift=0,
                                                                                         const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                         const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE,
                                                                                         const double deviation=0.1);
   int               AddNewForce(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13,
                                                                                     const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                     const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewFractals(const string symbol,const ENUM_TIMEFRAMES timeframe);
   int               AddNewFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                     const int ma_shift=0,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewGator(const string symbol,const ENUM_TIMEFRAMES timeframe,const int jaw_period=13,
                                                                                     const int jaw_shift=8,
                                                                                     const int teeth_period=8,
                                                                                     const int teeth_shift=5,
                                                                                     const int lips_period=5,
                                                                                     const int lips_shift=3,
                                                                                     const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN);
   int               AddNewIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,const int tenkan_sen=9,
                                                                                        const int kijun_sen=26,
                                                                                        const int senkou_span_b=52);
   int               AddNewBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,const int mom_period=14,
                                                                                        const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                   const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=10,
                                                                                  const int ma_shift=0,
                                                                                  const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int fast_ema_period=12,
                                                                                    const int slow_ema_period=26,
                                                                                    const int signal_period=9,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,const int fast_ema_period=12,
                                                                                    const int slow_ema_period=26,
                                                                                    const int signal_period=9,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,const double step=0.02,
                                                                                   const double maximum=0.2);
   int               AddNewRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                   const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=10);
   int               AddNewStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=20,
                                                                                      const int ma_shift=0,
                                                                                      const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                      const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,const int Kperiod=5,
                                                                                          const int Dperiod=3,
                                                                                          const int slowing=3,
                                                                                          const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                          const ENUM_STO_PRICE price_field=STO_LOWHIGH);
   int               AddNewTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                    const int ma_shif=0,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period=14);
   int               AddNewVIDyA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int cmo_period=9,
                                                                                     const int ema_period=12,
                                                                                     const int ma_shift=0,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,       // path to the indicator (for example, "Examples\\MACD.ex5")
                                                                                      const string name,       // name of custom indicator (for example, "Custom MACD")
                                                                                      const uint   buffers,    // number of buffers
                                                                                      const MqlParam &param[]);// array of parameters
//--- Timer
   void OnTimer(void)
     {
      //--- In a loop through all indicators form the collection
      int total=this.m_list.Total();
      for(int i=0;i<total;i++)
        {
         //--- get a pointer to the next indicator object
         //--- and call its timer
         CIndMSTF *obj=this.m_list.At(i);
         if(obj!=NULL)
            obj.OnTimer();
        }
     }
//--- Constructor/destructor
                     CMSTFIndicators(void){ this.m_list.Clear(); }
                    ~CMSTFIndicators(void){;}
  };


Implementation of methods for working with indicators in the list:

//+------------------------------------------------------------------+
//| Creates indicator calculation part for the passed object         |
//+------------------------------------------------------------------+
bool CMSTFIndicators::CreateIndicator(CIndMSTF *ind_obj)
  {
   //--- If the calculation part of the indicator could not be created
   if(!ind_obj.CreateIndicator())
     {
      //--- look for the index of the indicator object in the collection list
      //--- using the index, delete the indicator object from the collection list
      this.m_list.Sort();
      int index=this.m_list.Search(ind_obj);
      this.m_list.Delete(index);
      //--- Return false
      return false;
     }
//--- The calculation part has been successfully created - return true
   return true;
  }
//+------------------------------------------------------------------+
//| Returns indicator object by the calculation part handle          |
//+------------------------------------------------------------------+
CIndMSTF *CMSTFIndicators::GetIndicatorObj(const int ind_handle,const string source) const
  {
//--- If an invalid handle is passed to the method, report this and return NULL
   if(ind_handle==INVALID_HANDLE)
     {
      ::PrintFormat("%s: Error handle",source);
      return NULL;
     }
//--- In a loop through all indicator objects in the collection list
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get a pointer to the next indicator object
      CIndMSTF *obj=this.m_list.At(i);
      if(obj==NULL)
         continue;
      //--- If the indicator handle is equal to that passed to the method -
      //--- return a pointer to the found indicator object
      if(obj.Handle()==ind_handle)
         return obj;
     }
//--- Nothing is found - return NULL
   return NULL;
  }
//+------------------------------------------------------------------+
//| Populate buffers of the indicator at the handle                  |
//+------------------------------------------------------------------+
bool CMSTFIndicators::Calculate(const int ind_handle)
  {
   //--- Get a pointer to an indicator object using the handle
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
      return false;
   //--- Return the result of the Calculate method obtained from the handle of the indicator object
   return obj.Calculate();
  }
//+------------------------------------------------------------------+
//| Populate buffers of all indicators in the collection             |
//+------------------------------------------------------------------+
bool CMSTFIndicators::Calculate(void)
  {
   //--- Declare the variable for storing the result
   bool res=true;
//--- In a loop through all indicator objects in the collection list
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get a pointer to the next indicator object
      CIndMSTF *obj=this.m_list.At(i);
      if(obj==NULL)
         continue;
      //--- Add to the 'res' variable the result of calling the Calculate method of the next indicator object
      res &=obj.Calculate();
      //--- If the method worked with an error, inform that in the journal
      if(!res)
         ::PrintFormat("%s::%s: Error in indicator calculation: %s",__FUNCTION__,obj.Title(),TypeErrorcDescription(obj.TypeError()));
     }
//--- If the overall result is false, inform of that in the journal
   if(!res)
      ::PrintFormat("%s: Not all indicators have been calculated successfully. It is necessary to recalculate the buffers of all indicators",__FUNCTION__);
//--- Return the result of calling the Calculate methods of all indicators in the collection
   return res;
  }
//+------------------------------------------------------------------+
//| Returns data of the indicator at the handle                      |
//| from the specified buffers by index as is                        |
//+------------------------------------------------------------------+
double CMSTFIndicators::GetData(const int ind_handle,const uint buffer_num,const uint index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return EMPTY_VALUE;
     }
//--- Return data from the specified indicator buffer at the index passed to the method
   return obj.GetData(buffer_num,index);
  }
//+------------------------------------------------------------------+
//| Returns data of the indicator at the handle                      |
//| from the specified buffer at index for this symbol/timeframe     |
//+------------------------------------------------------------------+
double CMSTFIndicators::GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const uint index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return EMPTY_VALUE;
     }
//--- Return data from the specified indicator buffer at the index passed to the method
   return obj.GetDataTo(symbol_to,timeframe_to,buffer_num,index);
  }
//+------------------------------------------------------------------+
//| Fills the passed indicator buffer with data                      |
//+------------------------------------------------------------------+
bool CMSTFIndicators::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const int limit,double &buffer[])
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return false;
     }
//--- Fill the buffer array passed to the method from the specified indicator buffer
   return obj.DataToBuffer(symbol_to,timeframe_to,buffer_num,limit,buffer);
  }
//+------------------------------------------------------------------+
//| Set the specified description for the buffer line                |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetPlotLabel(const uint plot_index,const string descript)
  {
   ::PlotIndexSetString(plot_index,PLOT_LABEL,descript);
  }
//+------------------------------------------------------------------+
//| Set default description for the buffer line                      |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetPlotLabelFromBuffer(const uint plot_index,const int ind_handle,const uint buffer_num)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set the description of the specified indicator buffer to the specified plotting buffer
   ::PlotIndexSetString(plot_index,PLOT_LABEL,obj.BufferDescription(buffer_num));
  }
//+------------------------------------------------------------------+
//| Set the shift for the specified plotting buffer                  |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetPlotShift(const uint plot_index,const int shift)
  {
   ::PlotIndexSetInteger(plot_index,PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+
//| Return the description of the given buffer                       |
//| of the indicator specified by handle                             |
//+------------------------------------------------------------------+
string CMSTFIndicators::BufferDescription(const int ind_handle,const uint buffer_num)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If pointer to object received, return description of the specified buffer from it. Otherwise error text
   return(obj!=NULL ? obj.BufferDescription(buffer_num) : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Set the initializing value for the specified buffer              |
//| of the indicator specified by handle                             |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetBufferInitValue(const int ind_handle,const uint buffer_num,const double value)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set the specified initializing "empty" value for the specified buffer
   obj.SetBufferInitValue(buffer_num,value);
  }
//+------------------------------------------------------------------+
//| Return the initialization value of the specified buffer          |
//| of the indicator specified by handle                             |
//+------------------------------------------------------------------+
double CMSTFIndicators::BufferInitValue(const int ind_handle,const uint buffer_num) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the initializing "empty" value set for the specified buffer
   return obj.BufferInitValue(buffer_num);
  }
//+------------------------------------------------------------------+
//| Returns the line data state of the given buffer                  |
//| specified by indicator handle at the specified bar               |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineState(const int ind_handle,const uint buffer_num,const int index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Return the state of the line of the specified buffer at the specified index
   return obj.BufferLineState(buffer_num,index);
  }
//+------------------------------------------------------------------+
//| Returns the line data state of the given buffer                  |
//| specified by indicator handle at the symbol/timeframe bar        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineState(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const uint buffer_num,const int index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Get the time of the passed to the method
   datetime array[1];
   if(::CopyTime(symbol,timeframe,index,1,array)!=1)
     {
      ::PrintFormat("%s::%s: Failed to get the time of the bar with index %ld. Error %lu",__FUNCTION__,obj.Title(),index,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get the bar index in the indicator object buffer corresponding to the found time
   int bar=::iBarShift(obj.Symbol(),obj.Timeframe(),array[0]);
//--- If a bar is found, return the line state on the found bar, otherwise an undefined state
   return(bar!=WRONG_VALUE ? obj.BufferLineState(buffer_num,bar) : LINE_STATE_NONE);
  }
//+------------------------------------------------------------------+
//| Return the line data ratio for the given buffer                  |
//| specified by indicator handle at the specified bar               |
//| with specified valiues                                           |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineStateRelative(const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Return the ratio of the indicator line and the level in the specified buffer at the specified index
   return obj.BufferLineStateRelative(buffer_num,index,level0,level1);
  }
//+------------------------------------------------------------------+
//| Return the line data ratio for the given buffer                  |
//| specified by indicator handle at the specified bar               |
//| with the specified values on the specified chart symbol/period   |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineStateRelative(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Return the ratio of the indicator line and the level in the specified buffer at the specified index
   return obj.BufferLineStateRelative(symbol,timeframe,buffer_num,index,level0,level1);
  }
//+------------------------------------------------------------------+
//| Return category description                                      |
//+------------------------------------------------------------------+
string CMSTFIndicators::CategoryDescription(const int ind_handle)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the description of the category Otherwise error text
   return(obj!=NULL ? obj.CategoryDescription() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Set identifier                                                   |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetID(const int ind_handle,const int id)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set the identifier for the received object
   obj.SetID(id);
  }
//+------------------------------------------------------------------+
//| Set Digits of the indicator                                      |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetDigits(const int ind_handle,const int digits)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set Digits for the received object
   obj.SetDigits(digits);
  }
//+------------------------------------------------------------------+
//| Set a custom description                                         |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetDescription(const int ind_handle,const string descr)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set a description for the received object
   obj.SetDescription(descr);
  }
//+------------------------------------------------------------------+
//| Set a description of the specified buffer                        |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetBufferDescription(const int ind_handle,const uint buffer_num,const string descr)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set a description for the specified buffer of the received object
   obj.SetBufferDescription(buffer_num,descr);
  }
//+------------------------------------------------------------------+
//| Returns the timeseries flag of the given buffer                  |
//+------------------------------------------------------------------+
bool CMSTFIndicators::IsSeries(const int ind_handle,const uint buffer_num) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return false;
     }
//--- Return the timeseries flag of the specified buffer of the received object
   return obj.IsSeries(buffer_num);
  }
//+------------------------------------------------------------------+
//| Returns the synchronization flag for                             |
//| historical data for the symbol/period                            |
//+------------------------------------------------------------------+
bool CMSTFIndicators::IsSynchronized(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return false;
     }
//--- Return the synchronization flag of the received object
   return obj.IsSynchronized();
  }
//+------------------------------------------------------------------+
//| Return the timeframe of the specified indicator                  |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CMSTFIndicators::Timeframe(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the timeframe of the received object
   return obj.Timeframe();
  }
//+------------------------------------------------------------------+
//| Returns the symbol of the specified indicator                    |
//+------------------------------------------------------------------+
string CMSTFIndicators::Symbol(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the name of the symbol Otherwise error text
   return(obj!=NULL ? obj.Symbol() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the name of the specified indicator                       |
//+------------------------------------------------------------------+
string CMSTFIndicators::Name(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the name of the indicator Otherwise error text
   return(obj!=NULL ? obj.Name() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Returns a list of parameters of the specified indicator          |
//+------------------------------------------------------------------+
string CMSTFIndicators::Parameters(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return a list of indicator parameters Otherwise error text
   return(obj!=NULL ? obj.Parameters() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return Digits of the specified indicator                         |
//+------------------------------------------------------------------+
int CMSTFIndicators::Digits(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return Digits of the received object
   return obj.Digits();
  }
//+------------------------------------------------------------------+
//| Return the number of buffers of the specified indicator          |
//+------------------------------------------------------------------+
uint CMSTFIndicators::BuffersTotal(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return 0;
     }
//--- Return the number of buffers of the received object
   return obj.BuffersTotal();
  }
//+------------------------------------------------------------------+
//| Return the number of timeseries bars for specified the indicator |
//+------------------------------------------------------------------+
uint CMSTFIndicators::RatesTotal(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return 0;
     }
//--- Return the number of bars in the timeseries of the received object
   return obj.RatesTotal();
  }
//+------------------------------------------------------------------+
//| Return the identifier of the specified indicator                 |
//+------------------------------------------------------------------+
int CMSTFIndicators::ID(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the identifier of the received object
   return obj.ID();
  }
//+------------------------------------------------------------------+
//| Return a description of the specified indicator                  |
//+------------------------------------------------------------------+
string CMSTFIndicators::Description(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the indicator description Otherwise error text
   return(obj!=NULL ? obj.Description() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the title of the specified indicator                      |
//+------------------------------------------------------------------+
string CMSTFIndicators::Title(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the indicator title Otherwise error text
   return(obj!=NULL ? obj.Title() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the category of the specified indicator                   |
//+------------------------------------------------------------------+
ENUM_IND_CATEGORY CMSTFIndicators::Category(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return IND_CATEGORY_NONE;
     }
//--- Return the category of the received object
   return obj.Category();
  }
//+------------------------------------------------------------------+
//| Return the number of parameters of the specified indicator       |
//+------------------------------------------------------------------+
uint CMSTFIndicators::ParamsTotal(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return 0;
     }
//--- Return the number of parameters of the received object
   return obj.ParamsTotal();
  }
//+------------------------------------------------------------------+
//| Return the structure of parameters by index from the array       |
//| for the specified indicator                                      |
//+------------------------------------------------------------------+
MqlParam CMSTFIndicators::GetMqlParam(const int ind_handle,const int index) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      MqlParam null;
      ::ZeroMemory(null);
      return null;
     }
//--- Return the structure of parameters of the received object by index from the array of parameters
   return obj.GetMqlParam(index);
  }
//+------------------------------------------------------------------+
//| Return a timeframe description for the specified indicator       |
//+------------------------------------------------------------------+
string CMSTFIndicators::TimeframeDescription(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return a description of the indicator timeframe Otherwise error text
   return(obj!=NULL ? obj.Description() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the amount of calculated data of the specified indicator  |
//+------------------------------------------------------------------+
int CMSTFIndicators::Calculated(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the amount of calculated data of the received object
   return obj.Calculated();
  }
//+------------------------------------------------------------------+
//| Return the type of the specified indicator                       |
//+------------------------------------------------------------------+
ENUM_INDICATOR CMSTFIndicators::Type(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return (ENUM_INDICATOR)WRONG_VALUE;
     }
//--- Return the indicator type of the received object
   return (ENUM_INDICATOR)obj.Type();
  }

The logic of all methods is commented in the method listing. First, we get a pointer to the required indicator in the list, and then set or return its property or perform a calculation and return its result. All called methods were discussed above when discussing the base class of the multi-symbol, multi-period indicator.

Implementation of methods for creating new indicator objects:

//+------------------------------------------------------------------+
//| Add the specified indicator to the collection                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewIndicator(CIndMSTF *ind_obj,const string source)
  {
//--- Set the sorted list flag to the collection list
   this.m_list.Sort();
//--- Search the list for an index matching the indicator object passed to the method
   int index=this.m_list.Search(ind_obj);
//--- If such an indicator with the same parameters is already in the list,
   if(index>WRONG_VALUE)
     {
      //--- report this to journal and delete the new indicator object
      ::PrintFormat("%s: The %s indicator with such parameters %s is already in the collection",source,ind_obj.Name(),ind_obj.Parameters());
      delete ind_obj;
      //--- Get a pointer to an already existing indicator object in the list and return its handle
      ind_obj=this.m_list.At(index);
      return(ind_obj!=NULL ? ind_obj.Handle() : INVALID_HANDLE);
     }
//--- If such an indicator is not in the list, but it could not be placed in the list
   if(!this.m_list.Add(ind_obj))
     {
      //--- report the error in the journal, delete the indicator object and return INVALID_HANDLE
      ::PrintFormat("%s: Error. Failed to add %s indicator to collection",source,ind_obj.Name());
      delete ind_obj;
      return INVALID_HANDLE;
     }
//--- If indicator is placed in list, but creating a calculation part for it failed, return INVALID_HANDLE
//--- (if there is an error creating a calculation part, the indicator object is deleted in the CreateIndicator method)
   if(!this.CreateIndicator(ind_obj))
      return INVALID_HANDLE;
//--- Successful - inform about addition of a new indicator to collection and return its handle
   ::PrintFormat("%s: %s indicator (handle %ld) added to the collection",source,ind_obj.Title(),ind_obj.Handle());
   return ind_obj.Handle();
  }
//+------------------------------------------------------------------+
//| Add the Accelerator Oscillator indicator to the collection       |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAC(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAC *ind_obj=new CIndAC(symbol,timeframe);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create AC indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Accumulation/Distribution indicator to the collection     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAD *ind_obj=new CIndAD(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create A/D indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+--------------------------------------------------------------------+
//| Add Average Directional Movement Index to the collection           |
//+--------------------------------------------------------------------+
int CMSTFIndicators::AddNewADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndADX *ind_obj=new CIndADX(symbol,timeframe,adx_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create ADX indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add to the collection the indicator                              |
//| Average Directional Movement Index by Welles Wilder              |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndADXW *ind_obj=new CIndADXW(symbol,timeframe,adx_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create ADX Wilder indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Alligator indicator to the collection                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const int jaw_period=13,
                                      const int jaw_shift=8,
                                      const int teeth_period=8,
                                      const int teeth_shift=5,
                                      const int lips_period=5,
                                      const int lips_shift=3,
                                      const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                      const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAlligator *ind_obj=new CIndAlligator(symbol,timeframe,jaw_period,jaw_shift,teeth_period,teeth_shift,lips_period,lips_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Alligator indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Adaptive Moving Average indicator to the collection      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ama_period=9,
                                const int fast_ma_period=2,
                                const int slow_ma_period=30,
                                const int ama_shift=0,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAMA *ind_obj=new CIndAMA(symbol,timeframe,ama_period,fast_ma_period,slow_ma_period,ama_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create AMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Awesome Oscillator indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAO(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAO *ind_obj=new CIndAO(symbol,timeframe);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create AO indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Average True Range indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndATR *ind_obj=new CIndATR(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create ATR indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Bears Power indicator to the collection                  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBears *ind_obj=new CIndBears(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Bears indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Bulls Power indicator to the collection                  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBulls *ind_obj=new CIndBulls(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Bulls indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Bollinger Bands® indicator to the collection             |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int bands_period=20,
                                  const int bands_shift=0,
                                  const double deviation=2.0,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBands *ind_obj=new CIndBands(symbol,timeframe,bands_period,bands_shift,deviation,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Bands indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Commodity Channel Index indicator to the collection      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=14,
                               const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCCI *ind_obj=new CIndCCI(symbol,timeframe,ma_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create CCI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Chaikin Oscillator indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const int fast_ma_period=3,
                                    const int slow_ma_period=10,
                                    const ENUM_MA_METHOD ma_method=MODE_EMA,
                                    const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCHO *ind_obj=new CIndCHO(symbol,timeframe,fast_ma_period,slow_ma_period,ma_method,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Chaikin indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+-------------------------------------------------------------------+
//| Add Double Exponential Moving Average to the collection           |
//+-------------------------------------------------------------------+
int CMSTFIndicators::AddNewDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ma_period=14,
                                const int ma_shift=0,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndDEMA *ind_obj=new CIndDEMA(symbol,timeframe,ma_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create DEMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the DeMarker indicator to the collection                     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndDeM *ind_obj=new CIndDeM(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create DeMarker indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Envelopes indicator to the collection                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const int ma_period=14,
                                      const int ma_shift=0,
                                      const ENUM_MA_METHOD ma_method=MODE_SMA,
                                      const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE,
                                      const double deviation=0.1)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndEnvelopes *ind_obj=new CIndEnvelopes(symbol,timeframe,ma_method,ma_shift,ma_method,applied_price,deviation);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Envelopes indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Force Index indicator to the collection                  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int ma_period=13,
                                  const ENUM_MA_METHOD ma_method=MODE_SMA,
                                  const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndForce *ind_obj=new CIndForce(symbol,timeframe,ma_period,ma_method,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Force indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Fractals indicator to the collection                     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewFractals(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndFractals *ind_obj=new CIndFractals(symbol,timeframe);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Fractals indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Fractal Adaptive Moving Average indicator to collection  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                 const int ma_period=14,
                                 const int ma_shift=0,
                                 const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndFrAMA *ind_obj=new CIndFrAMA(symbol,timeframe,ma_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create FrAMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Gator indicator to the collection                        |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int jaw_period=13,
                                  const int jaw_shift=8,
                                  const int teeth_period=8,
                                  const int teeth_shift=5,
                                  const int lips_period=5,
                                  const int lips_shift=3,
                                  const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndGator *ind_obj=new CIndGator(symbol,timeframe,jaw_period,jaw_shift,teeth_period,teeth_shift,lips_period,lips_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Gator indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Ichimoku Kinko Hyo indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const int tenkan_sen=9,
                                    const int kijun_sen=26,
                                    const int senkou_span_b=52)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndIchimoku *ind_obj=new CIndIchimoku(symbol,timeframe,tenkan_sen,kijun_sen,senkou_span_b);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Ichimoku indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Market Facilitation Index indicator to the collection    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBWMFI *ind_obj=new CIndBWMFI(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create BW MFI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Momentum indicator to the collection                     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const int mom_period=14,
                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMomentum *ind_obj=new CIndMomentum(symbol,timeframe,mom_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Momentum indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Money Flow Index indicator to the collection             |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=14,
                               const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMFI *ind_obj=new CIndMFI(symbol,timeframe,ma_period,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create MFI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Moving Average indicator to the collection               |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=10,
                               const int ma_shift=0,
                               const ENUM_MA_METHOD ma_method=MODE_SMA,
                               const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMA *ind_obj=new CIndMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create MA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Moving Average of Oscillator indicator to the collection |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int fast_ema_period=12,
                                const int slow_ema_period=26,
                                const int signal_period=9,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndOsMA *ind_obj=new CIndOsMA(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create OsMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add to the collection the indicator                              |
//| Moving Averages Convergence/Divergence                           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int fast_ema_period=12,
                                const int slow_ema_period=26,
                                const int signal_period=9,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMACD *ind_obj=new CIndMACD(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create MACD indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the On Balance Volume indicator to the collection            |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndOBV *ind_obj=new CIndOBV(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create OBV indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+-------------------------------------------------------------------+
//| Add Parabolic Stop and Reverse system to the collection           |
//+-------------------------------------------------------------------+
int CMSTFIndicators::AddNewSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const double step=0.02,
                               const double maximum=0.2)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndSAR *ind_obj=new CIndSAR(symbol,timeframe,step,maximum);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create SAR indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Relative Strength Index indicator to the collection      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=14,
                               const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndRSI *ind_obj=new CIndRSI(symbol,timeframe,ma_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create RSI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Relative Vigor Index indicator to the collection         |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=10)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndRVI *ind_obj=new CIndRVI(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create RVI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Standard Deviation indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int ma_period=20,
                                  const int ma_shift=0,
                                  const ENUM_MA_METHOD ma_method=MODE_SMA,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndStdDev *ind_obj=new CIndStdDev(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create StdDev indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Stochastic Oscillator indicator to the collection        |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const int Kperiod=5,
                                      const int Dperiod=3,
                                      const int slowing=3,
                                      const ENUM_MA_METHOD ma_method=MODE_SMA,
                                      const ENUM_STO_PRICE price_field=STO_LOWHIGH)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndStoch *ind_obj=new CIndStoch(symbol,timeframe,Kperiod,Dperiod,slowing,ma_method,price_field);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Stochastic indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+-------------------------------------------------------------------+
//| Add Triple Exponential Moving Average indicator to the collection |
//+-------------------------------------------------------------------+
int CMSTFIndicators::AddNewTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ma_period=14,
                                const int ma_shift=0,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndTEMA *ind_obj=new CIndTEMA(symbol,timeframe,ma_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create TEMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
  
//+------------------------------------------------------------------+
//| Add to the collection the indicator                              |
//| Triple Exponential Moving Averages Oscillator                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ma_period=14,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndTriX *ind_obj=new CIndTriX(symbol,timeframe,ma_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create TriX indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add Larry Williams' Percent Range to the collection              |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndWPR *ind_obj=new CIndWPR(symbol,timeframe,calc_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create WPR indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add Variable Index Dynamic Average to the collection             |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewVIDyA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int cmo_period=9,
                                  const int ema_period=12,
                                  const int ma_shift=0,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndVIDyA *ind_obj=new CIndVIDyA(symbol,timeframe,cmo_period,ema_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create VIDyA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Volumes indicator to the collection                      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndVolumes *ind_obj=new CIndVolumes(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Volumes indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add a custom indicator to the collection                         |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,const string name,const uint buffers,const MqlParam &param[])
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,param);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }

All codes for the indicator collection class are ready. Each method is provided with comments describing its logic, so that they can be easily understood.


Testing

To test the classes of multi-indicators and their collection, let's create a simple indicator. The indicator will fetaure a selection from several moving averages. The purpose of these tests is to check the operation of classes with indicators that draw one line from one buffer in the main chart window. This purpose is best achieved using moving averages from the set of trend indicators in the client terminal. In the next articles, we will create templates for quickly creating and working with any indicators from the standard set provided in the terminal + working with custom indicators. Gradually, we will completely finalize the classes created today to work correctly with any type of indicators.

The test indicator will plot the lines of the created multi-indicators on the chart. The relevant data will be displayed on the dashboard. The creation of this dashboard was described in the first article of this series.

We will create two identical indicators inside one indicator. One of them will be calculated using the current chart data, and the second one will be calculated using the data of the symbol/period selected in the settings. This way, we will always see the moving average line on the current chart and the line of the same moving average, but created using data of a different chart period. It is possible to select a symbol from another chart, but then the lines will not match the price level.

Create a new indicator file named TestMSTFMovingAverages.mq5:


Select the OnTimer and OnChartEvent handlers:

Select two buffers to be plotted as lines in the main chart window:

You can use any buffer names, as they will be renamed in the code. Click "Finish" and get an indicator template:

//+------------------------------------------------------------------+
//|                                       TestMSTFMovingAverages.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
//--- plot MA1
#property indicator_label1  "MA1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot MA2
#property indicator_label2  "MA2"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- indicator buffers
double         MA1Buffer[];
double         MA2Buffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MA1Buffer,INDICATOR_DATA);
   SetIndexBuffer(1,MA2Buffer,INDICATOR_DATA);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   
//--- return value of prev_calculated for the next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   
  }

In the global area, add an enumeration to select the type of the moving average, connect the multi-symbol multi-period indicators class file and the dashboard class file, and declare input variables.
Rename the buffers
to provide a more meaningful representation and declare global indicator variables:

//+------------------------------------------------------------------+
//|                                       TestMSTFMovingAverages.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
//--- enums
enum ENUM_USED_MA
  {
   USED_MA_AMA    =  IND_AMA,    // Adaptive Moving Average
   USED_MA_DEMA   =  IND_DEMA,   // Double Exponential Moving Average
   USED_MA_FRAMA  =  IND_FRAMA,  // Fractal Adaptive Moving Average
   USED_MA_MA     =  IND_MA,     // Moving Average
   USED_MA_TEMA   =  IND_TEMA,   // Triple Exponential Moving Average
   USED_MA_VIDYA  =  IND_VIDYA,  // Variable Index Dynamic Average
  };
//--- plot MA1
#property indicator_label1  "MA1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- plot MA2
#property indicator_label2  "MA2"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//--- includes
#include <IndMSTF\IndMSTF.mqh>
#include <Dashboard\Dashboard.mqh>
//--- input parameters
input ENUM_USED_MA         InpIndicator   =  USED_MA_MA;       /* Used MA        */ // Type of moving average to use
input string               InpSymbol      =  NULL;             /* Symbol         */ // Moving average symbol
input ENUM_TIMEFRAMES      InpTimeframe   =  PERIOD_CURRENT;   /* Timeframe      */ // Moving average timeframe
input ENUM_APPLIED_PRICE   InpPrice       =  PRICE_CLOSE;      /* Applied Price  */ // Price used for calculations
input ENUM_MA_METHOD       InpMethod      =  MODE_SMA;         /* MA Method      */ // Moving Average calculation method
input int                  InpShift       =  0;                /* MA Shift       */ // Moving average shift
input bool                 InpAsSeries    =  true;             /* As Series flag */ // Timeseries flag of indicator buffer arrays

//--- indicator buffers
double         BufferMA1[];
double         BufferMA2[];
//--- global variables
int handle_ma1;
int handle_ma2;
CMSTFIndicators indicators;      // An instance of the indicator collection object
//--- variables for the panel
CDashboard *panel=NULL;          // Pointer to the panel object
int         mouse_bar_index;     // Index of the bar the data is taken from

Inside the OnInit() handler, enter the creation of a timer, assign plot buffers, create handles for the selected indicators and create a dashboard:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set a timer with an interval of 1 second
   EventSetTimer(1);
//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers 0 and 1, respectively
   SetIndexBuffer(0,BufferMA1,INDICATOR_DATA);
   SetIndexBuffer(1,BufferMA2,INDICATOR_DATA);
//--- sets indicator shift
   //PlotIndexSetInteger(0,PLOT_SHIFT,InpShift);   // analog in line 116
   //PlotIndexSetInteger(1,PLOT_SHIFT,InpShift);   // analog in line 117
//--- Set the timeseries flags for the indicator buffer arrays (for testing, to see that there is no difference)
   ArraySetAsSeries(BufferMA1,InpAsSeries);
   ArraySetAsSeries(BufferMA2,InpAsSeries);
   
//--- For different indicators, the dashboard width will be individual (due to the number of parameters in the description)
   int width=0;
//--- According on the indicator selected in the settings, create two indicators of the same type
//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   switch(InpIndicator)
     {
      case USED_MA_AMA     :
         handle_ma1=indicators.AddNewAMA(NULL,PERIOD_CURRENT,9,2,30,InpShift);
         handle_ma2=indicators.AddNewAMA(InpSymbol,InpTimeframe,9,2,30,InpShift);
         width=269;
        break;
      case USED_MA_DEMA    :
         handle_ma1=indicators.AddNewDEMA(NULL,PERIOD_CURRENT,14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewDEMA(InpSymbol,InpTimeframe,14,InpShift,InpPrice);
         width=255;
        break;
      case USED_MA_FRAMA   :
         handle_ma1=indicators.AddNewFrAMA(NULL,PERIOD_CURRENT,14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewFrAMA(InpSymbol,InpTimeframe,14,InpShift,InpPrice);
         width=259;
        break;
      case USED_MA_TEMA    :
         handle_ma1=indicators.AddNewTEMA(NULL,PERIOD_CURRENT,14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewTEMA(InpSymbol,InpTimeframe,14,InpShift,InpPrice);
         width=253;
        break;
      case USED_MA_VIDYA   :
         handle_ma1=indicators.AddNewVIDyA(NULL,PERIOD_CURRENT,9,12,InpShift,InpPrice);
         handle_ma2=indicators.AddNewVIDyA(InpSymbol,InpTimeframe,9,12,InpShift,InpPrice);
         width=267;
        break;
      default:
         handle_ma1=indicators.AddNewMA(NULL,PERIOD_CURRENT,10,InpShift,InpMethod,InpPrice);
         handle_ma2=indicators.AddNewMA(InpSymbol,InpTimeframe,10,InpShift,InpMethod,InpPrice);
         width=231;
        break;
     }
//--- If failed to create indicator handles, return initialization error
   if(handle_ma1==INVALID_HANDLE || handle_ma2==INVALID_HANDLE)
      return INIT_FAILED;
//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(0,handle_ma1,0);
   indicators.SetPlotLabelFromBuffer(1,handle_ma2,0);
//--- Set "empty" values for calculation part buffers of the created indicators
   indicators.SetBufferInitValue(handle_ma1,0,EMPTY_VALUE);
   indicators.SetBufferInitValue(handle_ma2,0,EMPTY_VALUE);
//--- Set shifts for indicator lines
   indicators.SetPlotShift(0,InpShift);
   indicators.SetPlotShift(1,InpShift);
      
//--- Dashboard
//--- Create the panel
   panel=new CDashboard(1,20,20,width,264);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Set font parameters
   panel.SetFontParams("Calibri",9);
//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));

//--- Create a table with ID 0 to display bar data in it
   panel.CreateNewTable(0);
//--- Draw a table with ID 0 on the dashboard background
   panel.DrawGrid(0,2,20,6,2,18,width/2-2);

//--- Create a table with ID 1 to display the data of indicator 1
   panel.CreateNewTable(1);
//--- Get the Y2 table coordinate with ID 0 and
//--- set the Y1 coordinate for the table with ID 1
   int y1=panel.TableY2(0)+22;
//--- Draw a table with ID 1 on the dashboard background
   panel.DrawGrid(1,2,y1,2,2,18,width/2-2);

//--- Create a table with ID 2 to display the data of indicator 2
   panel.CreateNewTable(2);
//--- Get the Y2 coordinate of the table with ID 1 and
//--- set the Y1 coordinate for the table with ID 2
   int y2=panel.TableY2(1)+3;
//--- Draw a table with ID 2 on the background of the dashboard
   panel.DrawGrid(2,2,y2,3,2,18,width/2-2);
   
//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=0;
//--- Display the data of the current bar on the dashboard
   DrawData(mouse_bar_index,TimeCurrent());

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

Add the OnDeinit() handler:

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the timer
   EventKillTimer();
//--- If the panel object exists, delete it
   if(panel!=NULL)
      delete panel;
//--- Delete all comments
   Comment("");
  }

In the OnCalculate() handler, call the calculation of all multi-symbol multi-period indicators. If calculation fails, exit the handler and return zero to recalculate the indicators on the next tick.
After successful calculation, the data in the array-buffers of indicator objects already exists and can be displayed in the dashboard. After displaying the data on the dashboard, output data from the calculated buffers to the indicator's plot buffers:

//+------------------------------------------------------------------+
//| 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[])
  {
//--- Number of bars for calculation
   int limit=rates_total-prev_calculated;
//--- If limit > 1, then this is the first calculation or change in the history
   if(limit>1)
     {
      //--- specify all the available history for calculation
      limit=rates_total-1;
      /*
      // If the indicator has any buffers that display other calculations (not multi-indicators),
      // initialize them here with the "empty" value set for these buffers
      */
     }
//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      return 0;

//--- Display the bar data under cursor (or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);

//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma1,0,limit,BufferMA1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma2,0,limit,BufferMA2))
      return 0;

//--- return value of prev_calculated for the next call
   return(rates_total);
  }

In the indicator timer, call the timer of the indicator collection object:

//+------------------------------------------------------------------+
//| Timer function                                                   | 
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Call the indicator collection timer
   indicators.OnTimer();
  }

In the OnChartEvent() handler, call the dashboard object event handler and process cursor movements to determine the bar where it is located:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Handling the panel
//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Declare the variables to record time and price coordinates in them
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }

//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }

Function that displays multi-indicator data on the dashboard:

//+------------------------------------------------------------------+
//| Display data from the specified timeseries index to dashboard    |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Declare the variables to receive data in them
   MqlRates rates[1];

//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Set font parameters for bar and indicator data headers
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,9);

//--- Display the data of the specified bar in table 0 on the panel
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Output the data of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_ma1), panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value1=indicators.GetData(handle_ma1,0,index);
   string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_ma1)) : " ");
   panel.DrawText(value_str1,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,110);
   
//--- Display a description of the indicator 1 line state
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma1,0,index);
   panel.DrawText(BufferLineStateDescription(state1),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,110);
   
//--- Output the data of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_ma2), panel.CellX(2,0,0)+2, panel.CellY(2,0,0)+2);
   double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,0,index);
   string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_ma2)) : " ");
   panel.DrawText(value_str2,panel.CellX(2,0,1)+2,panel.CellY(2,0,1)+2,clrNONE,110);
   
//--- Display a description of the indicator 2 line state
   panel.DrawText("Line state", panel.CellX(2,1,0)+2, panel.CellY(2,1,0)+2);
   ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma2,0,index);
   panel.DrawText(BufferLineStateDescription(state2),panel.CellX(2,1,1)+2,panel.CellY(2,1,1)+2,clrNONE,110);
   
//--- Display description of relationship between indicator 1 line relative to indicator 2 line
   double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,0,index+1);
   ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_ma1,0,index,value2,value21);
   string ma1=indicators.Name(handle_ma1);
   string ma2=indicators.Name(handle_ma2);
   string state_relative=
     (
      stateR==LINE_STATE_ABOVE      ? StringFormat("%s1 > %s2",ma1,ma2)   :
      stateR==LINE_STATE_BELOW      ? StringFormat("%s1 < %s2",ma1,ma2)           :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"   :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing"  :
      BufferLineStateDescription(stateR)
     );
   panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(2,2,0)+2, panel.CellY(2,2,0)+2);
   panel.DrawText(state_relative, panel.CellX(2,2,1)+2, panel.CellY(2,2,1)+2,clrNONE,110);
   
//--- Redraw the chart to immediately display all changes on the panel
   ChartRedraw(ChartID());
  }

As you can see, we can calculate multi-symbol, multi-period indicators by simply calling the Calculate() method of the indicator collection class. After successful calculation, all data is already available. This data can be accessed and processed from Expert Advisors. In indicators, after successful calculation, data can be displayed on a chart in the form of lines using the DataToBuffer() method of the indicator collection class. That's all you need to calculate and display multi-indicators on the chart.

After compiling the test indicator, we will launch it on a chart with a period of M1, in the settings we will select the current symbol and the calculation period of the indicator M5. In this case, two moving averages selected in the indicator settings will be created. One of them will be calculated using the current chart data, and the second one will be based on the data from a five-minute chart period. By switching the chart timeframe, you can see how two lines are drawn on M1: one will correspond to the moving average calculated on M1, and the second line will correspond to the moving average calculated on M5. If you switch the chart to M5, only one indicator will be created, since the second one will be identical to the first one, and it will not be created. If you switch the chart to M15, then one indicator will be calculated for M15, and the second for M5, and it will also be displayed on the chart.


As you can see, the declared functionality works and we can see indicators in the main chart window. Multiple indicators are created while using one buffer.

The files of all classes and the test indicator are available in the attachment.


Conclusion

Today we have created functionality for quickly creating multi-symbol multi-period indicators and receiving their data in indicators as well as plotting the calculated indicators in the main chart window. In the following articles we will create templates for creating other multi-symbol, multi-period standard indicators that plot their data in the main chart window, and not only with one buffer. As a result, we will get a convenient tool for quickly turning any indicators into multi-symbol, multi-period versions. The multi-indicator classes are likely to be further developed as we will create other standard and custom indicators.


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

Attached files |
Dashboard.mqh (217.85 KB)
IndMSTF.mqh (473.51 KB)
Last comments | Go to discussion (24)
Denis Kirichenko
Denis Kirichenko | 5 Nov 2023 at 09:59

Forum on trading, automated trading systems and testing trading strategies

Discussion of the article "Preparing multisymbol multi-period indicators"

Artyom Trishkin, 2023.11.01 04:46 AM

...After publication of the next article on this topic, I will check and test (if you do not test me before with changes in p.211).


To test something, you have to be in the paradigm of what's going on ))

I realised that it's easier for me to sketch my own version, as I'm not close to the current approach. In particular, it seems to me that the CIndMSTF class is some kind of super class. Then a bunch of indicator classes are created on its basis. Creepy - the IndMSTF.mqh file is 4 thousand lines of code )) I went the way of using an instance of CIndicators class as an indicator collection. It is very convenient. You don't need to invent a bicycle.

Then why should the CIndMSTF class store data on buffers (SBuffer m_buffers[])? We calculated them once in OnCalculate() and that's enough. I.e. we took it as a parameter by reference, calculated it and gave it back...

I'll write more later about what I disagree with once I finish my version....

Yes, I like that there is this mechanism:

...При работе с данными не текущего графика для исключения "освобождения" таймсерии, необходимо не реже. чем раз в две минуты обращаться к этой таймсерии. В этом случае будет происходить "удержание" таймсерии, что ускорит к ней обращение (не нужно будет каждый раз дожидаться синхронизации данных)...


Artem, one more thing. If articles are written as a manual, which there is a desire to study, then, imho, there are not enough schemes of relationships of those classes, which the developer creates....

Then, why put the code of all indicators in the article material? I mean this section - " Acomplete list of all inheritor classes of the base class of the multisymbol multiperiod indicator". Ilooked at how many lines of code there are .

Here is the beginning:

And here's the end:


Almost 2 thousand. Wow!

Artyom Trishkin
Artyom Trishkin | 5 Nov 2023 at 10:13
Denis Kirichenko #:

To test something, you have to be in the paradigm of what's going on ))

I realised that it's easier for me to sketch my own version, as I'm not close to the current approach. In particular, it seems to me that the CIndMSTF class is some kind of super class. Then a bunch of indicator classes are created on its basis. Creepy - the IndMSTF.mqh file is 4 thousand lines of code )) I went the way of using an instance of CIndicators class as an indicator collection. It is very convenient. You don't need to reinvent the wheel...

Then why should the CIndMSTF class store data on buffers (SBuffer m_buffers[])? We calculated them once in OnCalculate() and that's enough. I.e. we took it as a parameter by reference, calculated it and gave it to you...

I'll write more later about what I disagree with when I finish my version...

Yes, liked that there is this mechanism:

Because of the extensibility - so that once calculated data is in a separate array from the calculation part. CopyBuffer copies a given number of bars into the array and resizes the receiving array to fit the copied amount of data. I.e., if you need data all the time, you need to copy them from the buffer of the calculation part all the time.

Here we have made it so that there is a constantly filled array with all the data, and the data are obtained from it by index. Without copying. Copying is done once at startup, and then only adding data two bars at a time to the end of the list. I think the same logic is used in SB in the class you mentioned. I may forget and be wrong about it.

Denis Kirichenko
Denis Kirichenko | 6 Nov 2023 at 15:55
Artyom Trishkin #:

Because of the extensibility - to keep the calculated data in a separate array from the calculation part. CopyBuffer copies a specified number of bars into the array and changes the size of the receiving array to fit the copied amount of data. I.e., if you need data all the time, you should copy it from the calculation part buffer all the time.

Here we have made it so that there is a constantly filled array with all the data, and the data are obtained from it by index. Without copying. Copying is done once at startup, and then only adding data two bars at a time to the end of the list. I think the same logic is used in SB in the class you mentioned. I may forget and be wrong about it.

Yes, there is array filling. But there is also an attempt of direct access - the api-method CIndicator::GetData(), which calls the native function CopyBuffer() head-on.

StohanoV
StohanoV | 10 Nov 2023 at 10:48
100 errors, 6 warnings 100 7. Why 100 errors when compiling. What am I doing wrong?

Artyom Trishkin
Artyom Trishkin | 10 Nov 2023 at 10:54
StohanoV #:
100 errors, 6 warnings 100 7. Why 100 errors when compiling. What am I doing wrong?

The very first error in the editor's log is what?

Neural networks made easy (Part 61): Optimism issue in offline reinforcement learning Neural networks made easy (Part 61): Optimism issue in offline reinforcement learning
During the offline learning, we optimize the Agent's policy based on the training sample data. The resulting strategy gives the Agent confidence in its actions. However, such optimism is not always justified and can cause increased risks during the model operation. Today we will look at one of the methods to reduce these risks.
Quantization in machine learning (Part 1): Theory, sample code, analysis of implementation in CatBoost Quantization in machine learning (Part 1): Theory, sample code, analysis of implementation in CatBoost
The article considers the theoretical application of quantization in the construction of tree models and showcases the implemented quantization methods in CatBoost. No complex mathematical equations are used.
Integrating ML models with the Strategy Tester (Conclusion): Implementing a regression model for price prediction Integrating ML models with the Strategy Tester (Conclusion): Implementing a regression model for price prediction
This article describes the implementation of a regression model based on a decision tree. The model should predict prices of financial assets. We have already prepared the data, trained and evaluated the model, as well as adjusted and optimized it. However, it is important to note that this model is intended for study purposes only and should not be used in real trading.
Working with ONNX models in float16 and float8 formats Working with ONNX models in float16 and float8 formats
Data formats used to represent machine learning models play a crucial role in their effectiveness. In recent years, several new types of data have emerged, specifically designed for working with deep learning models. In this article, we will focus on two new data formats that have become widely adopted in modern models.