
Creating multi-symbol, multi-period indicators
Contents
- Introduction
- Basic principles
- Base class of the MSTF indicator
- Indicator classes by type
- Indicator collection class
- Testing
- Conclusion
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:
- The base class of a multi-symbol, multi-period indicator containing the main functionality common to all indicators;
- Classes derived from the base object, which describe each indicator by its type;
- 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.
//--- 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.
- 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. - 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).
- 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 ¶m[] // 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 ¶m[]);// 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 ¶m[]) { //--- 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





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
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.
The very first error in the editor's log is what?
A message from the developers on the forum.
I remember something like that about 4, but I must have missed it about 5....
Strange that such things are not configurable in the terminal. And are not described in the documentation.
Please tell me, does your approach allow to get data from the zero buffer of the upper TF on the first tick?
I remember something like that about the four, but about the five, apparently I missed it....
It's strange that such things are not configured in the terminal. And are not described in the documentation.
Please tell me, does your approach allow to get data from the zero buffer of the upper TF on the first tick?