MQL's OOP notes: Arrayed indicator buffers based on operators overloading

16 September 2016, 21:07
Stanislav Korotky
6
1 051
From very first moment as I started learning MetaTrader many years ago I was wondering why there are no multidimentional buffers in indicators. Indeed, when you code an indicator with multiple buffers you need to declare them one by one:

double buffer1[];
double buffer2[];
double buffer3[];

Why not to write simply:

double buffers[3][];

???

This would not only spare a couple of lines, but allow streamline further calculations tremendously, especially if you write a multicurrency indicator where all buffers are calculated in a similar way. Then one could write something like that:

for(int i = 0; i < Bars; ++i)
{
  for(int j = 0; j < ArrayRange(buffers, 0); ++j)
  {
    buffers[j][i] = iClose(Symbols[j], 0 , i) * getIndex(j);
  }
}

All these years and up to the current moment, you need to replace the inner loop with N assignments for different buffers.

But now, thanks to OOP (which was initially missing in MQL4), we can devise a workaround. Let us develop an indicator to demonstrate this approach. The full code is attached below, and only most important parts are explained in details in the text. The indicator is for MetaTrader 4 but can be easily ported to MetaTrader 5.

The indicator will have 4 buffers, 3 of which are simple moving averages with given periods and the 4-th is a weighted sum of them.

Let's start from a class of indicator buffer.

class Indicator
{
  private:
    double buffer[];
    int cursor;
    
  public:
    Indicator(int i)
    {
      SetIndexBuffer(i, buffer);
    }

It holds an array of doubles for later binding as indicator buffer in the class contructor. The cursor is an integer pointer for this buffer. We want the class to behave exactly as an array, this is why we need to overload operator[].

    Indicator *operator[](int b)
    {
      cursor = (int)b;
      return GetPointer(this);
    }
That's the place where most of the magic happens. The code stores index of bar passed as the paramater and returns the object itself. To actually store a value into the buffer we need to overload assignment operator.

    double operator=(double x)
    {
      buffer[cursor] = x;
      return x;
    }

This is where the bar index (cursor) saved in the previous method is used to update an element in the buffer. Among assigment operator there mus be overloaded many other arithmetic operators in order to mimic ordinary array. 

    double operator+(double x) const
    {
      return buffer[cursor] + x;
    }
    
    double operator-(double x) const
    {
      return buffer[cursor] - x;
    }
    
    double operator*(double x) const
    {
      return buffer[cursor] * x;
    }
    
    double operator/(double x) const
    {
      return buffer[cursor] / x;
    }

    double operator+=(double x)
    {
      buffer[cursor] += x;
      return buffer[cursor];
    }
    
    double operator-=(double x)
    {
      buffer[cursor] -= x;
      return buffer[cursor];
    }
    
    double operator*=(double x)
    {
      buffer[cursor] *= x;
      return buffer[cursor];
    }
    
    double operator/=(double x)
    {
      buffer[cursor] /= x;
      return buffer[cursor];
    }
};

Now let us define another class which manages an array of the objects of the class Indicator. Remember? - We're after an array of buffers.

class IndicatorArray
{
  private:
    Indicator *array[];
    
  public:
    IndicatorArray(int n)
    {
      ArrayResize(array, n);
      for(int i = 0; i < n; ++i)
      {
        array[i] = new Indicator(i);
      }
    }
    
    virtual ~IndicatorArray()
    {
      int n = ArraySize(array);
      for(int i = 0; i < n; ++i)
      {
        if(CheckPointer(array[i]) == POINTER_DYNAMIC)
        {
          delete array[i];
        }
      }
      ArrayResize(array, 0);
    }
    
    Indicator *operator[](int n) const
    {
      return array[n];
    }
    
    int size() const
    {
      return ArraySize(array);
    }
};
The constructor accepts a number of buffers to allocate. Operator[] is also overloaded here because we need ordinary array look and feel.

Having these 2 classes we can start coding the indicator itself (I use old-fashined event handlers for simplicity).

#property indicator_chart_window
#property indicator_buffers 4

#property indicator_color1 Red
#property indicator_color2 Green
#property indicator_color3 Blue
#property indicator_color4 Yellow
#property indicator_width4 3

#property strict

extern int Period1 = 5;
extern int Period2 = 11;
extern int Period3 = 21;

IndicatorArray buffers(4); // 1 single line magic!

int periods[3];
double periodsSum;

int init()
{
  periods[0] = Period1;
  periods[1] = Period2;
  periods[2] = Period3;
  
  periodsSum = 1.0/Period1 + 1.0/Period2 + 1.0/Period3;
  
  SetIndexLabel(0, "MA " + IntegerToString(Period1));
  SetIndexLabel(1, "MA " + IntegerToString(Period2));
  SetIndexLabel(2, "MA " + IntegerToString(Period3));
  SetIndexLabel(3, "MA SUM");
  
  return 0;
}

int start()
{
  int limit = MathMax(Bars - IndicatorCounted(), 1);
  
  for(int i = limit - 1; i >= 0; --i)
  {
    buffers[3][i] = 0;
    for(int j = 0; j < 3; ++j)
    {
      buffers[j][i] = iMA(_Symbol, _Period, periods[j], 0, MODE_EMA, PRICE_TYPICAL, i);
      buffers[3][i] += buffers[j][i] / periods[j];
    }
    buffers[3][i] /= periodsSum;
  }

  return 0;
}
This does even work. But there is a flaw in the classes we've just implemented. If you try to print a value from one of the buffers, you'll get strange results. For example,

Comment(buffers[3][0]);

will output strange integer number, which is obviously not a value of the 0-th element. If you try

Comment(DoubleToString(buffers[3][0], _Digits));

the things get even worse - you'll get a code generation error (older versions of compiler generate bad ex4, while newer can't compile the code). 

This happens because our overloaded operator[] returns a pointer to the object, which can not be casted to double. The simplest way to overcome the obstacle is to perform a fake arithmetic operation, such as adding 0:

Comment(DoubleToString(buffers[3][0] + 0, _Digits));

But this seems not so elegant. The natural way of solving this would be defning another "getter" operator[]:

    double operator[](int b)
    {
      return buffer[b];
    }

but we can't do that, because such method is already defined with different return type (remember Indicator *operator[](int b)?). We can use the following trick here:

    double operator[](uint b)
    {
      return buffer[b];
    }
The type of the parameter is changed from int to uint, so there is no conflict between two operators anymore. But now we need to cast the index to uint.
Comment(DoubleToString(buffers[3][(unit)0], _Digits));
The other possible way is to implement a special helper class for the class Indicator, which would provide the required "getter" operator - double operator[](int b) - on its behalf. Then the helper object must be somehow generated for Indicator upon request and all places in the code where a value of a buffer is needed "as is", the helper object must be used. The attached example demonstrates this as well. It introduces the class:

class IndicatorGetter
{
  private:
    Indicator *owner;
    int cursor;
    
  public:
    IndicatorGetter(Indicator &o)
    {
      owner = GetPointer(o);
    }
    
    double operator[](int b)
    {
      return owner[(uint)b];
    }
};

And the class Indicator creates an instance of IndicatorGetter internally in its contructor and makes it available outside via special method edit - here is the updated Indicator definition:

class Indicator
{
  private:
    double buffer[];
    int cursor;
    IndicatorGetter *instance;
    
  public:
    Indicator(int i)
    {
      SetIndexBuffer(i, buffer);
      instance = new IndicatorGetter(this);
    }

    IndicatorGetter *edit() const
    {
      return instance;
    }
    
    virtual ~Indicator()
    {
      delete instance;
    }
    ...
Do not forget to delete the helper instance in destructor.

Finally, for convenience the helper class is accompanied by the class IndicatorArrayGetter supporting an array metaphor for the helper buffers, in the same way as IndicatorArray does for Indicator objects. Please, consult with the source code for further details. Also note that not all operators are overloaded in the example: if you use other ones, you need to extend the classes.



Files:
threema.mq4  6 kb
Share it with friends: