Creating an Indicator with Multiple Indicator Buffers for Newbies

Nikolay Kositsin | 8 December, 2010


Introduction

In my previous articles "Custom Indicators in MQL5 for Newbies" and "Practical Implementation of Digital Filers in MQL5 for Beginners" I focused in details on structure of the indicator with one indicator buffer.

Obviously, such a method can be widely applied for writing custom indicators, but the real life can hardly be limited to their use, and thus it is time to approach more complex methods of building the indicator code. Fortunately, the capabilities of MQL5 are truly inexhaustible and are limited only by the RAM of our PCs.


The Aroon Indicator as example of code doubling

The formula of this indicator contains two components: the bullish and the bearish indicators, which are plotted in a separate chart window:

BULLS =  (1 - (bar - SHIFT(MAX(HIGH(), AroonPeriod)))/AroonPeriod) * 100
BEARS = (1 - (bar - SHIFT(MIN (LOW (), AroonPeriod)))/AroonPeriod) * 100

where:

Right from the formulas of the indicator, we can conclude that to construct an indicator, we must have only two indicator buffers, the indicator structure will differ very little from the structure of the SMA_1.mq5, considered in the previous article.

Practically, it is simply the same duplicated code, with different numbers of indicator buffers. So let's open the code of this indicator in the MetaEditor, and save as Aroon.mq5. Now, in the first 11 lines of the code, which relates to the copyright and its version number, we will replace only the name of the indicator:

//+------------------------------------------------------------------+
//|                                                        Aroon.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//---- copyright
#property copyright "2010, MetaQuotes Software Corp."
//---- link to the author's site
#property link      "http://www.mql5.com"
//---- version number
#property version   "1.00"

Next, in the 12th line of code, we need to change the plotting of the indicator from the basic chart window to the separate window:

//---- plot indicator in the separate window
#property indicator_separate_window

Since this indicator has a completely different range of values, its plotting will be made in a separate window.

After this, in the following 4 lines of the code (the general indicator properties), we change the number of the used indicator buffers to two:

//---- two buffers are used
#property indicator_buffers 2
//---- two plots are used 
#property indicator_plots   2

The next 10 lines of code are related to the plotting of the indicator from a specific indicator buffer, it's label must be duplicated, after which, we must replace all the indexes from 1 to 2. We must also change all of the labels of the indicator buffers:

//+----------------------------------------------+
//| bullish strength indicator parameters        |
//+----------------------------------------------+
//---- drawing style = line
#property indicator_type1   DRAW_LINE
//---- drawing color = Lime
#property indicator_color1  Lime
//---- line style = solid line
#property indicator_style1  STYLE_SOLID
//---- line width = 1
#property indicator_width1  1
//---- label of the BullsAroon indicator
#property indicator_label1  "BullsAroon"
//+----------------------------------------------+
//|  bearish strength indicator parameters       |
//+----------------------------------------------+
//---- drawing style = line
#property indicator_type2   DRAW_LINE
//---- drawing color = Red
#property indicator_color2  Red
//---- line style = solid line
#property indicator_style2  STYLE_SOLID
//---- line width = 1
#property indicator_width2  1
//---- label of the BearsAroon indicator
#property indicator_label2  "BearsAroon"

This indicator uses three horizontal levels with the values of 30, 50 and 70.

In order to plot these levels, we need to add five more code lines to the code of the indicator.

//+----------------------------------------------+
//| Horizontal levels                            |
//+----------------------------------------------+
#property indicator_level1 70.0
#property indicator_level2 50.0
#property indicator_level3 30.0
#property indicator_levelcolor Gray
#property indicator_levelstyle STYLE_DASHDOTDOT

For the indicator input parameters, in comparison to the previous indicator, everything remains the same, aside for small changes in the titles:

//+----------------------------------------------+
//| Indicator input parameters                   |
//+----------------------------------------------+
input int AroonPeriod = 9; // Period 
input int AroonShift = 0// Horizontal shift of the indicator in bars 

 Now, however, there will be two arrays, which will be used as indicator buffers, and they will have appropriate names:

//--- declare the dynamic arrays used further as indicator buffers
double BullsAroonBuffer[];
double BearsAroonBuffer[]; 

We proceed in a completely same matter with the code of the OnInit() function.

First, we modify the lines of code of the zeroth buffer:

//--- set BullsAroonBuffer dynamic array as indicator buffer 
SetIndexBuffer(0, BullsAroonBuffer, INDICATOR_DATA);
//--- horizontal shift (AroonShift) of the indicator 1
PlotIndexSetInteger(0, PLOT_SHIFT, AroonShift);
//--- plot draw begin (AroonPeriod) of the indicator 1
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, AroonPeriod);
//--- label shown in DataWindow
PlotIndexSetString(0, PLOT_LABEL, "BearsAroon"); 

After this, copy the entire code to the Windows clipboard, and paste it right after the same code.

Then, in the pasted code, we change the number of the indicator buffer from 0 to 1, change the name of the indicator array, and label of the indicator:

//--- set BearsAroonBuffer dynamic array as indicator buffer 
SetIndexBuffer(1, BearsAroonBuffer, INDICATOR_DATA); 
//--- horizontal shift (AroonShift) of the indicator 2 
PlotIndexSetInteger(1, PLOT_SHIFT, AroonShift); 
//--- plot draw begin (AroonPeriod) of the indicator 2 
PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, AroonPeriod); 
//--- label shown in DataWindow 
PlotIndexSetString(1, PLOT_LABEL, "BullsAroon");  

The indicator short name has also undergone minor changes:

//--- initialization of the variable for a short indicator name
string shortname;
StringConcatenate(shortname, "Aroon(", AroonPeriod, ", ", AroonShift, ")"); 

Now, let's consider the accuracy of plotting the indicator. The actual range of the indicator is from 0 to 100, and this range is shown all the time.

In this situation, it is quite possible to use only the integer values of the indicator, plotted on the chart. For this reason, we use 0 for the numbers after the decimal point, for the indicator plotting:

//--- set accuracy of drawing of indicator values
IndicatorSetInteger(INDICATOR_DIGITS, 0);

In the SMA_1.mq5 indicator, we used the first form of the OnCalculate() function call.

It's is not suitable for the Aroon indicator, because of its lack of  the high[] and low[] price arrays. These arrays are available in the second calling form of this function. And, therefore, it is necessary to change the function's header:

int OnCalculate( 
                const int rates_total,    // total bars on the current tick
                const int prev_calculated,// total bars on the previous tick
                const datetime& time[],
                const double& open[],    
                const double& high[],     // price array of the maximum prices for the indicator calculations
                const double& low[],      // price array of the minimum prices for the indicator calculations
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[]
              )

After this change, the use of the begin parameter, has lost all meaning, so it needs to be removed from the code!

The code for calculating the limits of variable changes of the operation cycle, the data verification for calculation sufficiency, has practically remained unchanged.

//--- checking the number of bars
if (rates_total < AroonPeriod - 1) return(0);
   
//--- declare the local variables 
int first, bar;
double BULLS, BEARS; 

//--- calculation of the first (staring index) for the main loop
if (prev_calculated == 0)          // checking for the first call of the OnCalculate function
    first = AroonPeriod - 1;       // starting index for calculating all of the bars 
else first = prev_calculated - 1// starting index for calculating new bars

However, the certain problems appear with the algorithms for calculating the indicator values. The problem is that MQL5 does not have the built-in functions to determine the indexes of the maximum and the minimum, for the period from the current bar, in the direction of the decreasing index.

One way out of this situation is to write these functions ourselves. Fortunately, such functions already exists in the ZigZag.mq5 indicator in the of custom indicators, located in the "MetaTrader5\MQL5\Indicators\Examples" folder.

The easiest way out - is to select the code of these functions in the ZigZag.mq5 indicator, copy them to the Windows clipboard, and paste them into our code, for example, right after the describing the OnInit() function, at the global level:

//+------------------------------------------------------------------+
//|  searching index of the highest bar                              |
//+------------------------------------------------------------------+
int iHighest(const double &array[], // array for searching for the index of the maximum element
             int count,            // number of the elements in the array (in the decreasing order), 
             int startPos          // starting index
             )                     
  {
//---+
   int index = startPos;
   
   //---- checking the starting index
   if (startPos < 0)
     {
      Print("Incorrect value in the function iHighest, startPos = ", startPos);
      return (0);
     } 
   //---- checking the startPos values
   if (startPos - count < 0) count = startPos;
    
   double max = array[startPos];
   
   //---- index search
   for(int i = startPos; i > startPos - count; i--)
     {
      if(array[i] > max)
        {
         index = i;
         max = array[i];
        }
     }
//---+ return of the index of the largest bar
   return(index);
  }
//+------------------------------------------------------------------+
//|  searching index of the lowest bar                               |
//+------------------------------------------------------------------+
int iLowest(
            const double &array[], // array for searching for the index of the maximum element
            int count,            // number of the elements in the array (in the decreasing order),
            int startPos          // starting index
            ) 
{
//---+
   int index = startPos;
   
   //--- checking the stating index
   if (startPos < 0)
     {
      Print("Incorrect value in the iLowest function, startPos = ",startPos);
      return(0);
     }
     
   //--- checking the startPos value
   if (startPos - count < 0) count = startPos;
    
   double min = array[startPos];
   
   //--- index search
   for(int i = startPos; i > startPos - count; i--)
     {
      if (array[i] < min)
        {
         index = i;
         min = array[i];
        }
     }
//---+ return of the index of the smallest bar
   return(index);
  }

After this, the code of the OnCalculate() function will look the following way:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate( const int rates_total,    // total number of bars on the current tick
               const int prev_calculated,// number of calculated bars on the previous tick
               const datetime& time[],
               const double& open[],    
               const double& high[],     // price array for the maximum price for the indicator calculation
               const double& low[],      // price array for the minimum price for the indicator calculation
               const double& close[],
               const long& tick_volume[],
               const long& volume[],
               const int& spread[]
             )
  {
//---+   
   //--- checking the number of bars
   if (rates_total < AroonPeriod - 1)
    return(0);
   
   //--- declare the local variables 
   int first, bar;
   double BULLS, BEARS;
   
   //--- calculation of the starting bar number
   if (prev_calculated == 0// checking for the first start of the indicator calculation
     first = AroonPeriod - 1; // starting number for the calculation of all of the bars

   else first = prev_calculated - 1; // starting number for the calculation of new bars

   //--- main loop
   for(bar = first; bar < rates_total; bar++)
    {
     //--- calculation of values
     BULLS = 100 - (bar - iHighest(high, AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;
     BEARS = 100 - (bar - iLowest (low,  AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;

     //--- filling the indicator buffers with the calculated values 
     BullsAroonBuffer[bar] = BULLS;
     BearsAroonBuffer[bar] = BEARS;
    }
//---+     
   return(rates_total);
  }
//+------------------------------------------------------------------+

For the symmetry of the axes, I slightly corrected in the code, by adding the vertical shift of the indicator, as compared to the original one, using the value of 0.5.

Here are the results of the work of this indicator on the chart:

                                                                              

To find the position of the element with the maximum or minimum values on a distance no further than AroonPeriod from the current bar we can use built-in ArrayMaximum() and ArrayMinimum() functions of MQL5, which also search for the extremums, but these functions perform the search using the increasing order.

However, the search should be done in decreasing order of indexes. For this case the simplest solution is to change the direction of indexing in the indicator and price buffers, using the ArraySetAsSeries() function.

But we also need to change the direction of the bar ordering in the calculation loop and change the algorithm of the first variable calculation.

In this case the resulting OnCalculate() function will look like this:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(
                const int rates_total,    // total number of bars on the current tick
                const int prev_calculated,// number of calculated bars on the previous tick
                const datetime& time[],
                const double& open[],    
                const double& high[],     // price array for the maximum price for the indicator calculation
                const double& low[],      // price array for the minimum price for the indicator calculation
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[]
              )
  {
//---+   
   //--- checking the number of bars
   if (rates_total < AroonPeriod - 1)
    return(0);
    
   //--- set indexation as timeseries
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low,  true);
   ArraySetAsSeries(BullsAroonBuffer, true);
   ArraySetAsSeries(BearsAroonBuffer, true);
   
   //--- declare the local variables 
   int limit, bar;
   double BULLS, BEARS;
   
   //--- calculation of the starting bar index
   if (prev_calculated == 0)                      // check for the first call of OnCalculate function
       limit = rates_total - AroonPeriod - 1// starting index for the calculation of all of the bars
   else limit = rates_total - prev_calculated; // starting index for the calculation of new bars
   
   //--- main loop
   for(bar = limit; bar >= 0; bar--)
    {
     //--- calculation of the indicator values
     BULLS = 100 + (bar - ArrayMaximum(high, bar, AroonPeriod) - 0.5) * 100 / AroonPeriod;
     BEARS = 100 + (bar - ArrayMinimum(low,  bar, AroonPeriod) - 0.5) * 100 / AroonPeriod;

     //--- filling the indicator buffers with the calculated values 
     BullsAroonBuffer[bar] = BULLS;
     BearsAroonBuffer[bar] = BEARS;
    }
//----+     
   return(rates_total);
  }
//+------------------------------------------------------------------+

I changed the name of the variable "first" to "limit", it's more appropriate in this case.

In this case the code of the main loop is similar to way as it was done in MQL4. So, this style of writing the OnCalculate() function can be used for the conversion of indicators from MQL4 to MQL5 with minimal changes of the code.


Conclusion

So, it's done! The indicator is written, and even in two versions.

For a case of the right, conservative and smart way of solving such problems the solutions turn out slightly more complicated than construction of a some toy using the children's Lego constructor.