MQL's OOP Booklet

Stanislav Korotky

Stanislav Korotky

Table of contents

Introduction

Print data using overloads and templates

Managing resources by object wrappers

Arrayed indicator buffers based on operators overloading

Multiple timers with publisher/subscriber design pattern and abstract class 

Converting MetaTrader 4 indicators to MetaTrader 5 

HashMap supports old-fashioned indicators in MetaTrader 5

Singleton, Command queue, and Template method patterns in simple order manager

Object hierarchies and serialization 

Self-made profiler on static & automatic objects 

Rubber arrays, safe numbers, exception handling and other tricks: part 1

Rubber arrays, safe numbers, exception handling and other tricks: part 2

On The Fly Self-Optimization of Expert Advisers: part 1

On The Fly Self-Optimization of Expert Advisers: part 2

Particle Swarm Speeds Up Expert Adviser Optimization On The Fly

Optimystic library for On The Fly Optimization of EA meets fast particle swarm algorithm 

Online Analytical Processing of trading hypercubes: part 1

Online Analytical Processing of trading hypercubes: part 2

 



All articles are not interconnected (unless it's explicitly stated in titles), so you can freely jump to any one of them if it concerns a topic you are interested in.

All these materials and other stuff can be found in the blog


Introduction

16 September 2016, 21:06
Both MetaTrader 4 and MetaTrader 5 provide powerfull tools for trading automation, which can be extended even more - to almost any feasible idea - thanks to the built-in programming language - MQL.

MQL5 supports object oriented paradigm of programming from very beginning, and MQL4 has been improved to meet MQL5, so basically there is no difference between the languages (only core APIs of MetaTrader 4 and MetaTrader 5 are different).

OOP is a great thing, yet many MetaTrader users are not programmers, and they often ask questions about OOP in MQL: why, when, and how should they use it.

This series of articles try to answer some of these questions, explain applicational value of OOP, and provide examples of using it in MetaTrader.

 

Definition

OOP is intended to make complex code simplier.

This works in many aspects, such as:

  1. brevity - lower number of code lines needed to implement a required product via reuse of existing classes (without any changes in the classes);
  2. reliability - error minimization as a consequence of decomposed code with managable access rules for different contexts (classes, variables);
  3. readability - short and clear presentation of algorithm in a notation, which is close to natural language and hierarchy of real-life entities;
  4. maintainability - when it becomes necessary, one need to change as little code lines as possible;
  5. standartization - best practices of solving common programming tasks are collected and available as compendiums of design patterns;

Main question here is - how can one know whether a specific code is complex enough to make it a good candidate for simplification by using OOP? Or maybe it will only become more complex?

Well, indeed, OOP can seem an overkill for a single buffer indicator, but if you have a bunch of similar indicators it is already an occasion for extracting common structures and behavioral patterns into classes. Anytime you find yourself making "copy&paste" of code you're probably missing the point to get the benefits of OOP world. You may say you could use plain old functions and includes for eliminating "copy&paste", and this works in many cases, but then it usually gets more and more agglomerated over time. And this is when OOP comes in handy. Just a simple example: when you include a library of functions, you have only 2 options - either use it "as is" (if it meets your needs entirely), or customize for specific purpose (with these specific changes affecting every other code relying on the library, which can be error prone). With OOP, you can include a class with base functionality, derive new descendant class from it, and customize it specifically for your code.

Of course, back-compatibility and intergation with diversity of non-OOP legacy code is supported, so OOP transformation does not have to be instant and ubiquitous. You can move step by step.

Just as a short reminder, let us formulate main principles of OOP. This is the last time we speak about theory, and will from now on concentrate on practice.


Main Principles 

Data abstraction and encapsulation

In OOP you define a class with an interface, beyond which all implementation details are hidden from the rest of code. This makes it easy to improve or completely change implementation when necessary without affecting other parts of code.

A class and its objects can store internal state. This provides a unified context for class methods (functions) and does not mess up global context.


Decomposition and inheritance

All tasks to be performed by a code are distributed among logically decoupled code parts - classes with clear and unique responsibility (purpose) and minimal dependencies.

Derived classes can inherit internal state and functions from base classes, extend them.


Polymorphism

An object pointer or reference can hold instance of a derived class. This allows you to call methods declared in the base class and actually invoke an overridden implemenation, thus customize base behavior partially and utilize all the other useful features of the parent.


Print data using overloads and templates

16 September 2016, 21:06
One of the most frequently used MQL functions is Print. It's simple and indispensable. Unfortunately it usually requires a lot of routine typing which makes code bulky. For example, if you need to print several values, you need to explicitly add some separators. To output datetime properly you should call TimeToString with specific formatting. And printing a number with floating point implies usage of DoubleToString where you specify number of digits after the point.

If you try to use a natural way of printing:

Print(M_PI, "test", boolean, day, dt, clr); 
you'll get something like this:

3.141592653589793testtrue12016.09.14 11:48:24clrRed

where M_PI constant is printed with default excessive precision, dt varibale, which is actualy a datetime, shows probably unnecessary seconds, and all values are stuck together. In order to get this right, one should add a lot of auxiliary stuff:

Print(DoubleToString(M_PI, 5), " test ", boolean, " ", day, " ", TimeToString(dt, TIME_DATE | TIME_MINUTES), " ", clr);

This line is more than 2 times longer than the previous one.  

Of course one can invent some shorthands, such as a popular DS(number, digits) macro for DoubleToString, but they do not actually solve the problem, but make it less obtrusive.  

Fortunately, OOP can simplify the matter. The idea of the solution comes from C++ output stream and its overloaded operator <<. Let us define a stream-like class which will hold all settings, such as formatting, separators, etc and then apply them automatically to all data passed into the output stream. The complete code is attached, here we'll consider most important parts.

First start the class and its private member variables.

class OutputStream
{
  private:
    int digits;      // default number of digits for floating point numbers
    char delimiter;  // default separator between values
    int timemode;    // default format for datetime
    string format;   // format string for numbers with floating point
    string line;     // internal buffer, where all printed data is accumulated
The string format should be initialized according to the given number of digits.

    void createFormat()
    {
      format = "%." + (string)digits + "f";
    }
We'll call this method from constructors shown below.

  public:
    OutputStream(): digits(_Digits), timemode(TIME_DATE|TIME_MINUTES)
    {
      createFormat();
    }
    OutputStream(int p): digits(p), timemode(TIME_DATE|TIME_MINUTES)
    {
      createFormat();
    }
    OutputStream(int p, char d): digits(p), delimiter(d), timemode(TIME_DATE|TIME_MINUTES)
    {
      createFormat();
    }
    OutputStream(int p, char d, int t): digits(p), delimiter(d), timemode(t)
    {
      createFormat();
    }

Default value for digits (if it's not specified) is the _Digits of current _Symbol. Default datetime format is without seconds. Default separator is empty.

Now most important part - the overloaded insertion operator <<.

    template<typename T>
    OutputStream *operator<<(const T v)
    {
      if(typename(v) == "int" || typename(v) == "uint"
      || typename(v) == "long" || typename(v) == "ulong"
      || typename(v) == "short" || typename(v) == "ushort")
      {
        if(typename(v) == "ushort" && v == '\n')
        {
          Print(trimmedLine());
          line = NULL;
          return GetPointer(this);
        }
        else
        {
          line += IntegerToString((int)v);
        }
      }
      else
      if(typename(v) == "double" || typename(v) == "float")
      {
        if(v == EMPTY_VALUE) line += "<EMPTY_VALUE>";
        else line += StringFormat(format, v);
      }
      else
      if(typename(v) == "datetime")
      {
        line += TimeToString((datetime)v, timemode);
      }
      else
      if(typename(v) == "color")
      {
        line += (string)v;
      }
      else
      if(typename(v) == "char" || typename(v) == "uchar")
      {
        if(v == '\n')
        {
          Print(trimmedLine());
          line = NULL;
          return GetPointer(this);
        }
        else
        {
          line += CharToString((char)v);
        }
      }
      else
      {
        line += (string)v;
      }
      if(delimiter != 0)
      {
        line += CharToString(delimiter);
      }
      return GetPointer(this);
    }

In this case it's implemented as a single templatized method which can accept value of any built-in type. Instead of this you could define multiple methods with different parameter types - one per every built-in type, but I decided the single universal one. In this method we process passed variable according to its type determined by typename function. For every type, specific API function - such as DoubleToString, TimeToString, CharToString - is used to get its string representation, which is added to the line member variable. The process uses predefined settings specified only once in constructor. When the input contains single newline character '\n', all the contents of the line varibale is flushed by Print call and the line is emptied. Printing uses a private method trimmedLine, which cuts latest separator. Separators are added automatically after every streamed value, because we assume a next value to come, but the latest separator makes no sense. Thus this method is required. Alternatively one could insert separator before every new value except very first one, but then you should check if the line is empty before every concatenation.

The method returns a pointer to the object itself. This allows for chaining its calls in the same line (as shown below).

Now we can create an object of this class.

OutputStream out(5, ',');

And then use it in the following way

out << M_PI << "test" << boolean << EN(day) << dt << clr << '\n';

which outputs

3.14159,test,true,two,2016.09.14 11:42,FF0000

The EN helper is a shorthand for enumeration printing.

template<typename T> string EN(T enum_value) { return(EnumToString(enum_value)); }

One important thing to note is that operator << defined above can process only built-in types. For objects we should add another templates.

    template<typename T>
    OutputStream *operator<<(const T &v)
    {
      line += typename(v) + StringFormat("%X", GetPointer(v));
      if(delimiter != 0)
      {
        line += CharToString(delimiter);
      }
      return GetPointer(this);
    }

    template<typename T>
    OutputStream *operator<<(const T *v)
    {
      line += typename(v) + StringFormat("%X", v);
      if(delimiter != 0)
      {
        line += CharToString(delimiter);
      }
      return GetPointer(this);
    }

The type T here is resolved to any object - they are passed by reference or as pointers. This implementation prints out not so much information about the objects. If you want objects to provide additional information, you can define a class Printable like that

class Printable
{
  public:
    virtual string toString() const
    {
      return "<?>";
    };
};
and then inherit all other classes from it, overriding the method with a code returning a meaningful result. Then the method can be used as appropriate inside the stream class, but details go outside the scope of this article for the sake of simplicity.

Please, use recent versions of MQL compiler to eliminate possible errors which may arise on older versions. MQL is constantly extending its support of more and more OOP features. 



Table of contents



Files:
fmtprntl.mqh 4 kb

Managing resources by object wrappers

16 September 2016, 21:06
MQL programmers use files here and there. Standard API provides a set of file functions which employ old plain procedural approach. But sometimes it's better to upgrade this style up to OOP.

One simple use case where OOP can be helpful is related to resource control. A file is a resource of a special kind. It should be allocated (created), used, and finally released (closed).

Usual practice is to call: FileOpen, then some of FileRead/FileWrite functions, and finally FileClose. All looks fine as long as algorithm stays straightforward. Once it gets branches and multiple return operators, the task of releasing resources becomes a bit complicated. You should remeber about all exit points and copy FileClose to many places in the code.

This problem can be easily solved by OOP. Let us define a class which wraps file handle and takes care about (potentially, all) manipulations with it. To open a file one will create an automatic object of this class, for example inside a function. And when the flow control will leave the context (the function in this case, or other block of code where the file variable is declared), the object destructor will be called automatically and close file handle. It's important to note that the variable should be declared as automatic, not via a dynamically allocated pointer using operator new (because such a stuff must be deleted explicitly by programmer).  

Here is the simple self-explanatory class.

class File
{
  private:
    int file; // file handle
  
  public:
    File(const string name, const int flags, const short delimiter)
    {
      file = FileOpen(name, flags, delimiter);
    }

    File(const string name, const int flags)
    {
      file = FileOpen(name, flags);
    }
    
    bool isOpened() const
    {
      return (file != INVALID_HANDLE);
    }
    
    bool operator!() const
    {
      return !isOpened();
    }
    
    int handle() const
    {
      return file;
    }
    
    virtual ~File()
    {
      if(file != INVALID_HANDLE)
      {
        Print("File is closed automatically"); // this is just a debug log
        FileClose(file);
      }
    }
};
Having this class one can use it as follows.

int processFile(const string inputFileName = "abc.csv")
{
  // declare automatic variable of class File
  File f(inputFileName, FILE_READ | FILE_TXT | FILE_SHARE_READ, ',');
  if(!f)
  {
    Alert("Can't read file " + inputFileName);
    return 1;
  }
  
  int handle = f.handle();

  // these are just placeholders for some actual conditions  
  bool requiredCondition1, requiredCondition2;
  
  Print("Processing started...");
  
  if(!requiredCondition1)
  {
    // FileClose(handle) call is not required
    return 2;
  }
  
  string line = FileReadString(handle);

  Print("More in-depth processing...");

  if(!requiredCondition2)
  {
    // FileClose(handle) call is not required
    return 3;
  }
  
  // ...
  
  Print("Processing done");
  
  // FileClose(handle) call is not required

  return 0;
}
Closing the file is important because otherwise it can be left locked and inaccessible for other parts of algorithm or other programs. This class makes the job for you automatically.


Table of contents

Files:
file.mqh 2 kb
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.



Table of contents


Files:
threema.mq4 6 kb
Every expert and indicator in MetaTrader can have a timer. From time to time a novice MQL developer asks a question: what should I do if I need many timers? The answer is simple: use a single timer with lesser period to manage and dispatch all other timer events with larger periods. Thanks to OOP this can be done easily using a well-known design pattern "publisher/subscriber".

The example will demonstrate the whole idea based on EventSetTimer function which supports intervals starting from 1 second. You can modify it for more fine-grained timers using EventSetMillisecondTimer

Let's start from a class which defines a ground-based astraction of a timer. Usually it is called interface and comprises only pure virtual methods, that is virtual methods without implementation (see below). But in this case we will use an abstract class with a bit of fundamental timer-related data and logic. Nevertheless it's still an abstract class, that is a class which can not be instantiated (only objects of derived classes with concrete implementations of pure virtual methods can be created).

Surely the class should have a method to signal the timer event and a member variable storing a time of previous notification.

class TimerNotification
{
  private:
    datetime lastNotification;
    
  public:
    TimerNotification(): lastNotification(0)
    {
    }
  
    virtual void notify()
    {
      lastNotification = TimeLocal();
    }
In the notify method we save the time when it's called last time. Next method should return a number of seconds in timer period.
    
    virtual int getSeconds() const = 0;
This is a pure virtual method. It requires an implementation in a descendant class in order to create an object.

Timer period is the main and the only property of a timer, but we do not define a variable for it in the abstract class - insteed we allow derived classes to provide this information via the virtual method, and manage the period on their own, including probably a floating period with changes in time.

Finally we need a method to detect when the timer event should fire. Here is where we use the method getSeconds returning data from descendants.  
    
    bool isTimeCome()
    {
      return lastNotification + getSeconds() < TimeLocal();
    }
};
All looks fine except for the nuance that if the method notify will not be called in descendants, the variable lastNotification will remain 0 and the checkup in isTimeCome will work incorrectly. We need somehow check if descendants call the base implementation of notify from their overriden versions. For that purpose we add a new variable and more code. Here is the complete code.

class TimerNotification
{
  private:
    datetime lastNotification;
    bool checkedNotifyCall;
    
  public:
    TimerNotification(): lastNotification(0), checkedNotifyCall(false)
    {
    }
  
    virtual void notify()
    {
      lastNotification = TimeLocal();
    }
    
    virtual int getSeconds() const = 0;
    
    bool isTimeCome()
    {
      if(lastNotification == 0)
      {
        if(!checkedNotifyCall)
        {
          checkedNotifyCall = true;
          return true;
        }
        else
        {
          Print("ERROR: call to TimerNotification::notify() is missing in concrete timer class");
        }
      }
        
      return lastNotification + getSeconds() < TimeLocal();
    }
};

If descendant "forgets" to invoke the base notify from its own notify, the error will be shown on logs.

Now, having this abstract class we can implement a worker class of a timer derived from it.

class Timer: public TimerNotification
{
  private:
    int seconds;
    
  public:
    Timer(const int s)
    {
      seconds = s;
    }
    
    virtual int getSeconds() const
    {
      return seconds;
    }
    
    virtual void notify()
    {
      Print(__FUNCSIG__, seconds);
      TimerNotification::notify();
    }
};

The constructor accepts a number of seconds as the timer period and then returns this number from getSeconds. Do not forget to call notify method of the base class.

For this timer to work we need a kind of a timer manager, which would call every second isTimeCome method and if it returns true, then fire the timer via notify. Actually it should do so for all existing timers. Here is where the "publisher/subscriber" design pattern comes into action.

class MainTimer
{
  private:
    TimerNotification *subscribers[];

This array will store pointers to all timers which registered themselves in the manager. 

  public:
    MainTimer()
    {
      EventSetTimer(1);

We use 1 second period here as the least possible period for timers with second granularity. If you plan to work with milliseconds consider the value carefully, because too small value may increase CPU overheads.  

    }
    
    ~MainTimer()
    {
      EventKillTimer();
    }

Finally - the method to add a timer object in the internal array. This is a subscription.

    void bind(TimerNotification &tn)
    {
      int i, n = ArraySize(subscribers);
      for(i = 0; i < n; ++i)
      {
        if(subscribers[i] == &tn) return; // already added
        if(subscribers[i] == NULL) break; // empty slot
      }
      
      if(i == n) // no empty slots, extend the array
      {
        ArrayResize(subscribers, n + 1);
      }
      else // use empty slot (if any)
      {
        n = i;
      }
      subscribers[n] = &tn;
    }

The next method does the contrary - unsubscribes (disables) a timer.

    void unbind(TimerNotification &tn)
    {
      int n = ArraySize(subscribers);
      for(int i = 0; i < n; ++i)
      {
        if(subscribers[i] == &tn)
        {
          subscribers[i] = NULL; // mark the slot as empty
          return;
        }
      }
    }

And this is the method which should be called from OnTimer event handler in global context. It publishes information for existing subscribers. The timer manager class is the publisher.

    void onTimer()
    {
      int n = ArraySize(subscribers);
      for(int i = 0; i < n; ++i)
      {
        if(CheckPointer(subscribers[i]) != POINTER_INVALID)
        {
          if(subscribers[i].isTimeCome())
          {
            subscribers[i].notify();
          }
        }
      }
    }
};

Now we need to go back to our Timer class and insert some lines in order to make subscription/unsubscription. The final variant of code is the following:

class Timer: public TimerNotification
{
  private:
    int seconds;
    MainTimer *owner;
    
  public:
    Timer(MainTimer &main, const int s)
    {
      seconds = s;
      owner = &main;
      owner.bind(this); // subscribe itself
    }
    
    ~Timer()
    {
      owner.unbind(this); // unsubscribe itself
    }
    
    virtual int getSeconds() const
    {
      return seconds;
    }
    
    virtual void notify()
    {
      Print(__FUNCSIG__, seconds);
      TimerNotification::notify();
    }
};

To test the classes write a simple example with 2 timers.

const int seconds = 2;

MainTimer mt();
Timer t1(mt, seconds);
Timer t2(mt, seconds * 2);

void OnTimer()
{
  mt.onTimer();
}

The output looks like this:

0 01:40:56.272 : void Timer::notify()2
0 01:40:58.301 : void Timer::notify()4
0 01:40:59.315 : void Timer::notify()2
0 01:41:02.357 : void Timer::notify()2
0 01:41:03.414 : void Timer::notify()4
0 01:41:05.399 : void Timer::notify()2
0 01:41:08.441 : void Timer::notify()2
0 01:41:08.441 : void Timer::notify()4

The timer with 2 seconds period is fired 2 times faster than the timer with 4 seconds period. Notifications do not come exactly with 1 second accuracy, but this is normal for standard EventSetTimer. You can make sure that every next notification comes not ealier than the specified number of seconds for every timer.

Bingo.

 

Table of contents

 

Files:
timer.mq4 4 kb
It has been a long time since MetaTrader 5 was released, but MQL products for MetaTrader 4 do still prevail on mql5.com site (both in the codebase, and in the market), and in the Internet in general. This is why it's often a problem that an interesting indicator or expert adviser exists for MetaTrader 4 but is missing for MetaTrader 5. Today we'll consider how OOP can help in converting MetaTrader 4's indicators to MetaTrader 5's in a snap.

The process implies that there should be a header file (.mqh) with all required stuff, which could be sufficient to include into a mq4-file, and after changing its extension to .mq5 compilation should produce a ready-made indicator for MetaTrader 5.

Of course, the mqh-file should contain some non-OOP defines and functions. For example, here is a part of them:

#define StrToDouble StringToDouble
#define DoubleToStr DoubleToString
#define TimeToStr TimeToString
#define Ask SymbolInfoDouble(_Symbol, SYMBOL_ASK)
#define Bid SymbolInfoDouble(_Symbol, SYMBOL_BID)

#define Digits _Digits
#define Point _Point

#define extern input

...

int ObjectsTotal()
{
  return ObjectsTotal(0);
}

bool ObjectCreate(const string name, const ENUM_OBJECT type, const int subwindow, const datetime time1, const double price1)
{
  return ObjectCreate(0, name, type, subwindow, time1, price1);
};

...

bool ObjectSetText(const string name, const string text, const int fontsize = 0)
{
  bool b = ObjectSetString(0, name, OBJPROP_TEXT, text);
  if(fontsize != 0) ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontsize);
  return b;
}

...

int iBarShift(string symbol, ENUM_TIMEFRAMES timeframe, datetime time, bool Exact = true)
{
  datetime lastBar;
  SeriesInfoInteger(symbol, timeframe, SERIES_LASTBAR_DATE, lastBar);
  return(Bars(symbol, timeframe, time, lastBar) - 1);
}

...

long iVolume(string symbol, ENUM_TIMEFRAMES tf, int b)
{
  long result[1];
  return CopyTickVolume(symbol, tf, b, 1, result) > 0 ? result[0] : 0;
}

...

bool _SetIndexBuffer(const int index, double &buffer[], const ENUM_INDEXBUFFER_TYPE type = INDICATOR_DATA)
{
  bool b = ::SetIndexBuffer(index, buffer, type);
  ArraySetAsSeries(buffer, true);
  ArrayInitialize(buffer, EMPTY_VALUE); // default filling
  return b;
}

#define SetIndexBuffer _SetIndexBuffer

void SetIndexStyle(const int index, const int type, const int style = EMPTY, const int width = EMPTY, const color clr = clrNONE)
{
  PlotIndexSetInteger(index, PLOT_DRAW_TYPE, type);
  if(style != EMPTY) PlotIndexSetInteger(index, PLOT_LINE_STYLE, style);
  if(width != EMPTY) PlotIndexSetInteger(index, PLOT_LINE_WIDTH, width);
  if(clr != clrNONE) PlotIndexSetInteger(index, PLOT_LINE_COLOR, clr);
}

All of these is obvious. You'll find complete code in the attachment.

Most interesting parts deal with some features which require OOP for indicator transformation.

First, remember MT4 accessors for timeseries - Open[], High[], Low[], Close[], etc. We can mimic this by objects with overloaded operator[]. For example, for volume data we can use the class:

class VolumeBroker
{
  public:
    long operator[](int b)
    {
      return iVolume(_Symbol, _Period, b);
    }
};

VolumeBroker Volume;

Actually, as all these classes are very similar to each other, one can generate them by preprocessor using one define:

#define DefineBroker(NAME,TYPE) \
class NAME##Broker \
{ \
  public: \
    TYPE operator[](int b) \
    { \
      return i##NAME(_Symbol, _Period, b); \
    } \
}; \
NAME##Broker NAME;

DefineBroker(Time, datetime);
DefineBroker(Open, double);
DefineBroker(High, double);
DefineBroker(Low, double);
DefineBroker(Close, double);
DefineBroker(Volume, long);

Second, remember that object property setter function ObjectSet has changed in MT5 to a set of functions ObjectSetInteger, ObjectSetDouble, etc, accepting different number of parameters. For example, mql4-code:

ObjectSet(name, OBJPROP_TIME1, Time[0]);
ObjectSet(name, OBJPROP_PRICE1, price);

should be somehow translated into mql5:

ObjectSetInteger(0, name, OBJPROP_TIME, 0, Time[0]);
ObjectSetDouble(0, name, OBJPROP_PRICE, 0, price);

It's important that there are no constants such as OBJPROP_TIME1 and OBJPROP_PRICE1 in mql5, and instead of OBJPROP_TIME1, OBJPROP_TIME2, OBJPROP_TIME3 one need to use the single constant OBJPROP_TIME and additional index. Price-related constants changed in the same way. How to emulate this? Well, by objects. Let's define 2 classes for properties of integer and double types (other types you can add yourself).

class OBJPROP_INTEGER_BROKER
{
  public:
    ENUM_OBJECT_PROPERTY_INTEGER p;
    int i;
    
    OBJPROP_INTEGER_BROKER(const ENUM_OBJECT_PROPERTY_INTEGER property, const int modifier)
    {
      p = property;
      i = modifier;
    }
};

class OBJPROP_DOUBLE_BROKER
{
  public:
    ENUM_OBJECT_PROPERTY_DOUBLE p;
    int i;
    
    OBJPROP_DOUBLE_BROKER(const ENUM_OBJECT_PROPERTY_DOUBLE property, const int modifier)
    {
      p = property;
      i = modifier;
    }
};

OBJPROP_INTEGER_BROKER OBJPROP_TIME1(OBJPROP_TIME, 0);
OBJPROP_DOUBLE_BROKER OBJPROP_PRICE1(OBJPROP_PRICE, 0);
OBJPROP_INTEGER_BROKER OBJPROP_TIME2(OBJPROP_TIME, 1);
OBJPROP_DOUBLE_BROKER OBJPROP_PRICE2(OBJPROP_PRICE, 1);
OBJPROP_INTEGER_BROKER OBJPROP_TIME3(OBJPROP_TIME, 2);
OBJPROP_DOUBLE_BROKER OBJPROP_PRICE3(OBJPROP_PRICE, 2);

Look how constants OBJPROP_TIMEn/OBJPROP_PRICEn "became" the objects storing corresponding index inside. Now we can implement new ObjectSet functions, and pass broker objects as parameters:

bool ObjectSet(const string name, const OBJPROP_INTEGER_BROKER &property, const long value)
{
  return ObjectSetInteger(0, name, property.p, property.i, value);
}

bool ObjectSet(const string name, const OBJPROP_DOUBLE_BROKER &property, const double value)
{
  return ObjectSetDouble(0, name, property.p, property.i, value);
}

Voila!

ObjectSet(name, OBJPROP_TIME1, Time[0]);
ObjectSet(name, OBJPROP_PRICE1, price);

works again in MetaTrader 5. 

Resulting mqh-file is attached below. To convert an indicator, just include the file into mq4-file and rename it to .mq5.

Please note that a couple of lines of code should be added manually anyway, because some completely new language structures have been introduced in MetaTrader 5, so there is no chance to generate them based on mql4 source code.

Let us consider a process of convertion of several indicators.

Let's take standard ZigZag.mq4 from MetaQuotes (specifically, dated 2014). You should add indicator_plots directive and change indicator_buffers number:

#property indicator_plots 1   // added
#property indicator_buffers 3 // was 1

because this is how MetaTrader 5 adds auxiliary buffers, and IndicatorBuffers is not supported anymore (it should be deleted in the source code). Also lines:

SetIndexBuffer(1, ExtHighBuffer);
SetIndexBuffer(2, ExtLowBuffer);

should be edited as follows:

SetIndexBuffer(1, ExtHighBuffer, INDICATOR_CALCULATIONS);
SetIndexBuffer(2, ExtLowBuffer, INDICATOR_CALCULATIONS);

As the indicator uses new style of event handlers (OnInit, OnCalculate) add MT4_NEW_EVENT_HANDLERS define before include

#define MT4_NEW_EVENT_HANDLERS
#include <ind4to5.mqh>

That's it - the indicator is ready to compile (3 lines added, 1 removed, 2 changed).

MT4_NEW_EVENT_HANDLERS define does basically invoke ArraySetAsSeries(,true) for all input arrays of OnCalculate. Not all indicators require this, so this define is optional (use as appropriate).

For example standard Parabolic.mq4 from MetaQuotes (dated 2014 as well) uses ArraySetAsSeries explicitly (on its own), so its conversion is even simplier: just add 2 lines:

#property indicator_plots 1

and

#include <ind4to5.mqh> 

Probably not all portable features specific for MetaTrader 4 are covered in the attached mqh-file and not all possible replacements are provided for MetaTrader 5. Feel free to extend and alter the file as you need.

Please note that MetaTrader 5 is more strict and sensitive for errors in mql code. If an indicator contains a bug, it can still run without a visible issue on MetaTrader 4, but will fail on MetaTrader 5 after convertion due to the bug. Always use #property strict in your codes and fix bugs.

Finally let's take a custom indicator from codebase. It happened to be MaBased-ZigZag.mq4 (as one of most popular). Here we need to change a bit more - partly because it contains bugs obscured by forgiving MetaTrader 4 (and they should be fixed), and partly because... because it invokes other indicator, specifically iMA. Indicators are created and used in MetaTrader 5 in a new way, which we have not yet addressed in this blog post. But there is already a lot of gimmicks for one time, so let us take a break and leave this question for the next publication.

Table of contents


Files:
ind4to5.mqh 12 kb

MetaTrader 5 is not back compatible with MetaTrader 4 in many aspects. This is bad, but we can do nothing with this. Yet we can do something to simplify translation of MetaTrader 4 products into MetaTrader 5.

One of the problems that arises when we move from MetaTrader 4 to MetaTrader 5 is the way how latter uses different method of indicator invocation. In MetaTrader 4, a call to an indicator returns a value right away, whereas in MetaTrader 5 the similar call returns an indicator handle, which should be passed later to CopyBuffer function in order to get the value. We faced this problem in the previous blog post about converting indicators. Actually, the problem is general for any MQL program using indicators, including expert advisers. So if we solve it, we'll kill 2 birds in one shot.

We have chosen MaBased-ZigZag.mq4 (from the codebase) for example translation, and this indicator uses iMA internally (2 different instances, in fact). Let's think of a way how to emulate the old-fashined iMA behaviour in MetaTrader 5.

Because the workaround should work with all indicators we start a new class as a container and place related stuff inside.

class Indicators
{
  public:
    static double jMA(const string symbol, const ENUM_TIMEFRAMES period, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int bar)
    {
      // ???
      return EMPTY_VALUE;
    }

Here we define a static function jMA which replicates iMA prototype from MetaTrader 4. When this function is called first time, we need to create an indicator and save the handle. When this function is called second and all successive times, we use the handle to call CopyBuffer and return values (if they are ready). A draft of the code could look like this.

  private:
    static int handle = -1; // this is not a MQL ;-)

    static double jMA(const string symbol, const ENUM_TIMEFRAMES period, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int bar)
    {
      if(handle == -1)
      {
        handle = iMA(symbol, period, ma_period, ma_shift, ma_method, applied_price);
      }
      else
      {
        double result[1];
        if(CopyBuffer(handle, 0, bar, 1, result) == 1)
        {
          return result[0];
        }
      }
      return EMPTY_VALUE;
    }

But wait - there may exist many different instances of the indicator, every one with its own set of parameters. So instead of the single handle variable we need a kind of array which saves not only the handles but the sets of corresponding parameters. In other words, we need an associative array with elements which can be addressed by a composite index containing all parameters. For that purpose a hashmap can be used. The name comes from the fact that indices are usually calculated as a hash of the data, but this is not a requirement.

Lets put the indicator aside for a while and concentrate on the hashmap. It should be a general purpose tool, so let use template with types placeholders.

template<typename K, typename V>
class HashMapSimple
{
  private:
    K keys[];
    V values[];
    int count;
    K emptyKey;
    V emptyValue;

  public:
    HashMapSimple(): count(0), emptyKey(NULL), emptyValue((V)EMPTY_VALUE){};
    HashMapSimple(const K nokey, const V empty): count(0), emptyKey(nokey), emptyValue(empty){};

Keys and values can be of a different type, this is why there are 2 arrays for underlying data (just to clarify - there is a single type for keys, and single type for values, but these 2 types can differ). For both arrays we need to provide constants denoting empty values.

Further implementation is straightforward. Here are methods for adding and retrieving data.

    void add(const K key, const V value)
    {
      ArrayResize(keys, count + 1);
      ArrayResize(values, count + 1);
      keys[count] = key;
      values[count] = value;
      count++;
    }

Get index by key:

    int getIndex(K key) const
    {
      for(int i = 0; i < count; i++)
      {
        if(keys[i] == key) return(i);
      }
      return -1;
    }

Get key by index:

    K getKey(int index) const
    {
      if(index < 0 || index >= count)
      {
        Print(__FUNCSIG__, ": index out of bounds = ", index);
        return NULL;
      }
      return(keys[index]);
    }

Get value by index (as in conventional arrays):

    V operator[](int index) const
    {
      if(index < 0 || index >= count)
      {
        Print(__FUNCSIG__, ": index out of bounds = ", index);
        return(emptyValue);
      }
      return(values[index]);
    }

Get value by key (associative array feature):

    V operator[](K key) const
    {
      for(int i = 0; i < count; i++)
      {
        if(keys[i] == key) return(values[i]);
      }
      Print(__FUNCSIG__, ": no key=", key);
      return(emptyValue);
    }

Also we provide methods to update and remove (key;value) pairs.

    void set(const K key, const V value)
    {
      int index = getIndex(key);
      if(index != -1)
      {
        values[index] = value;
      }
      else
      {
        add(key, value);
      }
    }

Removal does actually mark an element as removed and does not resize the arrays. This allows you to remove many elements in a row, before the arrays will be shrinked by purge method shown below.

    void remove(const K key)
    {
      int index = getIndex(key);
      if(index != -1)
      {
        keys[index] = emptyKey;
        values[index] = emptyValue;
      }
    }

Purge wipes out elements marked as removed and squeezes the arrays:

    void purge()
    {
      int write = 0;
      int i = 0;
      while(i < count)
      {
        while(keys[i] == emptyKey)
        {
          i++;
        }
        
        while(keys[i] != emptyKey)
        {
          if(write < i)
          {
            values[write] = values[i];
            keys[write] = keys[i];
          }
          i++;
          write++;
        }
      }
      count = write;
      ArrayResize(keys, count);
      ArrayResize(values, count);
    }

All this is not optimized in any way but sufficient for our purpose. Types should be standard MQL types, not classes. Also type of keys can not be int because int is already used for accessing elements by indices. If necessary though, one may use long or uint for keys as a replacement.

Having this class, we can declare the following hashmap, for example:

HashMapSimple<datetime,double> map;

The resulting header file HashMapSimple.mqh is attached below.

Now let's return back to the indicator class and add a map into it.

#include <HashMapSimple.mqh>

class Indicators
{
  private:
    static HashMapSimple<string,int> mapOfInstances;

In our simple implementation we'll use string keys. As the map is static it's used for all instances of MA indicators and even can be used for different types of indicators (such as WPR, ATR, etc). For that purpose we should include indicator name into the key along with parameters. The values are int handles.

We need to initialize the map object after the class definition:

HashMapSimple<string,int> Indicators::mapOfInstances("", NO_VALUE);

The NO_VALUE is defined as -1:

#define NO_VALUE -1

This is invalid handle.

Here is how MA can be coded now:

    static double jMA(const string symbol, const ENUM_TIMEFRAMES period, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int bar)
    {
      string key = StringFormat("iMA-%s-%d-%d-%d-%d-%d", symbol, period, ma_period, ma_shift, ma_method, applied_price);
      int handle = mapOfInstances[key];
      if(handle == NO_VALUE)
      {
        handle = iMA(symbol, period, ma_period, ma_shift, ma_method, applied_price);
        if(handle != INVALID_HANDLE)
        {
          mapOfInstances.add(key, handle);
        }
      }
      else
      {
        double result[1];
        if(CopyBuffer(handle, 0, bar, 1, result) == 1)
        {
          return result[0];
        }
      }
      return EMPTY_VALUE;
    }

Please notice how the key is constructed from "iMA" name, symbol, period and other input parameters. Other indicators can be implemented in the similar way, but as an additional demonstration of the idea let us support iCustom function.

iCustom is problematic because it contains an arbitrary list of parameters - their types and number can change. The only solution is to provide several variants of iCustom - one for every specific number of parameters (up to some reasonably large number) and utilize templates for type changes. For example, iCustom for 2 parameters is as follows:

    template<typename T1, typename T2>
    static double jCustom(const string symbol, const ENUM_TIMEFRAMES period, const string name, T1 t1, T2 t2, const int buffer, const int bar)
    {
      string key = StringFormat("iCustom-%s-%d-%s-%s-%s", symbol, period, name, (string)t1, (string)t2);
      int index = mapOfInstances[key];
      if(index == NO_VALUE)
      {
        int handle = iCustom(symbol, period, name, t1, t2);
        if(handle != INVALID_HANDLE)
        {
          mapOfInstances.add(key, handle);
        }
      }
      else
      {
        double result[1];
        if(CopyBuffer(index, buffer, bar, 1, result) == 1)
        {
          return result[0];
        }
      }
      return EMPTY_VALUE;
    }

The key comprises "iCustom", symbol, period, custom indicator name, and 2 templatized parameters. For, say, 5 parameters we need similar but more lengthier implemenation:

    template<typename T1, typename T2, typename T3, typename T4, typename T5>
    static double jCustom(const string symbol, const ENUM_TIMEFRAMES period, const string name, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, const int buffer, const int bar)
    {
      string key = StringFormat("iCustom-%s-%d-%s-%s-%s-%s-%s-%s", symbol, period, name, (string)t1, (string)t2, (string)t3, (string)t4, (string)t5);
      int index = mapOfInstances[key];
      if(index == NO_VALUE)
      {
        int handle = iCustom(symbol, period, name, t1, t2, t3, t4, t5);
        if(handle != INVALID_HANDLE)
        {
          mapOfInstances.add(key, handle);
        }
      }
      else
      {
        double result[1];
        if(CopyBuffer(index, buffer, bar, 1, result) == 1)
        {
          return result[0];
        }
      }
      return EMPTY_VALUE;
    }

To tie up this methods with calls of standard iMA or iCustom use simple defines:

#define iMA Indicators::jMA
#define iCustom Indicators::jCustom

Finally, we can return to the custom indicator MaBased-ZigZag.mq4 and finish its adaptation to MetaTrader 5. Actually, it's sufficient to include a header file with new classes discussed above. Here is the side-by-side comparison of 2 files.

Indicator convertion from MT4 to MT5


Indicator convertion from MT4 to MT5

As you may see some changes are related to the transformation, while the others not.

For the convertion we added

#property indicator_plots 5

and then 

#define MT4_OLD_EVENT_HANDLERS
#include <ind4to5.mqh>
#include <MT4Indicators.mqh>

MT4Indicators.mqh is attached. ind4to5.mqh was published in the previous post.

The define MT4_OLD_EVENT_HANDLERS is used because the indicator uses old event handlers (init, start). We need to emulate IndicatorCounted in this case, and this is done by the macro, which acquires correct number from special OnCalculate placeholder (you may consult with ind4to5.mqh source code).

One important thing to note is that our new iMA implementation returns EMPTY_VALUE until the plot is calculated. This is a side effect of the fact that MetaTrader 5 builds indicators asynchronously. As a result, we need to skip all ticks while our iMA value is unavailable and return -1 from start (in old event handlers 0 means success, and other values denote errors). Consequently the event wrapper OnCalculate in ind4to5.mqh will return 0, and recalculation will continue until iMA is ready. To achieve this behaviour, we added the line

if(ma[i] == EMPTY_VALUE || ma_fast[i] == EMPTY_VALUE) return -1;

in the main loop of the indicator.

Other changes are just fixes for problems in the original indicator:

  • Colors are changed for better visibility (for example, black line is invisible on black background used for charts by default).
  • Empty values of the buffers should be left EMPTY_VALUE because otherwise (when set to 0) the picture is screwed up.
  • Input parameters with MA types and prices should use special enumerations ENUM_MA_METHOD and ENUM_APPLIED_PRICE.
  • Limit of bar to process was incorrect.
  • Buffers must be filled with empty values explicitly in MetaTrader 5.

 P.S. There is still one more bug in the source code of the original indicator, but it's not marked in the comparison screenshots. You may try to figure it out. This should be easy because MetaTrader 5 will show the error dynamically, if it'll occur. I hope you'll be able to fix it yourself - homework, so to speak ;-).

After all the changes and successfull compilation we can at last see how the indicator works in MetaTrader 5.

Converted MT4 indicator in MT5

 


Table of contents


As the title says this time we'll try to implement a simple object-oriented framework for trading. It's surely simple as a framework, but is probably most complex thing among all that we've discussed so far in the MQL's OOP notes. So stock up a cup of coffee with cakes, fasten seat belt, and prepare yourself to delve into code.


Introduction

MetaTrader 4 provides a bunch of trading functions. Unfortunately most of them seems a bit low level to me. In particular, default error handling is poor, if not to say missing - at least, MQL programmer must handle errors on its own every time he or she deals with the trading functions. As a result, everyone invents his own error handling methods, which are actually unified for all common errors. This is the first task we'll accomplish using OOP - unified error handling.

The errors can be divided into 2 distinct categories: permanent (critical) and temporary (manageable). Example of the 1-st kind could be ERR_NOT_ENOUGH_MONEY and of the 2-nd - ERR_OFF_QUOTES. The difference between them is that temporary errors can vanish themselves after one or more retries and refreshes of quotes, while critical errors can be solved only by deliberate change in trading function parameters or trading environment (for example, trader could deposit more money on his account or change its leverage).  

Most of MQL programmers try to handle temporary errors by running a loop with a trading function call and refreshing rates until it succeeds or a predefined number of iterations achieved. This appoach works but has many drawbacks. If you retry the call too often, this may overload you local trade context (which is only one for the terminal) or your server (which can give you another error - too many requests). And if you call Sleep function in between, your expert will miss any other events (such as chart events) or receive them with a lag.

The better way of handling errors would be an approach when you save incomplete requests for trading operations somewhere and retry them on next ticks. This can be easily done with well-known design pattern "command", and a queue of commands.

The logic of using commands is asynchronous. We'll create a command, pass it to a manager (which we'll discuss below) for execution, and then allow our code to do some other tasks (if any) or keep idling until the command will be finally completed or failed, and then the manager will notify our code about result. 


The command

To start with it we need to define a command class, which will hold all information about a trading request (MT4 order). This class will be a base class for many derived classes for specific trading commands.

class OrderCommand
{
  protected:
    int cmdID;
    datetime created;

There should be a way to identify every command, this is why we introduce cmdID and created fields.

The ID can be assigned by incrementing a global (hence static) counter in the command construtor (see below).

    static int cmdIncrementedID;

The variable cmdIncrementedID must be initialized - here we start ID numbering from 1.

int OrderCommand::cmdIncrementedID = 1;

Every command can have custom deviation and magic number, though we'll provide another common place for these settings later.

Now add fields for order properties:

    string symbol;
    int cmd;
    double volume;
    double price;
    double stoploss;
    double takeprofit;
    string comment;
    datetime expiration;
    color arrow_color;

As far as the command is asynchronous let's provide a field for resulting value and public method to access it:

    int getResult() const
    {
      return result;
    }

The command should handle errors, specifically "decide" if an error can be fixed by retry or not. For that purpose we add an array with temporary error codes.

    static int HandledErrorCodes[];

and the function for checking this array:

    int handledError(string opName, int errCode, bool &priceRequote)
    {
      int handled = -1;
      priceRequote = false;
      for(int i = 0; i < ArraySize(HandledErrorCodes); i++)
      {
        if(HandledErrorCodes[i] == errCode)
        {
          handled = i;
          priceRequote = (i >= 7);
          break;
        }
      }
      Print(opName + " error: ", errCode, " handled:", (handled != -1));
      return handled;
    }

The array should be filled after the class definition:

int OrderCommand::HandledErrorCodes[] = {0, ERR_SERVER_BUSY, ERR_NO_CONNECTION, ERR_TRADE_TIMEOUT, ERR_OFF_QUOTES, ERR_BROKER_BUSY, ERR_TRADE_CONTEXT_BUSY,
                           ERR_INVALID_PRICE, ERR_PRICE_CHANGED, ERR_REQUOTE};

The errors starting from 7-th position (2-nd line) implies that price refreshing may help.

Now we can code the constructor:

  public:
    OrderCommand(string symbol = "", int cmd = -1, double volume = 0, double price = 0, int deviation = 0, double stoploss = 0, double takeprofit = 0,
             string comment = "", int magicId = 0, datetime expiration = 0, color arrow_color = CLR_NONE)
    {
      this.symbol = symbol == "" || symbol == NULL ? _Symbol : symbol;
      int d = (int)MarketInfo(this.symbol, MODE_DIGITS);
      
      this.cmd = cmd;
      this.volume = volume;
      this.price = NormalizeDouble(price, d);
      this.stoploss = NormalizeDouble(stoploss, d);
      this.takeprofit = NormalizeDouble(takeprofit, d);
      this.expiration = expiration;
      this.comment = comment;
      
      this.deviation = deviation;
      this.magicId = magicId;
      
      cmdID = cmdIncrementedID++;
      created = TimeCurrent();
    }

All fields are assigned and ID is incremented for every new object. The fields are similar to properties of MT4 orders, so please consult with documentation if necessary.

A method for reading every field value should be implemented, for example:

    int getCmdID() const
    {
      return cmdID;
    }
 
    static int getNextCmdID()
    {
      return cmdIncrementedID;
    }
    
    ...
    
    double getVolume() const
    {
      return volume;
    }

Now let us consider error handling functions provided by MT4 and try to make use of them. Most impostant thing is that GetLastError function not only returns last error code but also clears it internally, so that successive calls will return 0. I don't think this is a good design. For example, if you use a 3-d party library which calls GetLastError inside, your code will never detect which error has actually happened. So let us save error code in the object as integer _LastErrorOverride and implement the wrapper function:

    int getLastError()
    {
      if(_LastErrorOverride != 0) return _LastErrorOverride;
      int e = GetLastError();
      if(e != 0)
      {
        _LastErrorOverride = e;
      }
      return _LastErrorOverride;
    }

The function returns last error code from the variable if it's assigned, or requests the code from the built-in GetLastError and then saves result in the variable. Also we need to wrap another standard function:

    void resetLastError()
    {
      ResetLastError();
      _LastErrorOverride = 0;
    }

After all the preparations we've made by abovementioned code, we are ready to deal with main task of any command - its execution. Our base class should provide a method which establishes some default process in a step-by-step manner. Derived classes will be able to customize every step. This design pattern is called "template method".

For the simplicity we'll use the former approach with a loop - this is harmless as far as we can control it by parameters and even make as trivial as single pass with zero sleeping time. Inside the loop there will be the following steps:

  1. reset error code;
  2. execute command specific action;
  3. analyze error code;
  4. if error is temporary - retry (go to step 1), otherwise - stop the process with the error.

    int process(const int maxRetries, const int timeWaitMsecs)
    {
      int i;
      for(i = 0; i < maxRetries; i++)
      {
        resetLastError();
        
        int code = execute();
        if(code > 0) return code; // success
        
        Print("DATA:", symbol, " ", cmd, " p=", D2S(price), " sl=", D2S(stoploss), " tp=", D2S(takeprofit),
        " / ask=", D2S(MarketInfo(symbol, MODE_ASK)), " bid=", D2S(MarketInfo(symbol, MODE_BID)), " V=", D2S(volume),
        " s=", (int)MarketInfo(symbol, MODE_SPREAD), " ", (int)MarketInfo(symbol, MODE_STOPLEVEL));
        
        int err = getLastError();
        bool priceRequote = false;
        
        if(handledError(classname(), err, priceRequote) == -1) break;

        Sleep(timeWaitMsecs); // NB: doesn't work in the tester
        RefreshRates();
        
        if(cmd == OP_BUY) price = MarketInfo(symbol, MODE_ASK);
        else if(cmd == OP_SELL) price = MarketInfo(symbol, MODE_BID);
        
        // we can't deduce updated price for pending orders and order deletion
        if((cmd > OP_SELL && priceRequote) || cmd < 0) break;
        
        Print("New price=", D2S(price));
      }
      
      if(i == maxRetries)
      {
        Print("Max retries count ", maxRetries, " reached, give up");
        return 0; // neutral, retry later
      }
   
      return -1;
    }

The loop runs maxRetries times. On every interation we start from resetting error code and then call yet unknown method execute which returns an integer number. This method should be virtual and overridden in descendants. This is where every specific command will perform its actual task, such as opening or closing an order. The resulting value should be positive on success, negative on error and 0 in a neutral situation. If it's positive we exit the method immediately returning the code (the meaning of the code may depend from specific command). Otherwise we output command details in log and read last error code by means of getLastError. Next we use handledError (described several paragraphs above) to discover if the error can be handled by waiting a bit or not. If not, we break the loop and exit with result -1. If the error seems temporary, we Sleep for timeWaitMsecs period, refresh rates and update command's price if it's for new market order. Then the cycle repeats if attempts count does not exceed maxRetries. If it is, we return 0, signalling outside that the command is not completed yet, but there are chances to make it next time.

In the call of handledError there is another yet unknown virtual function classname. Both virtual functions - execute and classname - can be made pure abstract in the base command class or provide minimal default implementation, for example:

    virtual int execute() // = 0;
    {
      _LastErrorOverride = ERR_NO_RESULT;
      return -1;
    }
    
    virtual string classname() const // = 0;
    {
      return typename(this);
    }

Execute does actually nothing and returns error (because we don't want to run the dummy process again and again).

Classname returns a name of current class. You'll see that we will override the function in all descendants with very same implemenation - so in every case typename will return actual name of a derived class.

Please note that const modifier DOES always affect method's signature. This is why it's important to keep const in all redefinitions. Otherwise, if you forget it, you'll actually add new non-const virtual method in a derived class instead of overriding the existing virtual method from the base class.
Let us look how a command for opening a new order can be defined as a derived class.

class OrderCreateCommand: public OrderCommand
{
  public:
    OrderCreateCommand(int cmd, double volume, string symbol, double price = 0, int deviation = 0, double stoploss = 0, double takeprofit = 0,
      string comment = "", int magicId = 0, datetime expiration = 0, color arrow_color = CLR_NONE):
      OrderCommand(symbol, cmd, volume, price, deviation, stoploss, takeprofit, comment, magicId, expiration, arrow_color)
    {
    }

    virtual int execute()
    {
      if(price == 0) // use current price for market/instant orders
      {
        if(cmd == OP_BUY) price = MarketInfo(symbol, MODE_ASK);
        else if(cmd == OP_SELL) price = MarketInfo(symbol, MODE_BID);
      }
      
      result = OrderSend(symbol, cmd, volume, price, deviation, stoploss, takeprofit, comment, magicId, expiration, arrow_color);
      return result;
    }
    
    virtual string classname() const
    {
      return typename(this);
    }
};

This is a simplified version of the code. At the bottom you'll find a file with complete code attached.

And here is how a command for order modification can look like.

class OrderModifyCommand: public OrderCommand
{
  private:
    int ticket;
    
  public:
    OrderModifyCommand(int ticket, double price = 0, double stoploss = 0, double takeprofit = 0,
      datetime expiration = 0, color arrow_color = CLR_NONE):
      OrderCommand("", -1, 0, price, 0, stoploss, takeprofit, NULL, 0, expiration, arrow_color)
    {
      this.ticket = ticket;
      if(OrderSelect(ticket, SELECT_BY_TICKET))
      {
        if(this.price == 0) this.price = OrderOpenPrice();
        this.symbol = OrderSymbol();
        this.cmd = OrderType();
        this.volume = OrderLots();
        this.comment = OrderComment();
        this.magicId = OrderMagicNumber();
      }
    }
    
    virtual int execute()
    {
      result = OrderModify(ticket, price, stoploss, takeprofit, expiration, arrow_color);
      return result;
    }
    
    virtual string classname() const
    {
      return typename(this);
    }
};

You'll find all the other commands in the attached file as well.


The manager

It's time now to develop a manager class which will control all the command objects. The manager should hold common settings for all commands, such as deviation, magic number, default number of retries in the loop and default time to wait between them:

class OrderManager
{
  private:
    int slippage;
    int magic;
  
    int maxRetries;
    int timeWaitMsecs;

The manager should store all pending commands somewhere.

    OrderCommand *commands[];

And the manager should be most likely instantiated only once. In fact, there could be cases when you want to have many managers in a single expert adviser, but for the simplicity we decide to use only one instance in this framework. This is why we use the "singleton" design pattern.

    static OrderManager *manager; // singleton object instance

    OrderManager() // constructor is hidden from outer space
    {
      maxRetries = 10;
      timeWaitMsecs = 5000;
      slippage = 10;
      
      manager = GetPointer(this);
    }

  public:

    static OrderManager *getInstance()
    {
      if(manager == NULL) manager = new OrderManager();
      return manager;
    }

The manager can only be created by the public factory method getInstance, which either creates a new manager object (if it does not yet exist) or returns already existing one.

As you remember the manager stores deviation and magic number, which are default values for all commands. The manager can "publish" them via getters:

    int getDeviation() const
    {
      return slippage;
    }
    
    int getMagic() const
    {
      return magic;
    }

As a result, we can use the default values in OrderCommand constructor and thus allow a caller to omit these specific properties in constructor call.

    // updated lines in the OrderCommand constructor:
    // use default values if deviation or magic are not specified  
    this.deviation = deviation > 0 ? deviation : OrderManager::getInstance().getDeviation();
    this.magicId = magicId > 0 ? magicId : OrderManager::getInstance().getMagic();

But lets go back to the manager.

To initiate execution of a command we pass its object (pointer) to the manager.

    void place(OrderCommand *cmd)
    {
      int i, n = ArraySize(commands);
      for(i = 0; i < n; i++)
      {
        if(commands[i] == NULL) break;
      }
      
      if(i == n)
      {
        ArrayResize(commands, n + 1);
      }
      else
      {
        n = i;
      }
      commands[n] = cmd;
    }

The method finds an empty element (if any) in the array of commands or extends the array and reserves new empty slot for the command. The commands stored in the array should be processed in a dedicated method. 

    void processAll()
    {
      int n = ArraySize(commands);
      for(int i = 0; i < n; i++)
      {
        if(CheckPointer(commands[i]) != POINTER_INVALID)
        {
          int r = commands[i].process(maxRetries, timeWaitMsecs);
          if(r != 0) // succeeded or failed, otherwise 0 - still pending
          {
            commands[i] = NULL;
          }
        }
      }
    }

For every not yet completed command it calls its method process, and if it fails unrecoverably or succeeds the command is removed from the queue.

The method processAll should be called periodically, for example from a timer event. For that purpose we'll define:

    void onTimer()
    {
      processAll();
    }

which should be invoked from standard OnTimer function of EA. Other important events should be also utilized.

    void onInit()
    {
      // TODO: restore command queue from Global Variables
    }
    
    void onDeinit()
    {
      // TODO: serialize pending commands to Global Variables
      // TODO: clean up the queue
      if(CheckPointer(&this) == POINTER_DYNAMIC) delete &this;
    }

Lets wire up all things together using new order creation as example.

To open a new order we have to create an object of the class OrderCreateCommand and pass it to the manager via its place method. Next time the timer event fires, the manager executes all pending commands including our OrderCreateCommand. This will try to do OrderSend, error analysis, and then either leave the command in the queue for more trials or exclude the command as completed or failed (depending from the result).  


Refinement

There are 2 problems here. First, we missed object destruction. Second, we did not provide a way to propagate command's result back to caller.

The first issue is not an issue if we save the pointer to our command in the EA an delete it ourselves. But this is not convenient in many cases. For example, it's much simplier to have an option to write:

manangerObject.place(new OrderCreateCommand(OP_BUY, 0.1, Symbol()));

and "forget" about the object allowing the manager to release the memory.

As far as use-cases may vary, automatic deletion of processed commands should be enabled or disabled by a flag. For this purpose let's add a protected variable selfDestruction to OrderCommand class and corresponding accessor method:

    bool isSelfDestructing()
    {
      return selfDestruction && (CheckPointer(&this) == POINTER_DYNAMIC);
    }

Here we added the check if the pointer is dynamic, because only dynamically allocated objects can be deleted explicitly (we can't guarantee that some programmer will not set selfDestruction to true mistakenly for an automatically allocated object).

The variable can be initialized through a constructor parameter (in OrderCommand or in derived classes), so that caller can specify if he/she wants the object to devastate itself. Actually we can provide helper constructors with shorter syntax, for example: 

    OrderCreateCommand(const int cmd, const double volume):
      OrderCommand(_Symbol, cmd, volume)
    {
      selfDestruction = CheckPointer(&this) == POINTER_DYNAMIC; // convenient but tricky implicit initialization
    }

This way we have no need to pass additional parameter to the constructor. You may look through the attached code for more possibilities, including the "builder" pattern (which allows you to setup object properties on demand step by step, by chainable simple methods).

Once the variable selfDestruction is assigned, we can use it for object "finalization". In the manager's processAll method change the lines to:

    if(r != 0) // succeeded or failed, can be deleted
    {
      if(commands[i].isSelfDestructing())
      {
        delete commands[i];
      }
      commands[i] = NULL;
    }

In the attached file this part is more eleborated, but it's simplified here for readability. In any way, we can now delegate destruction of the command objects to the framework.

The second issue was related to returning result of a command execution back to EA. When it comes to asyncronous commands the best way for passing information is a notification interface.

class OrderCallback
{
  public:
    virtual void onSuccess(const int result, const OrderCommand *cmd)    // = 0;
    {
      Print("Completed ", cmd.toString(), ", result: ", result);
    }
    virtual void onFailure(const int error, const OrderCommand *cmd)     // = 0;
    {
      Print("Failed ", cmd.toString(), ", error: ", error);
    }
    virtual void onTimeout(const int timeout, const OrderCommand *cmd)   // = 0;
    {
      Print("Timeout ", cmd.toString(), ", timeout: ", timeout);
    }
};

Again, it could be a pure interface, that is an abstract class with pure virtual methods and no data, but we provide a minimal base implementation. The methods allows us to send 3 notifications: on successfull task completion, on insolvable problem, and on timeout (the usage of the later one is shown in the code only).

The variable of the type OrderCallback should be added to the manager class.

  private:
    OrderCallback *pCallback;

Then EA can implement an object of this class and assign it via special method in the manager:

    void bindCallback(OrderCallback *c)
    {
      pCallback = c;
    }

Now we can elaborate processAll method by adding the notifications: 

    void processAll()
    {
      int n = ArraySize(commands);
      for(int i = 0; i < n; i++)
      {
        if(CheckPointer(commands[i]) != POINTER_INVALID)
        {
          int r = commands[i].process(maxRetries, timeWaitMsecs);
          if(r != 0) // succeeded or failed, otherwise 0 - still pending
          {
            if(CheckPointer(pCallback) != POINTER_INVALID)
            {
              if(r > 0)
              {
                pCallback.onSuccess(r, commands[i]);
              }
              else // r < 0
              {
                pCallback.onFailure(commands[i].getLastError(), commands[i]);
              }
            }

            if(commands[i].isSelfDestructing())
            {
              delete commands[i];
            }
            commands[i] = NULL;
          }
        }
      }
    }

As you see, successfully completed command will send back the result of its execution. For OrderCreateCommand this is a ticket, and for other commands the content will vary. In case of a fatal error its code is sent with the notification.

Finally, for convenience we can add shorthand methods which will mimic standard functions such OrderSend, OrderModify etc. Here is a helper method in the manager:

    int Send(string symbol, int cmd, double volume, double price, int deviation, double stoploss, double takeprofit,
             string comment = "", int magicId = 0, datetime expiration = 0, color arrow_color = CLR_NONE)
    {
      OrderCreateCommand create(cmd, volume, symbol, price, deviation, stoploss, takeprofit, comment, magicId, expiration, arrow_color);
      return create.process(maxRetries, timeWaitMsecs);
    }

And accompanying define:

#define OrderSend OrderManager::getInstance().Send

More features are actually implemented in the code of the manager, but they are left uncovered in the text due to the lack of space. For example, it's possible to try to execute commands synchronously (as shown above). You may inspect the source code for details. 


The command with delays

One of advantages of OOP is that we can easily extend the base functionality of the framework with new capabilities. For example, as the tester does not allow us to evaluate how EA performance degrades due to arbitrary delays in order execution, we can develop a special class OrderCreateCommandWithDelay which will do it for us.

class OrderCreateCommandWithDelay: public OrderCreateCommand
{
  private:
    int delay; // seconds
    
  public:
    OrderCreateCommandWithDelay(int delay, int cmd, double volume):
      OrderCreateCommand(cmd, volume)
    {
      this.delay = delay;
      if(delay > 0) Print("delay till ", (string)(created + delay));
    }
    
    virtual int execute()
    {
      if(created + delay > TimeCurrent())
      {
        Print("delay");
        // randomly switch between handled (ERR_BROKER_BUSY) and unhandled (ERR_MARKET_CLOSED) simulated error
        this._LastErrorOverride = MathRand() > 32767 / 2 ? ERR_MARKET_CLOSED : ERR_BROKER_BUSY;
        return -1;
      }

      // after the specified delay, execute order as usual
      if(price == 0)
      {
        if(cmd == OP_BUY) price = MarketInfo(symbol, MODE_ASK);
        else if(cmd == OP_SELL) price = MarketInfo(symbol, MODE_BID);
      }
      
      result = OrderSend(symbol, cmd, volume, price, deviation, stoploss, takeprofit, comment, magicId, expiration, arrow_color);
      return result;
    }

    virtual string classname() const
    {
      return typename(this);
    }
};

Look how this code works according to testing logs.

01: delay till 2016.04.01 08:00:14
02: delay
03: DATA:EURUSD 0 p=0.000000 sl=0.000000 tp=0.000000 / ask=1.137450 bid=1.137350 V=0.010000 s=10 0
04: OrderCreateCommandWithDelay error: 137 handled:true
05: New price=1.137450
06: delay
07: DATA:EURUSD 0 p=1.137450 sl=0.000000 tp=0.000000 / ask=1.137450 bid=1.137350 V=0.010000 s=10 0
08: OrderCreateCommandWithDelay error: 137 handled:true
09: New price=1.137450
10: Max retries count 2 reached, give up
11: OrderSend error 138
12: DATA:EURUSD 0 p=1.137450 sl=0.000000 tp=0.000000 / ask=1.137250 bid=1.137150 V=0.010000 s=10 0
13: OrderCreateCommandWithDelay error: 138 handled:true
14: New price=1.137250
15: open #435 buy 0.01 EURUSD at 1.13725 ok
16: Completed 876 OrderCreateCommandWithDelay 0 EURUSD 0.01, result: 435

1-st line was issued by the command constructor at the moment when EA decided to open a trade. The 2-nd indicates that a simulated delay was applied once. The line 4 shows the code of simulated error - 137 (busy), and marks it as handled, that is the framework will retry this operation automatically. New price is received in the line 5, and second iteration of retries is logged in line 6. Since this is the 2-nd attempt and maxRetries was set to 2 processing is paused at this tick, according to the line 10. On an another tick (line 11), which is not actually the next tick, because commands are executed with the given timer period (1 second in the example setup) we see error 138 (requote), which is generated by the tester because the command object tries to open new order using saved price. This is also a handled error, so the framework refreshes quotes and get new price (line 14), after which the order is successfuly opened (line 15), and the framework sends notification about this in the line 16. 


The Expert Adviser

The framework is ready. Lets code an example EA. It uses simple rebound strategy on Bollinger bands. The file is attached.

Example of EA based on OrderManager (rebound Bollinger Bands strategy)

It works. It has been tested on EURUSD H1. You may compare execution without and with delays. You may also try to embed the framework in your existing EA since it supports regular syntax of OrderXXX functions and synchronous execution. Yet its advantage is asynchrony, and to enable it you need only to add ASYNC_ORDERS define before include. In this case, the manager will try to execute commands instantly and return their appropriate results, but if a minor error occurs, the command will be added to the queue for execution in future. If you need to get asynchronous results (such as ticket number), override notification callbacks as appropriate.

 

This is a draft rather than a real life expert adviser, but it covers many practical aspects making it a good starting point for further development.



Table of contents

Object hierarchies and serialization

21 October 2016, 13:28
When it comes to OOP, there should be an hierarchy of classes. And most of hierachies are built from a single base class. This is very powerful feature. It allows you to store objects in various containers, such as vectors, queues, lists, and process them consistently in batch tasks. MQL standard library does also include such a class called CObject. Actually I don't like the library and don't use it, but the idea about classes with a common root is worth a thorough consideration. BTW, many existing languages, such as Java, do also provide a base class, so anyone can investigate them and take some useful hints.

Let us look at the CObject first, and then try to design our own base class for MQL.

class CObject
{
  private:
   CObject          *m_prev;               // previous item of list
   CObject          *m_next;               // next item of list

  public:
                     CObject(void);
                    ~CObject(void);
   CObject          *Prev(void) const;
   void              Prev(CObject *node);
   CObject          *Next(void) const;
   void              Next(CObject *node);
   // methods for working with files
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   // method of identifying the object
   virtual int       Type(void) const;
   // method of comparing the objects
   virtual int       Compare(const CObject *node, const int mode = 0) const;
};
I have no idea why the object contains two pointers for next and previous items. Objects can be out of any list, and these members are of no use. I'd say that for list items there should be a dedicated class - name it Item, for example - derived from the base root. Then the pointers would belong to it. Actually this is a bad idea to have member variables in the root class. It should be as much abstract as possible.

The methods working with files are also inapproriate here. I agree that the object should have methods for serialization and deserializtion - that is saving and restoring it to/from some persistent storage. But why this feature is bound to files and specifically to file handles? What if I like to save object in global variables or on my server via WebRequest? What if someone will pass a handle of a file opened for reading into Save method? And what if the file is in text mode but object wants to write binary data? Such base class can become a cause for many potential errors. 

The method Type has a misleading name, imho. As its comment says, this is a kind of unique identifier of an object, so I'd change the name to something more appropriate. In Java corresponding method is called hashCode.

Last but not the least, every object should have a method to get its string represenation, and such thing is missing here.

After this short criticizm, we can finally develop our base class.  

class Object
{
  private:
  public:
    virtual string toString() const {return classname() + "#" + StringFormat("%d", &this);}
  
    virtual bool save(OutputStream &out) const {return false;}
    virtual bool load(InputStream &in) {return false;}

    virtual long version() const {return 0;}
    virtual string classname() const = 0;    
    virtual long signature() const {return 0;}
    
    virtual int compare(const Object &other, const double customValue = 0) const
    {
      return (int)(signature() - other.signature());
    }
};

You may ask what are the classes OutputStream and InputStream. Here is a draft.

class OutputStream
{
  public:
    virtual bool writeBoolean(const bool b) = 0;
    virtual bool writeByte(const uchar c) = 0;
    virtual bool writeInt(const int i) = 0;
    virtual bool writeLong(const long l) = 0;
    virtual bool writeString(const string &s) = 0;
    virtual bool writeDouble(const double d) = 0;
    virtual bool writeDatetime(const datetime t) = 0;
};

class InputStream
{
  public:
    virtual bool readBoolean() = 0;
    virtual uchar readByte() = 0;
    virtual int readInt() = 0;
    virtual long readLong() = 0;
    virtual string readString() = 0;
    virtual double readDouble() = 0;
    virtual datetime readDatetime() = 0;
};

class TextFileStream: public OutputStream
{
};

class BinaryFileStream: public OutputStream
{
};

class GlobalVariableStream: public OutputStream
{
};

I hope the idea behind them is clear now. They are abstract input/output "interfaces" without dependency to a concrete storage type. The object stored in such external streams can be restored lately using InputStreams. The set of writeXXX functions can be extended with a templatized function for convenience (please note, it's applicable only for textual representation, as it uses string):

    template<typename T>
    bool write(const T t)
    {
      return writeString((string)t);
    }

Now lets us return back to the Object class. Every object has a specification, including - but not limited to - which data does it contain. If you're going to save an object instance for longer persistence (for example, between teminal sessions) you should think about possible changes in specification. If you have a stored object and then extend its class with a new field which should be stored as well, then the old object will lack the new field, and your code should probably perform special actions for proper object initialization. This is why we have the method version - it allows you to return version number of current object specification and compare it with a version of any object being restored from an external storage.

The method classname is used to provide a readable identifier of the class. Most simple and suitable implementation can be just typename(this). Finally we have the method signature, which is an identifier of the object (this is the analogue of Java's hashCode). If 2 objects contains the same data they should return the same signatures. This makes it simple to compare objects. The base implementation is shown in the compare function. It's coded in such a way that compare can be used not only to detect if objects are different but also to order them by some property (the property could be specified by customValue, for example). This is useful for sorting, but requires a proper implementation of the signature method in the sense of "measuring" object "value" consistently.

Here is a simple example, which demonstrates the whole idea.

class Pair : public Object
{
  public:
    double x, y;
    Pair(const double _x, const double _y): x(_x), y(_y) {}
    Pair(const Pair &p2)
    {
      x = p2.x;
      y = p2.y;
    }
    Pair(const Pair *p2)
    {
      if(CheckPointer(p2) != POINTER_INVALID)
      {
        x = p2.x;
        y = p2.y;
      }
      else
      {
        x = y = 0.0;
      }
    }
    Pair(): x(0), y(0) {}
    
    virtual string classname() const
    {
      return typename(this);
    }
    
    virtual bool save(OutputStream &out) const
    {
      out.write(classname() + (string)version());
      out.write(x);
      out.write(y);
      return true;
    }
    
    virtual bool load(InputStream &in)
    {
      string type = in.readString();
      if(type != classname() + (string)version())
      {
        Print("Class mismatch:", type, "<>", classname() + (string)version());
        return false;
      }
      x = in.readDouble();
      y = in.readDouble();
      
      return true;
    }
    
    virtual int compare(const Pair &other, const double customValue = 0) const
    {
      return !(x == other.x && y == other.y);
    }
};

This class comprises 2 fields, which can be serialized and deserialized. At the beginning of every object stream we output the class name and version. When the object is being read from a stream, we check if the class name and version correspond to current values. In a real world case one should somehow upgrade the loaded objects with a lesser version up to the current version.

Another class can contain the Pair object and save/load all its own fields as well as the nested object.

class Example : public Object
{
  private:
    int index;
    double data;
    string text;
    datetime dt;
    Pair pair;

    ...
    
    virtual bool save(OutputStream &out) const
    {
      out.write(classname() + (string)version());
      out.write(index);
      out.write(data);
      out.write(text);
      out.write(dt);
      out.write(&pair); // pair.save(out);
      return true;
    }
    
    virtual bool load(InputStream &in)
    {
      string type = in.readString();
      if(type != classname() + (string)version())
      {
        Print("Class mismatch:", type, "<>", classname() + (string)version());
        return false;
      }
      index = in.readInt();
      data = in.readDouble();
      text = in.readString();
      dt = in.readDatetime();
      pair.load(in);
      
      return true;
    }

The complete source code is attached. Place it in MQL4/Scripts folder. The code provides different stream classes to store objects to the log (as a debug output) or text files. For example, we can save an object to a file and then restore it.

void OnStart()
{
  LogOutputStream log;  // this is just for debug purpose

  Example e1(1, 10.5, "text", TimeLocal());
  Print(e1.toString());
  e1.save(log);
  
  TextFileWriter tfw("file1.txt");
  e1.save(tfw);         // serialize object to the text file
  tfw.close();          // release file handle
  
  TextFileReader tfr("file1.txt");
  Example e2;
  e2.applyPair(-1, -2); // apply some changes, just to make sure load will override them
  e2.load(tfr);         // deserialize the text into another object instance
  Print(e2.toString());
  e2.save(log);
  
  Print("Is equal after restore:", (e1 == e2));      // true, obects are identical
  e2.applyPair(5, 6);
  Print("Is equal after modification:", (e1 == e2)); // false
}


 

Table of contents



Files:
MQL code performance is important in many aspects. For example, if your expert adviser is well optimized you could pay much less for it running in the cloud. Or you can eliminate a situation when your  terminal becomes unresponsive for a while while it calculates a heavy indicator. Of course, there can be many different ways of optimization (such as code refactoring, proper algorithm selection, caching, and a lot of others deserving thorough consideration, but implying a mass of text far larger than a single blog post can comprise). But the first step is always to analyze the code performance and find bottlenecks. MetaTrader provides a built-in tool for timing analysis of functions and even single lines of code - the profiler.

Unfortunately, the built-in profiler works only in online charts, where inefficient parts of code are not so easy to spot. It would be much more convenient to profile code in the tester (during single tests or even batch optimization of an expert adviser). For that purpose we can implement a profiler ourselves as a set of classes. It will be simple yet sufficient enough to perform main profiler tasks.

The header file Profiler.mqh is attached below. It contains 3 classes: Profiler, ProfilerLocator, and ProfilerObject.

  • Profiler - implements the main functionality;
  • ProfilerLocator - is intended for pointing program contexts or parts (such as functions or sequences of code lines), where we want to measure performance;
  • ProfilerObject - is a helper class, which allows for automating starts/stops of profiling process in accordance to its objects lifetime cycles; for example, you can declare such an object in a function (specifically, create an automatic instance on the stack), and then - no matter how many exit points the function has - every time control flow leaves the scope of the function, the object will be destroyed, which in turn will stop profiling (until the next time the function is entered again, which in turn will start profiling automatically again).

The comments in the file should make the code almost self-explainatory. And introductory information is given here in the text.

The Profiler object is already declared in the header. By default it's initialized with 100 points for measurements, that is one can evaluate duration of execution of 100 code fragments. Every point is basically backed with an element in internal arrays which store time lapse and count of times of code execution. If you need more points, adjust the initialization: the required number of measurement points is passed as the parameter of the Profiler constructor.

Profiling process is controlled by invocation of the profiler methods at specific points in your code. Here are some examples of the profiler usage.

The simpliest, but slowest (and hence not recommended) method is:

int pid = profiler.StartProfiler("UniqueNameOfTheFragment");
... // code for profiling
profiler.StopProfiler(pid);

The fragment above is identified by a unique name, specified in the StartProfiler method. There are more efficient forms of the method StartProfiler, for example the one which identifies code fragments by integer numbers. For this variant to use, one could first register all code fragments in the profiler (for example, in OnInit):

profiler.SetName(1, "First");
profiler.SetName(2, "Second");

Then you can start and stop profiling by the following calls:

profiler.StartProfiler(1);
... // code fragment 1
profiler.StopProfiler(1);
profiler.StartProfiler(2);
... // code fragment 2
profiler.StopProfiler(2);

As a convenient way of defining profiling points one may use ProfilerLocator objects inline - just before corresponding code fragments: 

static ProfilerLocator locator3("CalculateProfit");
profiler.StartProfiler(locator3);
... // code
profiler.StopProfiler(locator3);

The fragment above is identified by the locator object, which generates a unique integer identifier internally. Please note that the locator object is declared static, because it should be created only once. This guarantees that every execution of the code fragment will use the same profiling element accumulating statistics. If the object would not be static, every single pass would generate (reserve) new measurement point, and everyone would have the same name "CalculateProfit". 

Finally, one of the most popular variants of profiling - function profiling - can be achieved by placing the following lines at the beginning of a function: 

static ProfilerLocator locator(__FUNCTION__);
ProfilerObject obj(locator);

Automatic creation of the ProfilerObject object starts profiling of the entire function scope automatically, and its deletion upon function termination stops profiling automatically as well. A shorthand macro _PROFILEFUNC_ is defined for this in the header file.

After all profiling points are set by one of the methods of the profiler, you can access gathered statistics at the end of MQL program execution (for example, in OnTester). The function Profiler::GetStats(id) returns a string with a number of times and total time of execution of the fragment with the given identifier id. You may also output all statistics in the log by Profiler::PrintAllStats().

Here is an example.

#include <profiler.mqh>

#define CALC_PROFILE_ID 10

_PROFILESCRIPT_;

void subfunc()
{
  _PROFILEFUNC_;
  
  for(int i = 0; i < 1000; i++)
  {
    profiler.StartProfiler(CALC_PROFILE_ID);
    for(int j = 0; j < 10000; j++)
    {
      MathSqrt(MathPow(i, 3));
    }
    profiler.StopProfiler(CALC_PROFILE_ID);
  }

  Sleep(250);
}

void OnStart()
{
  profiler.SetName(CALC_PROFILE_ID, "calculation");
  Sleep(500);
  subfunc();
  profiler.PrintAllStats();
}

This script will produce something like this:

Profiler stats: name count seconds milliseconds     %% Comments
        profilertest     1       1         1607 100.00 WARNING! Keeps running
             subfunc     1       1         1107  68.89
         calculation  1000       0          858  53.39

 


 
Table of contents

 

 


Files:
Profiler.mqh 15 kb
Today we're starting a patchy subject, which will combine many different things. This is why I decided to split it into parts. Out final goal is exception handling, but before we can address it, we need to prepare a ground.

 

Overview

Exceptions are very powerfull and useful mechanism of modern object oriented programming languages. They exist in C++, Java, PHP, and many others. This makes me believe that exceptions are "must have" feature for a mature OOP language. The fact that they are (currently) missing in MQL diminishes its capabilities and value. This is definitely a design error from MetaQuotes.  

The reason why exceptions are so important can be easily seen in the following 2 axioms.

Every program - no matter how thoroughly it's been developed and tested - contains bugs.

Well-designed program should work stable despite the bugs.  

In MQL, if an error occures in a program, it is stopped and unloaded. This is a very frustrating feature for a program that controls your money. Far better approach would be to give the program a chance to analyse the error and recover, hence continue working if possible.

The new strict policy of MQL execution was not there from very beginning. Some time ago (before OOP epoch in MQL4) MQL4 could silently suppress part of errors. For example, when accessing an array element by incorrect index in a legacy MT4 indicator, it ignores the error and continues running. Then MQ introduced new property strict directive in MQL4, when it was upgraded to MQL5, and made the directive mandatory for the Market products. Since that moment both MQL4 and MQL5 programs (where the strict mode is always on) can not survive on errors.

The problem must thus be solved by application developers. We need to invent something similar to exceptions handling.

First let us decide which types of critical errors to handle. Most frequent errors are "index out of bounds" and "divide by zero". There is a number of others of course, but part of them can not be handled internally by MQL (for example, if a requested library or indicator is unavailable) and the rest can be addressed later in the similar way to the two most frequent ones, which are covered below. All beyond this is left for homework ;-). And here is a small hint: next best candidate could be "invalid pointer access" error, which can be processed by a kind of safe pointers class.

 

Rubber array

It's time now to develop a class of safe array which can deal with the "index out of bounds" error. Along with the error handling we can add into the class many other features which will be handy for operations with arrays. I'll name the class RubbArray (which stands for "rubber array") because its creation was initially inspired by the need to automatically adjust its size. I'm sure all of you typed a lot of times something like that:

int n = ArraySize(data);
ArrayResize(data, n + 1);
data[n] = d;

Placing such stuff inside an implicit helper method can simplify our code significantly. Of course, the class should be templatized because we want a universal array capable of storing values of different types. So start with:

template <typename T>
class RubbArray
{
  protected:
    T data[];

    void add(T d)
    {
      int n = ArraySize(data);
      ArrayResize(data, n + 1);
      data[n] = d;
    }

Data will be actually stored in the protected data array. We hide the method add in protected part as well, because no one would normally like to call it, provided that other shorthand notations can be easily implemeted. For example, in PHP we can declare and fill arrays like this:

<?php

$array1 = [];
$array1[] = 1;
$array1[] = 2;
$array1[] = 3;

?>

Here we added 3 elements into the array. Let's mimic this in RubbArray class.

  public:
    T operator=(T d)
    {
      add(d);
      return d;
    }

Now we can use the assignment operator to add new elements into the array. Similar operator can be overloaded for copying arrays.

    void operator=(const RubbArray &d)
    {
      int i, n = d.size();
      ArrayResize(data, n);
      for(i = 0; i < n; i++)
      {
        data[i] = d[i];
      }
    }

In addition to operator= we can provide another way of adding elements. For example: 

    RubbArray *operator<<(T d)
    {
      add(d);
      return &this;
    }

This is almost the same as operator=, but we return the array object itself, making it possible to chain calls in a single line:

    RubbArray<double> a;
    a << 1 << 2 << 3;

Now, when the array is filled with some data, code a method to access elements, specifically let's overload operator[].

    T operator[](int i) const
    {
      if(i < 0 || i >= ArraySize(data))
      {
        Print("Index out of bounds"); // ?
        return 0;                     // ?
      }
      return data[i];
    }

The lines marked by comments are just placeholders for something we'll need to elaborate on later. But it's already a safe code: for incorrect indices it returns 0 silently, as former MQL did for time series (it actually returned EMPTY_VALUE, but we use 0 here as more universal for different types, and the value can be customized in future, as you'll see). 

Also one may want to extract an element (specified by its index) from the array.

    T operator>>(int i)
    {
      T d = this[i];
      if(i >= 0 || i < ArraySize(data))
      {
        ArrayCopy(data, data, i, i + 1);
        ArrayResize(data, ArraySize(data) - 1);
      }
      return d;
    }

Notice how we used operator[] internally.

And what if we want to change an element? Well, since MQL's overloads are still far from ideal (in comparison to C++ or Pascal for example), it'll require a touch of voodoo. First, add a plain method for changing.

    void change(int i, T value)
    {
      data[i] = value;
    }

Now we need somehow call it in a neat way. This can be done in 3 steps. First create a helper class:

template <typename T>
class Editor
{
  RubbArray<T> *parent;
  int index;
  
  public:
    Editor(RubbArray<T> &array)
    {
      parent = &array;
    }
    Editor<T> *operator[](int i)
    {
      index = i;
      return &this;
    }
    void operator=(T value)
    {
      if(index != -1) // special case of error (see below)
      {
        parent.change(index, value);
      }
    }
};

As you may see, its sole purpose is to override operator[] and operator= to pass required index and value to the change method of RubbArray. Editor is created for specific RubbArray and stores a reference for the parent.

In turn RubbArray class should manage Editor object for itself, so add the following member variable into RubbArray (step 2):

  private:
    Editor<T> *editor;

and constructor/destructor: 

  public:
    RubbArray()
    {
      editor = new Editor<T>(this);
    }
    
    ~RubbArray()
    {
      delete editor;
    }

Step 3 - adding a method and new overload of operator[] to access the editor.

    Editor<T> *operator[](uint i) // notice different type 'uint', this is a hack
    {
      return edit(i);
    }
    
    Editor<T> *edit(int i)
    {
      if(i < 0 || i >= ArraySize(data))
      {
        return editor[-1]; // do nothing in case of error
      }
      return editor[i];
    }

For convenient usage of the new overload one may define a macro:

#define edit(A) ((uint)A)

Now we can code:

 Print(a[0]); // read 0-th element
 a[edit(0)] = 10; // write 10 to 0-th element

The complete class is attached in SafeArray.mqh at the bottom of the page.

As you remember we marked a couple of lines in RubbArray for potential "index out of bounds" error handling. Let us leave the stuff as is for a while, and switch to the other typical error "divide by zero".

 

Safe numbers

The problem with division by zero should be handler by another class. Name it Number and make templatized as well.

template<typename T>
class Number
{
  private:
    T v;
    double eps;
    void init(const double e)
    {
      eps = e == 0 ? MathPow(10, -1 * _Digits) : e;
    }

The class includes several things not related to error handling just for making hook-ups, and can be extended further. For example, the eps variable is intended for comparison of 2 numbers with a given accuracy, which is usually important for doubles. We can construct or assign a value to the Number in straightforward manner:

  public:
    Number() {init(0);}
    Number(const T &d, const double e = 0): v(d) {init(e);}
    Number(T d, const double e = 0): v(d) {init(e);}
    Number(const Number<T> &c): v(c.v) {init(c.eps);}
    
    T operator=(T x)
    {
      v = x;
      return v;
    }
    Number<T> operator=(const Number<T> &x)
    {
      v = x.v;
      return this;
    }
    bool operator==(T x) const
    {
      return MathAbs(v - x) < eps;
    }
    bool operator!=(T x) const
    {
      return !(this == x);
    }

Also Number should overload all possible operations, including division, which is actually our main concern.

    T operator*(Number<T> &x) const
    {
      return v * x.v;
    }

    T operator/(Number<T> &x) const
    {
      if(x.v == 0)
      {
        Print("Divide by zero"); // ?
        return 0;                // ?
      }
      return v / x.v;
    }

Again, the commented lines of code will require error handling in future. Currently we return 0, suppressing the error.

The complete code of the example class Number is attached in SafeNumbers.mqh file at the bottom of the page.

Now all is ready to deal with exceptions. But we already typed a lot of code, so let us take a break and continue with this in the part 2.


Files:
Exception handling is a powerfull and useful mechanism of modern OOP. It's used to improve program stability, manage resources, change control flow on-the-fly, simplify applied source code, which otherwise - without exception handling - is usually cluttered up with tons of explicit checkups preceding almost every variable access.

Unfortunately exceptions are missing in MQL. Which is why we're going to implement a surrogate, and have already prepared helper classes of rubber arrays and safe numbers in the previous publication. These classes will provide a playground for implementing our own exception handlers.

 

Exceptions

Typical exception handling block in C++ has the following structure:

try
{
  // protected code
}
catch(Exception e)
{
  // code to handle exception
}

// other code

Many OOP languages provide very similar syntax.

If an error occurs in the try block, it intercepts the error and passes control to the catch block which can analyse current situation and decide if the program can recover and how. The execution of protected code is stopped on the line where the error happened, but if the handler is able to mitigate the problem, the execution resumes on the lines below the catch block. 

Our exception handler will work slightly different, because MQL does not provide much freedom in what we can do in general. 

First, we need a technique of marking parts of code as try blocks. Objects of some class can do the trick. So, let us define the class try

class try
{
  static try *landmarks[];
  static int cursor;
  
  string location;

The object contains a variable of type string: an identifier of location. Also the class contains the static array landmarks where we will save references for all instances of try objects, and cursor which points to specific try (if any), containing lines of code being executed (of course, try blocks may not cover all the code, and there can be moments of executing code outside any try block). We need to initialize the static stuff outside the class definition.

try *try::landmarks[];
int try::cursor = -1;

And then get back to the class, specifically to its constructor.

  public:
    try(const string mark)
    {
      location = mark;
      int n = ArraySize(landmarks);
      ArrayResize(landmarks, n + 1);
      landmarks[n] = &this;
      Print("try at ", mark, " ", n);
    }

The contructor accepts a single parameter with an identifier. Then current instance - this - is added to the array of landmarks. We don't need destructor, which could remove the reference from the array, because all instances of try will be static. This makes sense since the objects are landmarks ;-). They will be created automatically by the platform before program initialization. But we need to know when a specific try block becomes active, so add appropriate method.

    int activate()
    {
      int back = cursor;
      int size = ArraySize(landmarks);
      for(int i = 0; i < size; i++)
      {
        if(landmarks[i] == &this)
        {
          cursor = i;
          return back;
        }
      }
      return back;
    }

Here we search through the array of landmarks and set cursor to found index. In addition we return previous value of the cursor, making it possible to save and restore code execution context.

    void restore(int rollback)
    {
      cursor = rollback;
    }

Who will switch the context, you ask? This should be objects of another class, and they can not be static this time, because they should be created and deleted locally and ad hoc, according to control flow, that is how program enters and leaves functions, loops and other code blocks defined by curly brackets. Here is the class. 

class context
{
  private:
    int restore;
    try *ptr;
    
  public:
    context(try &_try)
    {
      ptr = &_try;
      restore = ptr.activate();
    }
    
    ~context()
    {
      ptr.restore(restore);
    }
};

Every time an object context is created, it accepts a reference to nearby try block landmark and calls its activate method. As soon as it's deleted (when control flow leaves current code context), the index of previously active landamrk is restored.

In a program, the pair of try and context objects can be declared in the following way:

{
  static try _try(__FILE__ + ", " + (string)__LINE__ + ", " + __FUNCSIG__); context _context(_try);
}

Now we're approaching most tricky point of the scheme. Having a try block identified and activated, we need to intercept and handle exceptions somehow. The interception part is relatively simple. In the previous publication we created classes of rubber arrays and safe numbers, which can intercept specific errors, such as "index out of bounds" and "divide by zero". So, they can throw an exception and pass it to our try block. All we need is a method accepting the exception. Lets add it to the try class.

    static double throw(const string message)
    {
      int size = ArraySize(landmarks);
      if(cursor >= 0 && cursor < size)
      {
        // TODO: invoke an error handler
      }
      return 0;
    }

Having this method we need to jump for a moment back to the classes RubbArray and Number to refine their methods controlling possible errors. For example, RubbArray could have:

    T operator[](int i) const
    {
      if(i < 0 || i >= ArraySize(data))
      {
        return try::throw("Read index out of bounds: array size=" + (string)ArraySize(data) + ", index=" + (string)i);
      }
      return data[i];
    }

The method throw does nothing at the moment. Some error handler is required. This could be just a global function, for example OnError, but it would be better to try to mimic catch block syntax as much as possible, and define handlers locally for every block. In comparison with a single global function, this approach will give one more benefit of splitting error handling code to small parts, specific for every particular error and location.  

It's very logical to define the handler as a method of a class. 

class catch
{
  public:
    catch(try &_try)
    {
      _try.bind(this);
    }
    
    virtual void handle(Exception &e)
    {
      // just dump the error by default
    }
};

Of course, the catch object should be bound to try, this is why constructor accepts a reference for one. But what is the method bind? We don't have it yet. Lets add it to the class try.

class try
{
  catch *ptrc;

  public:

    void bind(catch &c)
    {
      ptrc = &c;
    }

And now it's time to elaborate the method throw.

    static double throw(const string message)
    {
      int size = ArraySize(landmarks);
      if(cursor >= 0 && cursor < size)
      {
        if(CheckPointer(landmarks[cursor].ptrc) != POINTER_INVALID)
        {
          Exception e(landmarks[cursor].location, message);
          landmarks[cursor].ptrc.handle(e);
          return e.result;
        }
      }
      return 0;
    }

Here we use another undefined yet class - Exception. It's that is easy to conceive.

class Exception
{
  public:
    const string location;
    const string message;
    double result;
    
    Exception(string l, string m): location(l), message(m), result(0) {}
    
    void throw(const double r)
    {
      result = r;
    }
};

This is just a storage of all information about current error, and also a value, which the handler can pass via Exception object back to protected code, as a result of error processing. The member variables made public for simplicity.

We're almost done. Let's see how an object with error handler could look. 

class catch22: public catch
{
  public:
    catchx(try &_x): catch(_x) {}
    
    virtual void handle(Exception &e)
    {
      Print(e.location, " : ", e.message);
      e.throw(EMPTY_VALUE);
    }
}

This example just prints information about the error and returns EMPTY_VALUE to protected block of code. If, for example, the origin of the error is a line trying to access a rubber array element by incorrect index, then it will get EMPTY_VALUE as a result, and the code continue execution smoothly. This is the difference between conventional exception handlers and our MQL exception handler. We don't have a reliable way to jump out of the block in pure MQL.  

For convenience we define the following macros, which simulate try-catch blocks via declaration of all abovementioned objects in a row.

#define TRY { static try _try(__FILE__ + ", " + (string)__LINE__ + ", " + __FUNCSIG__); context _context(_try);
#define CATCH(A) class catchx:public catch{public: catchx(try &_x):catch(_x){} virtual void handle(Exception &A)
#define END }; static catchx _catchx(_try); }

Cryptic? Well, this may take a while to understand (though all parts have been already described above), but what it actually means - we can now write something like this:

  Number<double> d(10.5);
  Number<double> x(0);
  Number<double> y;
  
  TRY
  {
    y = d / x;        // no error here, and y is DBL_MAX  
    Print(x.toString(), ", ", y.toString());
  }
  CATCH(e)
  {
    Print(e.location, " : ", e.message);
    e.throw(DBL_MAX); // pass DBL_MAX as result of division by 0
  }
  END

Magic? No - OOP. 

You may ask why do we need the separate class catch, if we could define the handler inside try? The answer is simple: because we want to place catch blocks below protected code. If it would be a part of try, the handler should be defined on top of the block.

The complete code of exception handling classes is attached below in the file Exception.mqh. Also an example script exception.mq4 is provided. Its output is:

try at exception.mq4, 56, void OnStart() 0
try at exception.mq4, 61, void OnStart() 1
try at exception.mq4, 25, void subfunc() 2
try at exception.mq4, 40, void subfunc2() 3
initialized
exception.mq4, 25, void subfunc() : Divide by zero
0, 1.797693134862316e+308
exception.mq4, 61, void OnStart() : Write index out of bounds: array size=2, index=3
exception.mq4, 56, void OnStart() : Read index out of bounds: array size=2, index=3
15.0
exception.mq4, 40, void subfunc2() : Divide by zero
0

Have fun.


Files:
Are you ready for a very very long story? It will be probably most complicated and prolonged publication in the series of MQL OOP so far, but it's surely worth reading, because its main theme is automatic self-optimization of expert advisers on the fly. This is one of the most frequently asked questions of MetaTrader users. And the answer so far is NO.

Ideally, MetaTrader could provide automatic self-optimization of EAs by design. The built-in tester does work, and only thing which is missing is an API, exposing the settings of the tester for MQL code. For example, there could be a couple of functions - TesterSetSettings, TesterGetSettings, accepting a structure with date ranges (how many bars from history to use), optimization mode (open prices, control points, ticks), spread, method and goal of optimization (straightforward/genetic, balance/profit-factor/drawdown/etc.), and a reference to a file with parameters optimization set (which parameters to optimize, in which ranges, with which increments). This set-file can be prepared by user only once (just by clicking Save button on Inputs tab of the optimization dialog), and then he can start optimization by preferred schedule from MQL code by calling a dedicated method like this: TesterStartOptimization. The results of optimization could be then accessed via similar API, and EA could select a better parameter set on the fly. Unfortunately, neither MT4 nor MT5 can afford this.

To overcome the problem we will create a library to perform virtual trading on history with different parameter sets and choose best candidates. It sounds simple, but actually requires to write more or less precise replica of the built-in optimizer. We can't do exact copy, because, for example, we can't generate ticks, but open prices mode is feasible, and this is our task. Control points mode is also doable, but with much more efforts and some limitations (for indicators), so we'll leave it to enthusiasts.

The library will work in MetaTrader 4. I have many reasons to choose it over MetaTrader 5 - its API is more simple and I use it more - just to name a few. If you like MetaTrader 5, you can develop similar library - it should be easy considering the fact that this library provides a detailed roadmap.   

 

Interface Design

It would be great to design the library in such a way, that its embedding into existing EA would require minimal alterations. As far as most natural and popular method of coding EAs for MetaTrader 4 is based on the set of functions from core APIs (such as, account and order related functions), the simpliest approach would be using the same functions both for online and virtual trading. In other words, if EA contains some code blocks calculating signals, price levels, and lots, they should be kept intact as much as possible, and the code should not even feel a difference between optimization cycles and real-time trading. Is this possible at all? Yes, it is.

The code of ordinary EA is running in the global context, where API functions such as OrderSend, OrderClose, iOpen or iClose are defined and provided by the platform. If we move the code inside a class and define our own methods imitating OrderSend, OrderClose, iOpen, iClose and all the others, inside the class, then the original algorithm will work as usual, without noticing the deception.   

For that purpose we need to implement the full trading context similar to what the platform provides, as a class. In this class we can define a virtual callback method (analogue of OnTick) to be invoked from the library. EA should create a derived class for the trade context and override the virtual callback, where the code from existing OnTick handler should be moved to. Here is an example:

class LibraryContext
{
  public:
    int OrdersTotal()
    {
      return 0; // stub
    }
    
    virtual void trade() = 0;
};

class EAContext: public LibraryContext
{
  public
    virtual void trade()
    {
      int n = OrdersTotal(); // will execute LibraryContext::OrdersTotal
    }
};

This is just a simplified scheme for explanation. In reality we'll need to use slightly different hierarchy of classes.  

The library will manage automatic switching between 2 modes: virtual optimization and real trading. When optimization is on (by some given schedule), all functions of the trade context will be redirected to internal virtual placeholders, collecting information about every order opening, modification, or closure. The library will run this virtual trading for different parameter sets and find a best set. When optimization is off, all functions of the trade context will be redirected to the real trade context of the platform, and all orders will go to the market.   

 

Interface blueprint

For the start let us make a draft of the programming interface. It should contain public declarations of all classes and methods available for EA programmers. All the stuff will go to a header file (mqh), which should be included into EA. Then we'll develop internal implementation of the APIs in the library source file (mq4), and add an import directive for it into the header.

Which information do we need to control an optimization process from within EA? Here is the main points:

  • a simple way to enable/disable the library as a whole
  • history depth to use for virtual trading (ending datetime is always now)
  • how often to repeat optimization (for example, if optimization runs on 2 months history, it probably makes sense to update it every week)
  • initial deposit size (remember, some EAs prefer to take risks only on a part of account balance)
  • work spread (current spread can be inappropriate if optimization is performed on weekends)
  • optimization goal (which value to optimize: balance, profit factor, sharpe ratio, etc)
  • and finally, parameters - an array of named entities (inputs) with a range of values to probe, and increments
Let's put it in a class. I chose the name Optimystic for the library, so use it for the main class (and for the header file name as well).

class Optimystic
{
  public:
    virtual void setEnabled(const bool enabled) = 0;
    virtual void setHistoryDepth(const int size, const TIME_UNIT unit) = 0;
    virtual void setReoptimizationPeriod(const int size, const TIME_UNIT unit) = 0;
    virtual void setDeposit(const double deposit) = 0;
    virtual void setSpread(const int points = 0) = 0;

    virtual void setOptimizationCriterion(const PERFORMANCE_ESTIMATOR type) = 0;

    virtual void addParameter(const string name, const double min, const double max, const double step) = 0;
};

All the methods are pure virtual, because we're defining an abstract interface. If it wouldn't be so, then after the header is included into EA source code, compiler would give the error "function must have a body" for every non-pure virtual method. Compiler needs a complete implementation of every concrete method (function) or requires an import directive pointing to a library, where to get it from. But in MQL it's not possible to import classes. Libraries can only export plain old functions. This is why we need to introduce a so-called factory function, which can create an instance of our class and provide it for EA.

#import "Optimystic.ex4"
  Optimystic *createInstance();
#import

This "says" to compiler: this function will return a runtime object with specified virtual functions, but their implementation is not your bussiness at the moment.

But let's go back to the class. It uses 2 enumerations, which are not yet defined.

enum TIME_UNIT
{
  UNIT_BAR,
  UNIT_DAY
};

We can specify time ranges either in bars or in days.

enum PERFORMANCE_ESTIMATOR
{
  ESTIMATOR_DEFAULT = -1,
  ESTIMATOR_NET_PROFIT, // default
  ESTIMATOR_PROFIT_FACTOR,
  ESTIMATOR_SHARPE,
  ESTIMATOR_DRAWDOWN_PERCENT,
  ESTIMATOR_WIN_PERCENT,
  ESTIMATOR_AVERAGE_PROFIT,
  ESTIMATOR_TRADES_COUNT,
  ESTIMATOR_CUSTOM
};

These constants allow us to choose one of most popular optimization criteria. I think their names are self-explaining.

Since the parameters are not only added by EA, but also updated by the library, they seem also requiring a dedicated programming interface. Indeed, we want, for example, to read current values during optimization and to apply final optimal values after optimization. So add a class with all parameter properties and then bind it with Optimystic class.

class OptimysticParameter
{
  public:
    virtual string getName() const = 0;
    virtual double getMin() const = 0;
    virtual double getMax() const = 0;
    virtual double getStep() const = 0;
    virtual double getValue() const = 0;
    virtual double getBest() const = 0;
    virtual bool getEnabled() const = 0;
    virtual void setEnabled(const bool e) = 0;
};

The first 4 methods just returns the properties that we passed to the library via addParameter method. getValue is a current value, and getBest is an optimal value (if any). Also we should have an option to exclude (disable) specific parameter from optimization temporary. This will allow us to add all parameters to the library in a batch and then play with them without recompiling EA - just by switching inputs.

Remember, we need a way to access objects of the class OptimysticParameter. So add a pair of methods.

class Optimystic
{
  public:
    virtual OptimysticParameter *getParameter(const int i) const = 0;
    OptimysticParameter *operator[](int i) const
    {
      return getParameter(i);
    }

The method getParameter is also pure virtual (and its implementation is postponed for a while), but the helper overload of operator[] is defined inline using the former method.  

When optimization is made, it's usually important to know all performance indicators for the best parameter set. We can provide the following method for this: 

    virtual double getOptimalPerformance(PERFORMANCE_ESTIMATOR type = ESTIMATOR_DEFAULT) = 0;

Finally, let's add a method which will be an entry point for the library on every tick.

    virtual void onTick() = 0;

It should be called inside EA's OnTick event handler.

When the library gets control from EA via this onTick method, it should prepare trading context (no matter how - we'll come back to this a moment later), and then execute a callback method, which EA should provide, as we described above (in the section Interface Design). The method is going to be a part of a class, so let's outline one.

class OptimysticCallback
{
  protected:
    datetime TimeCurrent()
    {
      //...
    }
    
    double AccountBalance()
    {
      //...
    }
    
    int OrdersTotal()
    {
      //...
    }
    
    int OrderSend(...)
    {
      //...
    }
    
    // ...
    
  public:
    virtual void trade() = 0;
};

The class contains not only the callback trade, but also stubs for most of standard API functions. It's not yet important what will be inside them. The main point to note here, is that from the method trade's point of view, all standard names will lead to the protected substitutes.

Every surrogate method should call corresponding platform implementation during normal trading mode, and provide an alternative virtual counterpart for optimization. 

If you remember, we created OptimysticCallback in order to provide the callback method in EA, which would be called from the library. It means that we need to pass a reference to the object OptimysticCallback into the library somehow. The best solution is to add it as parameter into our factory function:

#import "Optimystic.ex4"
  Optimystic *createInstance(OptimysticCallback &callback);
#import

Interface refinement

To put it all together, let's imagine how EA code could look right now.

input int HistoryDepth = 500;
input int ReoptimizationPeriod = 100;

extern int MAPeriod = 10;

input int MAPeriodMin = 5;
input int MAPeriodMax = 25;
input int MAPeriodStep = 5;

#include <Optimystic.mqh>

class MyOptimysticCallback: public OptimysticCallback
{
  public:
    virtual void trade() // override
    {
      // some code analyzing iMA with MAPeriod
      // the code of this method was originally placed in EA's OnTick
      // ...
      OrderSend(...);
    }
};

MyOptimysticCallback myc; // instantiate MyOptimysticCallback object

Optimystic *p;

void OnInit()
{
  p = createInstance(GetPointer(myc));
  p.setHistoryDepth(HistoryDepth, UNIT_BAR);
  p.setReoptimizationPeriod(ReoptimizationPeriod, UNIT_BAR);
  p.addParameter("MAPeriod", MAPeriodMin, MAPeriodMax, MAPeriodStep);
}

void OnDeinit(const int reason)
{
  delete p;
}

void OnTick()
{
  p.onTick();
}

The control flow is normally as follows:

  1. EA creates a custom callback object derived from OptimysticCallback;
  2. EA creates an instance of Optimystic and passes the callback object to it;
  3. EA adjusts settings of the library; 
  4. On every tick EA calls the library via Optimystic::onTick method;
  5. The library does some work behind the scenes and calls MyOptimysticCallback::trade method once for current datetime and parameter set;
  6. EA trades using OrderSend inside MyOptimysticCallback::trade
  7. As far as the library is in real trading mode, OrderSend goes to the market;
During virtual optimization the flow changes on the steps 5-7:
  1. The library does some work behind the scenes and calls MyOptimysticCallback::trade method many times for different bars in past and different parameter sets;
  2. EA trades using OrderSend inside MyOptimysticCallback::trade
  3. As far as the library is in optimization mode, OrderSend performs virtual trades;
  4. The library calculates performance indicators for all trades and finds best parameter set;
  5. ...
Here we stopped because we're missing some stuff - specifically a method to apply new parameters to EA. Let's extend OptimysticCallback.

class OptimysticCallback
{
  public:
    virtual bool onBeforeOptimization() = 0;
    virtual bool onApplyParameters() = 0;
    virtual void trade(const int bar, const double Ask, const double Bid) = 0;
    virtual void onReadyParameters()
    {
    }
    virtual void onAfterOptimization() = 0;

Actually, we have added 4 methods at once, because they are logically interconnected. It's likely that EA will need to make some preparations before every optimization, this is why we introduced onBeforeOptimization. And we do already know what to place inside its concrete implementation:

bool MyOptimysticCallback::onBeforeOptimization()
{
  p.setDeposit(::AccountBalance());
  p.setSpread((int)MarketInfo(Symbol(), MODE_SPREAD));
  return true;
}

As you remember, we have reserved setDeposit and setSpread in Optimystic. The method onBeforeOptimization returns boolean to let the library know if EA is ready to start optimization: by returning false EA can deny pending optimization. 

The method onApplyParameters should be called by the library when a new parameter set is about to be tested. And again, we do already know what to place inside:

bool MyOptimysticCallback::onApplyParameters()
{
  MAPeriod = (int)p[0].getValue();
  return true;
}

As you remember, operator[] is overloaded for Optimystic to return specific parameter, and it returns an object OptimysticParameter. In turn, it provides current parameter value via getValue.

The method onReadyParameters is called by the library after single pass of optimization. This is the place, where EA can read current performance indicators:

void MyOptimysticCallback::onReadyParameters()
{
  double net = p.getOptimalPerformance(ESTIMATOR_NET_PROFIT);
  double pf = p.getOptimalPerformance(ESTIMATOR_PROFIT_FACTOR);
  double ddp = p.getOptimalPerformance(ESTIMATOR_DRAWDOWN_PERCENT);
  int n = (int)p.getOptimalPerformance(ESTIMATOR_TRADES_COUNT);
  Print("Performance: profit=", DoubleToString(net, 2), " PF=", (pf == DBL_MAX ? "n/a" : DoubleToString(pf, 2)), " DD%=", DoubleToString(ddp, 2), " N=", n);
}

Just as a brief reminder, the method getOptimalPerformance has been added to Optimystic a few paragraphs above. 

Finally, the method onAfterOptimization is the place where the best parameter set is already known and can be applied to EA.

void MyOptimysticCallback::onAfterOptimization()
{
  if(p.getOptimalPerformance(ESTIMATOR_CUSTOM) > 0) // if have positive result
  {
    MAPeriod = (int)p[0].getBest();
  }
}

So get back to the control flow and complement it:

1. EA creates a custom callback object derived from OptimysticCallback;
2. EA creates an instance of Optimystic and passes the callback object to it;
3. EA adjusts settings of the library; 
4. On a next tick EA calls the library via Optimystic::onTick method;
5. The library does some work behind the scenes and decides if it's time to start optimization according to the settings given in setReoptimizationPeriod
     if it's time:
6.1.   The library calls MyOptimysticCallback::trade method many times for different bars in past and different parameter sets;
6.2.   EA trades using OrderSend inside MyOptimysticCallback::trade
6.3.   As far as the library is in optimization mode, OrderSend performs virtual trades;
6.4.   The library calculates performance indicators for all trades and finds best parameter set;
6.5.   EA applies the best parameters for real trading and even "jumps" to the step 7.1;
     if it is not:
7.1.   The library calls MyOptimysticCallback::trade method once for current datetime and active parameter set;
7.2.   EA trades using OrderSend inside MyOptimysticCallback::trade
7.3.   As far as the library is in real trading mode, OrderSend (if it's actually called according to strategy signals) goes to the market;

What is the work behind the scenes that we mention all the time? This implies switching the trading context between real and virtual one. Most simple way to explain this is to look inside any wrapper function we have added into OptimysticCallback.

class OptimysticCallback
{
  protected:
    int OrdersTotal()
    {
      //...
    }

What should we write instead the ellipsis?

To get the answer, we need first to recall that we're still speaking about the header file with abstract interfaces. We should not write much in there. All fat implementation details should go to internals of the library, that is to mq4 source and its ex4 compiled release. This means that all routine and scrupulous work of keeping track of virtual orders, prices and other stuff, which is actually forming the simulated trade context, should be packed into another worker class. Let's name it OptimysticTradeContext and define its interface as the complete abstract reflection of protected methods from OptimysticCallback

class OptimysticTradeContext
{
  public:
    virtual datetime TimeCurrent() = 0;
    virtual double AccountBalance() = 0;
    
    //...
    
    virtual int OrdersTotal() = 0;
    virtual int OrderSend(string symbol, int cmd, double volume, double price, int deviation, double stoploss, double takeprofit, string comment = NULL, int magic = 0, datetime expiration = 0, color arrow = clrNONE) = 0;
    
    //...
};

This is a contract for concrete implemention of our simulated trade context (I mean we will someday create a class, derived from the interface and populate all its method with complete meaning). We should somehow acquire a pointer to such implementation in OptimysticCallback and use it every time when optimization mode is on. For the simplicity, we can use the pointer itself as a flag: if it's empty - real trading is on, and if it's assigned to a virtual implementation - optimization mode is on. 

class OptimysticCallback
{
  private:
    OptimysticTradeContext *context;

  protected:
    datetime TimeCurrent()
    {
      return context != NULL ? context.TimeCurrent() : ::TimeCurrent();
    }
    
    double AccountBalance()
    {
      return context != NULL ? context.AccountBalance() : ::AccountBalance();
    }
    
    // ...

  public:
    void setContext(OptimysticTradeContext *c)
    {
      context = c;
    }
    
    OptimysticTradeContext *getContext() const
    {
      return context;
    }
    
    // ...
};

The implementation of OptimysticTradeContext is hidden inside the library, and the library should call special setContext method to control current trade context. This is the work behind the scenes.

MetaTrader 4 expert adviser on the fly optimization: Optimystic library interface diagram 

At the moment we have discussed all classes/interfaces of the library. The code snippets in the blogpost do not include full definitions of the classes - they comprise much more methods wrapping standard functions from trade context, but you may find the complete file Optimystic.mqh attached below.

There is only one last touch that should be made upon the header. As you remember, we have the import directive which binds our factory method with ex4 library file. We can't leave it as is, because we're going to write implemetation for our classes, and the same header file should be included into the library source Optimystic.mq4 as well. But it can't import from itself. To solve the problem we add a macro definition:

#ifndef OPTIMYSTIC_LIBRARY
#import "Optimystic.ex4"
  Optimystic *createInstance(OptimysticCallback &callback);
#import
#endif

In EA source code OPTIMYSTIC_LIBRARY is undefined, so the library is imported. In our library surce code we'll define it as simple as:

#define OPTIMYSTIC_LIBRARY

#include <Optimystic.mqh>

and the header will be processed without an issue.

Implementation of the interfaces and example of EA with on the fly optimization will be studied in the part 2.

 

Table of contents 


Files:
In the part 1 we have developed a set of programming interfaces (abstract classes) constituting Optimystic library. In this part we'll implement all classes and embed the library into example EA.

The library source code resides in the file Optimystic.mq4, and it's currently almost empty: in the part 1 we've left it on:

#property library
#property strict

#define OPTIMYSTIC_LIBRARY

#include <Optimystic.mqh>

The only function that is exported from the library is the factory:

Optimystic *createInstance(OptimysticCallback &callback) export
{
  return NULL; // ???
}

It should return an object which implements Optimystic interface. Let's start coding it.

class OptimysticImplementation: public Optimystic
{
  private:
    OptimysticCallback *callback;
    
    bool enabled;
    int rangeInBars;
    int resetInBars;
    PERFORMANCE_ESTIMATOR estimator;
    double deposit;
    int spread;
    double commission;
    
  public:
    OptimysticImplementation(OptimysticCallback &cb)
    {
      callback = GetPointer(cb);
    }

    virtual void setEnabled(const bool _enabled)
    {
      enabled = _enabled;
    }

    virtual OptimysticParameter * addParameter(const string name, const double min, const double max, const double step)
    {
      return NULL;
    }

    virtual OptimysticParameter *getParameter(const int i) const
    {
      return NULL;
    }
    
    virtual void setHistoryDepth(const int size, const TIME_UNIT unit)
    {
      if(unit == UNIT_DAY)
      {
        rangeInBars = size * (PeriodSeconds(PERIOD_D1) / PeriodSeconds());
      }
      else
      {
        rangeInBars = size;
      }
    }
    
    virtual void setReoptimizationPeriod(const int size, const TIME_UNIT unit)
    {
      if(unit == UNIT_DAY)
      {
        resetInBars = size * (PeriodSeconds(PERIOD_D1) / PeriodSeconds());
      }
      else
      {
        resetInBars = size;
      }
    }
    
    virtual void setOptimizationCriterion(const PERFORMANCE_ESTIMATOR type)
    {
      estimator = type;
    }

    virtual void setDeposit(const double d)
    {
      deposit = d;
    }
    
    virtual void setSpread(const int points = 0)
    {
      spread = points;
    }
    
    virtual void setCommission(const double c)
    {
      commission = c;
    }
    
    virtual void onTick()
    {
    }
    
    virtual double getOptimalPerformance(PERFORMANCE_ESTIMATOR type = ESTIMATOR_DEFAULT)
    {
      return 0;
    }
};

We're currently missing another class - a class with an implementation of OptimysticParameter. Let's code it as well.

class OptimysticParameterImplementation: public OptimysticParameter
{
  protected:
    string name;
    double min;
    double max;
    double step;
    double value;
    double best;
    bool enabled;
  
  public:
    OptimysticParameterImplementation(const string _name, const double _min, const double _max, const double _step)
    {
      name = _name;
      min = MathMin(_min, _max);
      max = MathMax(_max, _min);
      enabled = true;
      step = _step <= 0 ? 1 : _step;
      best = EMPTY_VALUE;
    }
    
    void setValue(const double v)
    {
      value = v;
    }
    
    void increment()
    {
      value += step;
    }
    
    void markAsBest()
    {
      best = value;
    }

    virtual string getName() const
    {
      return name;
    }
    virtual double getMin() const
    {
      return min;
    }
    virtual double getMax() const
    {
      return max;
    }
    virtual double getStep() const
    {
      return step;
    }
    virtual double getValue() const
    {
      return value;
    }
    virtual double getBest() const
    {
      return best;
    }
    virtual bool getEnabled() const
    {
      return enabled;
    }
    virtual void setEnabled(const bool e)
    {
      enabled = e;
    }
};

To store array of parameters inside OptimysticImplementation I'll use helper class RubbArray (it was described in the blog ealier, but also attached below).

class OptimysticImplementation: public Optimystic
{
  private:
    RubbArray<OptimysticParameterImplementation> parameters;
    int parameterSpace[];
    int parameterCursor[];
    double best;

Now we can complete implementation of the methods addParameter and getParameter:

    virtual OptimysticParameter * addParameter(const string name, const double min, const double max, const double step)
    {
      parameters << new OptimysticParameterImplementation(name, min, max, step);
      return parameters.top();
    }

    virtual OptimysticParameter *getParameter(const int i) const
    {
      return parameters[i];
    }

Now we can focus on the main method onTick. We can't write it at once, so start with a schematic draft. 

    virtual void onTick()
    {
      static datetime lastBar;
      if(enabled)
      {
        if(lastBar != Time[0] && TimeCurrent() > lastOptimization + PeriodSeconds() * resetInBars)
        {
          callback.setContext(context); // enable virtual trading
          optimize();
        }
      }
      callback.setContext(NULL);   // enable real trading
      callback.trade(0, Ask, Bid); // actual trading
      lastBar = Time[0];
    }

This will not compile, because some things here are undefined yet. For example, we need a private variable lastOptimization in the class to store a time of last optimization. We will assign it in the private method optimize, which should be also added to perform actual work. And we don't yet know what is the context. Let's fix the first 2 points:

  private:
    datetime lastOptimization;

  protected:
    bool optimize()
    {
      // ...
      lastOptimization = TimeCurrent();
      return true;
    }

As for the context, it should be a variable with implementation of the interface OptimysticTradeContext, which is left the only unimplemented one.

class OptimysticTradeContextImplementation: public OptimysticTradeContext
{
  // ...
};

It must contain a lot of methods. You can just copy and paste the declaration of the interface into the class and code all methods scrupulously, one by one. Here, in the blog, it's not possible to cover all the changes, so please consult with the attached source code to get the final picture.

One important thing which should be noted regarding implementation of any abstract interface is that it does normally require (a lot of) underlying (hidden) code. In this case, we'are implementing a back-testing engine, where current time is changing according to our own rules. This is why we need a variable holding a bar in past, which looks like current time for an EA being optimized. And we need a method to change the time.

class OptimysticTradeContextImplementation: public OptimysticTradeContext
{
  private:
    int bar;

  public:
    OptimysticTradeContextImplementation()
    {
    }
    
    void setBar(const int b)
    {
      bar = b;
    }

If you remember, we have a method optimize in OptimysticImplementation. This is where we place a cycle through bars on backtest period, and it will call setBar. Something like this:

class OptimysticImplementation: public Optimystic
{
  protected:
    bool optimize()
    {
      if(!callback.onBeforeOptimization()) return false;
      Print("Starting on-the-fly optimization at ", TimeToString(Time[rangeInBars]));
      
      if(spread == 0) spread = (int)MarketInfo(Symbol(), MODE_SPREAD);
      
      /* TODO: init parameters */
      do
      {
        if(!callback.onApplyParameters()) return false;
        
        // build balance and equity curves on the fly
        for(int i = rangeInBars; i >= 0; i--)
        {
          context.setBar(i);
          
          callback.trade(i, iOpen(Symbol(), Period(), i) + spread * Point, iOpen(Symbol(), Period(), i));
        }
        
        // TODO: calculate performance
        
        callback.onReadyParameters();
      }
      while(/* TODO: loop through possible parameter combinations */);

      callback.onAfterOptimization();
      
      lastOptimization = TimeCurrent();
      return true;
    }

Having the current bar in the context we can implement specific methods, for example, TimeCurrent:

class OptimysticTradeContextImplementation: public OptimysticTradeContext
{
  public:
    virtual datetime TimeCurrent()
    {
      return iTime(::Symbol(), ::Period(), bar);
    }

This is the first method of the abstract interface OptimysticTradeContext which got a concrete implementation in OptimysticTradeContextImplementation. Some of the other methods can be implemented in the similar way:

    virtual double iOpen(string symbol, int tf, int offset)
    {
      int b = bar + offset;
      if(symbol != Symbol())
      {
        b = ::iBarShift(symbol, tf, Time[b]);
      }
      return ::iOpen(symbol, tf, b);
    }

But the others require additional work to be done. For example, before we can create a new order with OrderSend we surely need to define a special class.

class Order
{
  private:
    static int tickets;
    
  public:
    
    int type;
    string symbol;
    double openPrice;
    datetime openTime;
    double lots;
    double takeProfit;
    double stopLoss;
    string comment;
    datetime expiration;
    int magicNumber;
    color arrow;

    int ticket;

    double closePrice;
    datetime closeTime;

    
    Order(datetime now, string s, int cmd, double volume, double price, int deviation, double stoploss, double takeprofit, string cmnt = NULL, int magic = 0, datetime exp = 0, color clr = clrNONE)
    {
      symbol = s;
      type = cmd;
      lots = volume;
      openPrice = price;
      openTime = now;
      stopLoss = stoploss;
      takeProfit = takeprofit;
      comment = cmnt;
      magicNumber = magic;
      expiration = exp;
      arrow = clr;
      ticket = ++tickets;
    }

The context should maintain an array of all market and history orders. So let's add them using RubbArray again:

class OptimysticTradeContextImplementation: public OptimysticTradeContext
{
  private:
    RubbArray<Order> orders;
    RubbArray<Order> history;

And implement OrderSend:

    virtual int OrderSend(string symbol, int cmd, double volume, double price, int deviation, double stoploss, double takeprofit, string comment = NULL, int magic = 0, datetime expiration = 0, color arrow = clrNONE)
    {
      Order *order = new Order(TimeCurrent(), symbol, cmd, volume, price, deviation, stoploss, takeprofit, comment, magic, expiration, arrow);
      orders << order;
      return order.ticket;
    }

To close an order we could code something like this:

    virtual bool OrderClose(int ticket, double lots, double price, int slippage, color arrow = clrNONE)
    {
      Order *order = findByTicket(ticket, orders);
      if(order != NULL && (order.type == OP_BUY || order.type == OP_SELL))
      {
        orders >> ticket; // remove from open orders
        history << order; // and add it to the history

        order.closePrice = price;
        order.closeTime = TimeCurrent();

        // TODO: calculate profit and other statistics
        
        return true;
      }
      return false;
    }

We use new private helper here - findByTicket. It's code is more or less obvious:

    Order * findByTicket(int &ticket, RubbArray<Order> &array)
    {
      int n = array.size();
      for(int i = 0; i < n; i++)
      {
        Order *order = array[i];
        if(order.ticket == ticket)
        {
          ticket = i;
          return order;
        }
      }
      return NULL;
    }

The calculation of order's profit (that shown as TODO in OrderClose) can be coded inside the Order class itself:

class Order
{
  public:
    double profit(double price, const int spread)
    {
      if(type == OP_BUY || type == OP_SELL)
      {
        if(closePrice != 0) price = closePrice;
        
        double ptv = MarketInfo(symbol, MODE_POINT) * MarketInfo(symbol, MODE_TICKVALUE) / MarketInfo(symbol, MODE_TICKSIZE);
        double pts = (type == OP_BUY ? +1 : -1) * (price - openPrice) / MarketInfo(symbol, MODE_POINT);
        
        return (pts - spread) * ptv * lots;
      }
      return 0;
    }

Now we can calculate profit in OrderClose. The profit should update balance curve, which we don't have yet. Actually we need to collect many more statistics about trading, such as order counter, equity curve, etc. Let's add them to the context:

class OptimysticTradeContextImplementation: public OptimysticTradeContext
{
  private:
    RubbArray<BarState> income;
    double profit;
    double plusProfit;
    double minusProfit;
    int plusCount;
    int minusCount;

Unfortunately we use yet another new class here - BatState. The good news is that this is the last class in our implementation. And it's simple.

class BarState
{
  public:
    datetime timestamp;
    double balance;
    double equity;
    
    BarState(const datetime dt, double b): timestamp(dt), balance(b), equity(b) {}
    BarState(const datetime dt, double b, double e): timestamp(dt), balance(b), equity(e) {}
};

Get back to the context, and we can finally complete OrderClose (only profit-related part is shown):

        double amount = order.profit(price, spread); // TODO: + order.swap(order.closeTime) + order.lots * commission;
        income.top().balance += amount;
        
        if(amount > 0)
        {
          plusProfit += amount;
          plusCount++;
        }
        else
        {
          minusProfit -= amount;
          minusCount++;
        }

You may see how we update balance and the counters on current bar. Of course, they should be initalized somewhere. So we need to add such a method in the context:

class OptimysticTradeContextImplementation: public OptimysticTradeContext
{
  public:
    void initialize(const double d, const int s, const double c)
    {
      deposit = d;
      spread = s;
      commission = c;
      plusProfit = minusProfit = 0;
      plusCount = minusCount = 0;
      income.clear();
      income << new BarState(TimeCurrent(), deposit);
    }

And it should be called from OptimysticImplementation::optimize method before we run the cycle through history bars. All input values are already known there.

        ...
        if(!callback.onApplyParameters()) return false;
        context.initialize(deposit, spread, commission);
        // build balance and equity curves on the fly
        for(int i = rangeInBars; i >= 0; i--)
        {
          context.setBar(i);
          callback.trade(i, iOpen(Symbol(), Period(), i) + spread * Point, iOpen(Symbol(), Period(), i));
          context.activatePendingOrdersAndStops();
          context.calculateEquity();
        }
        context.closeAll();

        // calculate performance
        context.calculateStats();
        callback.onReadyParameters();

In addition to initialize you see a bunch of other new methods called on the context. I hope their names are self-explaining. We need to implement them all, but let us skip details here - you may consult with the source code. The only thing which can fit is a note about income array. We need to add new element into it on every bar, this is why we extend the function setBar a bit:

    void setBar(const int b)
    {
      bar = b;
      income << new BarState(TimeCurrent(), income.top().balance);
    }

This code simply copies last known balance to the new bar, and if some orders will be closed (as you saw in OrderClose), their profit will update the balance.

BTW, we need to implement AccountBalance method in the context (if you remember), and since we have income array, this can be done easily:

    virtual double AccountBalance()
    {
      return income.size() > 0 ? income.top().balance : 0;
    }

As soon as we have total profit and other stats after the loop through the bars, the context can calculate performance indicators. So let's add a method which return them.

    double getPerformance(const PERFORMANCE_ESTIMATOR type = ESTIMATOR_DEFAULT)
    {
      if(type == ESTIMATOR_PROFIT_FACTOR)
      {
        return minusProfit != 0 ? plusProfit / minusProfit : DBL_MAX;
      }
      else if(type == ESTIMATOR_WIN_PERCENT)
      {
        return plusCount + minusCount != 0 ? 100.0 * plusCount / (plusCount + minusCount) : 0;
      }
      else if(type == ESTIMATOR_AVERAGE_PROFIT)
      {
        return plusCount + minusCount != 0 ? AccountBalance() / (plusCount + minusCount) : 0;
      }
      else if(type == ESTIMATOR_TRADES_COUNT)
      {
        return plusCount + minusCount;
      }
      
      // default: (type == ESTIMATOR_DEFAULT || type == ESTIMATOR_NET_PROFIT)
      return AccountBalance() - deposit;
    }

This method can be used to provide implementation for Optimystic::getOptimalPerformance, which is very important for the client EA.

With the help of getPerformance optimization process (in OptimysticImplementation::optimize) can be refined further. 

      best = 0;
      /* TODO: resetParameters(); */
      do
      {
        // loop through bars goes here
        ...
        // calculate performance (please find details in the sources)
        context.calculateStats();
        
        double gain = context.getPerformance(estimator);
        
        // if estimator is better than previous, then save current parameters
        if(gain > best && context.getPerformance(ESTIMATOR_TRADES_COUNT) > 0)
        {
          best = gain;
          /* TODO: storeBestParameters(); call markAsBest for every parameter */
        }
      }
      while(/* TODO: iterateParameters() */);

All calls related to the parameter set enumeration and selection are still marked as TODO. Due to the lack of space their implementation is not covered here and can be studied in the source code.

And that's actually all. The library is ready. It works.

MetaTrader 4 expert adviser on the fly optimization: Optimystic library class diagram 

Nevertheless, the presented version is very minimal and mostly demonstrates OOP power in such complex tasks. The limitations include (but not limited to):
  • incomplete multicurrency support: specifically we need to have separate spread values for every symbol;
  • no swaps calculation; 
  • no fast optimization by means of genetics or other algorithm (so don't specify too many combinations of parameters to check, because straightforward approach is currently used, and it's slow);
  • no validation of function parameters and error generation (such as, invalid stops);
  • tick values are taken for current exchange rates, whereas they should be recalculated using historic quotes;
  • rough price simulation mode (by open prices);
Also please note that our trade context interface does not provide wrappers for indicator functions, such as iMA, iMACD, iCustom, etc. That does not mean that we can't use indicators, but we need to adjust last parameter in every indicator call made in EA (shifting it to a given bar in past). And this is the reason why we do actually pass current bar number to the method OptimysticCallback::trade. Probably you have already noticed this. An example of EA with indicators will follow. Other 2 paramaters of the method trade reproduce current Ask and Bid prices as prices for i-th bar. They are defined in the platform as special variables, this is why the extensively used so far approach with interface methods is not suited.


Expert Adviser Example

Let's embed the library into example EA - MACD Sample from MetaTrader install. The file is attached at the bottom of the page.

After you add the include:

#include <Optimystic.mqh>

Declare your own class derived from OptimysticCallback:

class MyOptimysticCallback: public OptimysticCallback
{
  private:
    void print(const string title);

  public:
    virtual bool onBeforeOptimization();
    virtual bool onApplyParameters();
    virtual void trade(const int bar, const double Ask, const double Bid);
    virtual void onReadyParameters();
    virtual void onAfterOptimization();
};

and implement all the methods. Part of them was already described in the part 1. Most interesting is that code for the method trade should be taken completely from the former OnTick event handler. The lines that invoke indicators should be changed to take specific bar number passed via the first parameter:

void MyOptimysticCallback::trade(const int bar, const double Ask, const double Bid)
{
  double MacdCurrent, MacdPrevious;
  double SignalCurrent, SignalPrevious;
  double MaCurrent, MaPrevious;

  MacdCurrent = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, bar + 0);
  MacdPrevious = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, bar + 1);
  SignalCurrent = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, bar + 0);
  SignalPrevious = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, bar + 1);
  MaCurrent = iMA(NULL, 0, MATrendPeriod, 0, MODE_EMA, PRICE_CLOSE, bar + 0);
  MaPrevious = iMA(NULL, 0, MATrendPeriod, 0, MODE_EMA, PRICE_CLOSE, bar + 1);

All other calls made in the source to seemingly standard functions are intercepted by OptimysticCallback and redirected to appropriate backend: either the platfrom or our virtual trade context. And even Ask and Bid are replaced with correct values for the bar bar

Another nuance added into the example EA in relation to optimization is automatic disabling of trading for cases when optimal parameter set is not found (specified performance estimator is not positive).

bool TradeEnabled = true;

void MyOptimysticCallback::onAfterOptimization()
{
  if(p.getOptimalPerformance(Estimator) > 0)
  {
    // apply new parameters for real trading
    MACDOpenLevel = p[0].getBest();
    MACDCloseLevel = p[1].getBest();
    MATrendPeriod = (int)p[2].getBest();
    Print("Best parameters: ", (string)MACDOpenLevel, " ", (string)MACDCloseLevel, " ", (string)MATrendPeriod);
    TradeEnabled = true;
  }
  else
  {
    Print("No optimum");
    TradeEnabled = false;
  }
}

Then in the method trade:

void MyOptimysticCallback::trade(const int bar, const double Ask, const double Bid)
{
  // ...
  
  int cnt, ticket, total;

  total = OrdersTotal();
  if(total < 1 && ((getContext() != NULL) || TradeEnabled))
  {

Hence, new orders can be opened only if virtual context is not NULL (that is in optimization mode) or an applicable parameter set exists (TradeEnabled is true in real trading mode). 

Of course, this flag is automatically selected after every optimization batch, that is after trading was temporary disabled, it can be restored automaticaly as soon as suitable parameter set is found during next optimization.

Please, note, that since we need to change input parameters from MQL code they should be defined as extern instead of input. Also when on the fly optimization is enabled, input parameters which undergo optimization specify minimal value and step for optimization (they are the same for simplicity), and their maximal value is 10 times larger the minimal value. As a result, if you set 3 parameters for optimization, they will make 1000 combinations.

And here is the final warning: the MACD Sample was taken as example due to its simplicity and availability, but the EA itself is not very good (for example, stoploss is applied on orders only after they go in sufficient profit, so if price moves in unfavourable direction from very beginning, you'll get large loss), as a result you should not expect much from the EA performance. Try the library with your own EAs.

 

I'm sure there is still much to discuss on the subject, but the blog post can't last for ever, even if it's a very very long story. You may explore the library yourself and try MQL OOP in the action.

 

Table of contents 


Files:
macd.mq4 9 kb
In 2 previous chapters we have studied a library for on the fly optimization of expert advisers (part1, part2). One of drawbacks of the library is its slow method of optimization, because it's nothing else than straightforward iteration over all possible combinations of parameters' values. It would be great to speed up the process somehow.

There is a number of methods of fast optimization. One of them is genetic optimization, which is used by the tester in MetaTrader. The other one is simulated annealing. And yet another one is particle swarm optimization. This method is relatively simple to implement. Let's code it in MQL.

The main idea of the method is to generate many particels and spread them over the space of EA parameters. Then the particles will move and adjust their velocity in the space according to EA performance calculated in corresponding points, and the process is repeated many times. You may see exact formulae and pseudo-code on the abovementioned reference page.

Particle Swarm Optimization Pseudo-Code

According to the algorithm every particle should have current position, velocity, and memory about its best position in past. The "best" means that EA demonstarted highest (known to the particle) performance at those position (parameter set). Let's put this in a class.

class Particle
{
  public:
    double position[];    // current point
    double best[];        // best point known to the particle
    double velocity[];    // current speed
    
    double positionValue; // EA performance in current point
    double bestValue;     // EA performance in the best point
    
    Particle(const int params)
    {
      ArrayResize(position, params);
      ArrayResize(best, params);
      ArrayResize(velocity, params);
      bestValue = -DBL_MAX;
    }
};

All arrays have size of parameter space dimension, that is the number of parameters being optimized. As far as our optimization implies that better performance is denoted by higher values we initialize bestValue with most minimal possible value -DBL_MAX. As you remember, EA performance can hold any of main trade performance indicators such as profit, profit factor, sharpe ratio and etc.

I made the member arrays and variables public for the sake of efficiency. Strict OO principles would require us to hide them in private part and provide methods-accessors, but this will also impose some performance overheads (and performance here means simply the code execution time). This deviation from the good OO style is possible as it's a small project where encapsulation and access control are not of so great importance. Please note that in other OOP languages there are means that would allow us to keep both efficiency and encapsulation. For example, Java respects access modifier for nested classes (MQL does not), so we could define Particle inside a container class with private modifier and then use particle's member variables directly inside the container yet hide them from other classes (hence prevent intentional or accidental data mangling). In C++ we could make the member variables private but declare other classes using particles as friends, and hence allow them to access the variables directly.    

In addition to single particles the algorithm utilizes so called topologies or subsets of particles. They can vary, but we will use topology which simulates social groups. The group of particles will hold knowledge about best known position among all particles in the group.

class Group
{
  private:
    double result;    // best EA performance in the group
  
  public:
    double optimum[]; // best known position in the group
    
    Group(const int params)
    {
      ArrayResize(optimum, params);
      ArrayInitialize(optimum, 0);
      result = -DBL_MAX;
    }
    
    void assign(const double x)
    {
      result = x;
    }
    
    double getResult() const
    {
      return result;
    }
    
    bool isAssigned()
    {
      return result != -DBL_MAX;
    }
};

Now we can start implementing the swarm algorithm itself. Of course, we pack it in a class.

class Swarm
{
  private:
    Particle *particles[];
    Group *groups[];
    int _size;             // number of particles
    int _globals;          // number of groups
    int _params;           // number of parameters to optimize

Here we have a swarm of particles and a set of groups.

For every parameter we need to set a range of values to probe and increment.

    double highs[];
    double lows[];
    double steps[];

In addition we want to save optimal parameter set somewhere.

    double solution[];

Finally we need an index array that would mark every particle as a member of specific social group.

    static int particle2group[];

The static array should be instantiated after the class definition.

static int Swarm::particle2group[];

As far as we will probably have several different constructors of the swarm, let's define a unified method for its initialization.

    void init(const int size, const int globals, const int params, const double &max[], const double &min[], const double &step[])
    {
      _size = size;
      _globals = globals;
      _params = params;

      ArrayResize(particles, size);
      for(int i = 0; i < size; i++)
      {
        particles[i] = new Particle(params);
      }
      
      ArrayResize(groups, globals);
      for(int i = 0; i < globals; i++)
      {
        groups[i] = new Group(params);
      }
      
      ArrayResize(particle2group, size);
      for(int i = 0; i < size; i++)
      {
        particle2group[i] = (int)MathMin(MathRand() * 1.0 / 32767 * globals, globals - 1);
      }
      
      ArrayResize(highs, params);
      ArrayResize(lows, params);
      ArrayResize(steps, params);
      for(int i = 0; i < params; i++)
      {
        highs[i] = max[i];
        lows[i] = min[i];
        steps[i] = step[i];
      }
      
      ArrayResize(solution, params);
    }

All arrays are resized to given dimensions and filled with provided data. Group membership is initialized randomly.

Now we're ready to implement a couple of constructors with the help of init.

  public:
    Swarm(const int params, const double &max[], const double &min[], const double &step[])
    {
      init(params * 5, (int)MathSqrt(params * 5), params, max, min, step);
    }
    
    Swarm(const int size, const int globals, const int params, const double &max[], const double &min[], const double &step[])
    {
      init(size, globals, params, max, min, step);
    }

The first one uses some empirical rules to deduce size of the swarm and number of the groups from the number of parameters. The second allows you to specify all values explicitly.

Desctructor should free up particles and groups created by constructors.

    ~Swarm()
    {
      for(int i = 0; i < _size; i++)
      {
        delete particles[i];
      }
      for(int i = 0; i < _globals; i++)
      {
        delete groups[i];
      }
    }

And this is the moment when the main method of the class should be coded - the method performing optimization itself.

    double optimize(Functor &f, const int cycles, const double inertia = 0.8, const double selfBoost = 0.4, const double groupBoost = 0.4)
    {

Let's skip the first parameter for a while. The second one is a number of cycles which the algorithm will run. The next 3 arguments are parameters of the swarm algorithm: inertia is a coefficient to preserve particle's velocity (usually fading out, as you see from the default value 0.8), selfBoost and groupBoost define how sensitive a particle will be to requests for changing its movement in direction to best known position of the particle and its group respectively.

Now back to the parameter Functor &f. As you can imagine, during the optimization process we're going to call EA for different parameter sets and get a scalar value as result (which can be profit, profit factor or any other characteristic). Generally speaking the algorithm should not know anything about our EA. Its task is optimization of an abstract function (objective function). For that puspose we need a way to call this function many times with selected parameters. The best suited approach here is an abstract interface (or a class with pure virtual method in MQL notation). Let's name it Functor.

class Functor
{
  public:
    virtual double calculate(const double &vector[]) = 0;
};

The method accepts an array with parameters and returns a single double value. In future we can implement a derived class in our EA, and perform virtual back-testing inside the method calculate.

So the first parameter of the method optimize will accept an object with the callback method calculate. Now when all parameters are described we can concentrate on the optimization algorithm.

First initialize result (objective function value) and coordinates in the parameter space.

      double result = -DBL_MAX;
      ArrayInitialize(solution, 0);

Second, initialize particles. Their positions and velocities are selected randomly, and then we call the functor for every particle and save best values into groups and global solution.

      for(int i = 0; i < _size; i++)     // loop through particles
      {
        for(int p = 0; p < _params; p++) // loop through all dimesions
        {
          // random placement
          particles[i].position[p] = (MathRand() * 1.0 / 32767) * (highs[p] - lows[p]) + lows[p];
          
          // the only position is the best so far
          particles[i].best[p] = particles[i].position[p];
          
          // random speed
          particles[i].velocity[p] = (MathRand() * 1.0 / 32767) * 2 * (highs[p] - lows[p]) - (highs[p] - lows[p]);
        }
        
        // get the function value in the point i
        particles[i].positionValue = f.calculate(particles[i].position);
        
        // update best value of the group (if improvement is found)
        if(!groups[particle2group[i]].isAssigned() || particles[i].positionValue > groups[particle2group[i]].getResult())
        {
          groups[particle2group[i]].assign(particles[i].positionValue);
          for(int p = 0; p < _params; p++)
          {
            groups[particle2group[i]].optimum[p] = particles[i].position[p];
          }
          
          // update global solution (if improvement is found)
          if(particles[i].positionValue > result) // global maximum
          {
            result = particles[i].positionValue;
            for(int p = 0; p < _params; p++)
            {
              solution[p] = particles[i].position[p];
            }
          }
        }
      }

Then we repeat the cycle of optimization (it looks a bit lengthy but reproduces the pseudo-code almost exactly):

      for(int c = 0; c < cycles && !IsStopped(); c++)   // predefined number of cycles
      {
        for(int i = 0; i < _size && !IsStopped(); i++)  // loop through all particles
        {
          for(int p = 0; p < _params; p++)              // update particle position and speed
          {
            double r1 = MathRand() * 1.0 / 32767;
            double rg = MathRand() * 1.0 / 32767;
            particles[i].velocity[p] = inertia * particles[i].velocity[p] + selfBoost * r1 * (particles[i].best[p] - particles[i].position[p]) + groupBoost * rg * (groups[particle2group[i]].optimum[p] - particles[i].position[p]);
            particles[i].position[p] = particles[i].position[p] + particles[i].velocity[p];
            
            // make sure to keep the particle inside the boundaries of parameter space
            if(particles[i].position[p] < lows[p]) particles[i].position[p] = lows[p];
            else if(particles[i].position[p] > highs[p]) particles[i].position[p] = highs[p];
          }

          // get the function value for the particle i
          particles[i].positionValue = f.calculate(particles[i].position);

          // update the particle's best value and position (if improvement is found)          
          if(particles[i].positionValue > particles[i].bestValue)
          {
            particles[i].bestValue = particles[i].positionValue;
            for(int p = 0; p < _params; p++)
            {
              particles[i].best[p] = particles[i].position[p];
            }
          }
    
          // update the group's best value and position (if improvement is found)          
          if(particles[i].positionValue > groups[particle2group[i]].getResult())
          {
            groups[particle2group[i]].assign(particles[i].positionValue);
            for(int p = 0; p < _params; p++)
            {
              groups[particle2group[i]].optimum[p] = particles[i].position[p];
            }
            
            // update the global maximum value and solution (if improvement is found)          
            if(particles[i].positionValue > result)
            {
              result = particles[i].positionValue;
              for(int p = 0; p < _params; p++)
              {
                solution[p] = particles[i].position[p];
              }
            }
          }
        }
      }

That's all. To finalize the method we should return the result.

      return result;
    }

To read found solution (parameter set) from outer world we need another method.

    bool getSolution(double &result[])
    {
      ArrayResize(result, _params);
      for(int p = 0; p < _params; p++)
      {
        result[p] = solution[p];
      }
      return !IsStopped();
    }

Is it really all? It's too simple to be true. ;-) Actually we have a problem here.

Our implementation of the swarm algorithm is currently close to classical implementation suitable for functions with continuous parameters. But EA parameters are usually granular. Normally, we can't afford MA with period 11.5. This is why in both places where we assign new values to particle's position, we need to utilize step sizes.

          // if step size is specified, make sure new position is aligned with the step granularity
          if(steps[p] != 0)
          {
            particles[i].position[p] = ((int)MathRound(particles[i].position[p] / steps[p])) * steps[p];
          }

This solves the problem only partially. The functor will be executed with proper parameters, but there is no guarantee that algorithm will not call it many times with the same values (due to the rounding by the step sizes). To prevent this we need to learn to identify somehow every set of values and skip repetitive calls.

Parameter sets are just numbers, or a sequence of bytes. The best known approach to verify uniqueness of a sequences of bytes is a hash. And one of most popular hash functions is CRC. CRC is a single number generated from provided data in such a way that its equality with same value generated for another data means that both sources match with almost 100% probability (exact number depends from the length of CRC). 64-bit CRC implementation can be easily ported to MQL from C. You may find the file crc64.mqh attached below. The worker function has the following prototype.

ulong crc64(ulong crc, const uchar &s[], int l);

It accepts CRC of a previous block of data (if any, can be 0 if only one block of data is processed; allows you to chain many blocks), the array of bytes to process and its length. The function returns 64-bit CRC.

How can we pass a parameter set into this function? Every parameter is a double number. To convert it to byte array we will use 3-rd party library TypeToBytes (the file TypeToBytes.mqh is attached at the bottom of the page).

Having this library included, we can write the following helper function to get CRC64 for an array of parameters:  

#include <TypeToBytes.mqh>
#include <crc64.mqh>

template<typename T>
ulong crc64(const T &array[])
{
  ulong crc = 0;
  int len = ArraySize(array);
  for(int i = 0; i < len; i++)
  {
    crc = crc64(crc, _R(array[i]).Bytes, sizeof(T));
  }
  return crc;
}

The next question is where to store the hashes and how to check them against every new parameter set generated by the swarm. The answer is binary tree. This is a data structure which provides very fast operations for adding new values and checking for existing ones. Actually the fastness relies on the tree property called balance. In other words the tree should be balanced to ensure the most possible quick operations. Fortunately, the fact that we'll store CRC hashes in the tree plays into our hands. Here is a short definition of a hash.

A hash function should, insofar possible, generate for any set of inputs, a set of outputs that is uniformly distributed over its output space.

As a result, adding hashes into the binary tree makes it balanced, and automatically most efficient.

The binary tree is basically a set of nodes, where each node contains a value and two optional links to other nodes, namely left and right. The value in the left node is always less than value in the parent's and value in the right node is always larger then the parent's. We start adding values from the root, and compare it with node value. If it's equal, the value is already in the tree and we have nothing to do except for returning some flag indicating the existance. If new value is less than current node value we move to the left node and continue process there in its subtree. If new value is larger, we go to the rigth subtree. In case corresponding subtree is missing (the poiner is null), the search is fininshed without result and new node should be added as this subtree with the new value.

To implement this logic we need 2 classes: the node and the tree. It makes sense to use templates for both.

template<typename T>
class TreeNode
{
  private:
    TreeNode *left;
    TreeNode *right;
    T value;

  public:
    TreeNode(T t)
    {
      value = t;
    }

    // adds new value into subtrees and returns false or
    // returns true if t exists as value of this node or in subtrees    
    bool Add(T t)
    {
      if(t < value)
      {
        if(left == NULL)
        {
          left = new TreeNode<T>(t);
          return false;
        }
        else
        {
          return left.Add(t);
        }
      }
      else if(t > value)
      {
        if(right == NULL)
        {
          right = new TreeNode<T>(t);
          return false;
        }
        else
        {
          return right.Add(t);
        }
      }
      return true;
    }
    
    ~TreeNode()
    {
      if(left != NULL) delete left;
      if(right != NULL) delete right;
    }
};
  
template<typename T>
class BinaryTree
{
  private:
    TreeNode<T> *root;
    
  public:
    bool Add(T t)
    {
      if(root == NULL)
      {
        root = new TreeNode<T>(t);
        return false;
      }
      else
      {
        return root.Add(t);
      }
    }
    
    ~BinaryTree()
    {
      if(root != NULL) delete root;
    }
};

I hope the algorithm is clear enough. Please note that deletion of root in the destructor will be automatically unwound to deletion of all subsidiary nodes. 

Now we can add the BinaryTree into the Swarm.

class Swarm
{
  private:
    BinaryTree<ulong> index;

In the method optimize we should elaborate those parts where we move particles to new positions.

      // ...
      
      double next[];
      ArrayResize(next, _params);

      for(int c = 0; c < cycles && !IsStopped(); c++)
      {
        for(int i = 0; i < _size && !IsStopped(); i++)
        {
          bool alreadyProcessed;
          
          // new placement of particles using temporary array next
          for(int p = 0; p < _params; p++)
          {
            double r1 = MathRand() * 1.0 / 32767;
            double rg = MathRand() * 1.0 / 32767;
            particles[i].velocity[p] = inertia * particles[i].velocity[p] + selfBoost * r1 * (particles[i].best[p] - particles[i].position[p]) + groupBoost * rg * (groups[particle2group[i]].optimum[p] - particles[i].position[p]);
            next[p] = particles[i].position[p] + particles[i].velocity[p];
            if(next[p] < lows[p]) next[p] = lows[p];
            else if(next[p] > highs[p]) next[p] = highs[p];
            if(steps[p] != 0)
            {
              next[p] = ((int)MathRound(next[p] / steps[p])) * steps[p];
            }
          }

          // check if the tree contains this parameter set and add it if not
          alreadyProcessed = index.Add(crc64(next));
          
          if(alreadyProcessed)
          {
            continue;
          }

          // apply new position to the particle
          for(int p = 0; p < _params; p++)
          {
            particles[i].position[p] = next[p];
          }
          
          particles[i].positionValue = f.calculate(particles[i].position);
          
          // ...

We added an auxiliary array next, where we save newly generated position and check it for uniqueness. If new position was not processed yet, we add it into the tree, copy to the particle's position and continue the procedure. If new position exists in the tree (hence has been already processed), we skip this iteration.

The complete source code is attached below (see ParticleSwarm.mqh). In addition to the implementation of the particle swarm optimization, the script testpso.mq4 containing test class is also provided. It allows us to make sure the optimization does actually work. It's written in OO style and can be extended with your own test cases.

There exist many popular test objective functions (or benchmarks), such as "rosenbrock", "griewank", "sphere", etc. For example, for the sphere we can define the functor's calculate method as follows.

      virtual double calculate(const double &vec[])
      {
        int dim = ArraySize(vec);
        double sum = 0;
        for(int i = 0; i < dim; i++) sum += pow(vec[i], 2);
        return -sum; // negative for maximization
      }

Please note, that conventional formulae of the test objective functions implies minimization, whereas we coded the algorithm for maximization (because we want maximal performance from EA). This is why you need to negate result in the calculation. This is important to remember if you decide to test our implementation on other popular objective functions. 

Finally, all is ready to embed the fast optimization algorithm into Optimystic library for on the fly optimization of EAs. But let us address this question in the next publication.

 

Table of contents 

Files:
crc64.mqh 11 kb
testpso.mq4 4 kb
In previous parts we have developed the library Optimystic for self-optimization of EAs on the fly (part 1, part 2) and particle swarm algorithm (part 3) which is more efficient optimization method than brute force approach used in the library intially.

Next step is to embed the particle swarm into the library. Let's go back to Optimystic.mqh.

As far as we're going to add new optimization method to the library and users are supposed to choose between them, we need to define new enumeration.

enum OPTIMIZATION_METHOD
{
  METHOD_BRUTE_FORCE,    // brute force
  METHOD_PARTICLE_SWARM  // particle swarm
};

And we need a method to switch either of the methods on.

class Optimystic
{
  public:
    virtual void setMethod(OPTIMIZATION_METHOD method) = 0;

That's it! We're done with the header file. Let's move to the implementation part in Optimystic.mq4.

For beginning we add the header file of the particle swarm algorithm.

#include <ParticleSwarm.mqh>

And add new declarations to the class.

class OptimysticImplementation: public Optimystic
{
  private:
    OPTIMIZATION_METHOD method;
    Swarm *swarm;

  public:
    virtual void setMethod(OPTIMIZATION_METHOD m)
    {
      method = m;
    }

The class should change optimization approach according to the selected method, so onTick should be updated. 

    virtual void onTick()
    {
      static datetime lastBar;
      if(enabled)
      {
        if(lastBar != Time[0] && TimeCurrent() > lastOptimization + PeriodSeconds() * resetInBars)
        {
          callback.setContext(context); // virtual trading
          if(method == METHOD_PARTICLE_SWARM)
          {
            optimizeByPSO();
          }
          else
          {
            optimize();
          }
        }
      }
      callback.setContext(NULL); // real trading
      callback.trade(0, Ask, Bid);
      lastBar = Time[0];
    }

Here we added a new procedure optimizeByPSO. Let's define it using already existing optimize as a draft. 

    bool optimizeByPSO()
    {
      if(!callback.onBeforeOptimization()) return false;
      Print("Starting on-the-fly optimization at ", TimeToString(Time[rangeInBars]));
      
      uint trap = GetTickCount();

We marked the time to compare performance of the new and old algorithms later.

      isOptimizationOn = true;
      if(spread == 0) spread = (int)MarketInfo(Symbol(), MODE_SPREAD);
      best = 0;
      int count = resetParameters();

We modified the function resetParameters to return number of active parameters (as you remember, we can disable specific parameters by calling setEnabled). Normally all parameters are enabled, so count will be equal to the size of parameters array.

      int n = parameters.size();

The reason why we're bothering with this is our need to pass work ranges of parameters values to the swarm on the next lines.

      double max[], min[], step[];
      ArrayResize(max, count);
      ArrayResize(min, count);
      ArrayResize(step, count);
      for(int i = 0, j = 0; i < n; i++)
      {
        if(parameters[i].getEnabled())
        {
          max[j] = parameters[i].getMax();
          min[j] = parameters[i].getMin();
          step[j] = parameters[i].getStep();
          j++;
        }
      }
      swarm = new Swarm(count, max, min, step);

And here is the problems begin. Next thing that we should write is somethig like this:

      swarm.optimize(..., 100);

But we need a functor instead of ellipsis. The first thought you may have - let's derive the class OptimysticImplementation from the swarm's Functor interface, but this is not possible. The class is already derived from Optimystic, and MQL does not allow multiple inheritance. This is a classical dilemma for many OOP languages, because multiple inheritance is a double-edged sword.

C++ supports not only multiple inheritance but virtual inheritance which helps to solve well-known diamond problem. Java does not allow multiple inheritance (which means that a class can extend only one parent class), but allow multiple interfaces (a class can implement arbitrary set of interfaces) as a workaround. This can alleviate the problem in many cases.

When multiple inheritance is not supported one can apply a composition. This is a widely used technique.

In our case we need to create a class derived from Functor and include it into OptimysticImplementation as a member variable.

class SwarmFunctor: public Functor
{
  private:
    OptimysticImplementation *parent;
    
  public:
    SwarmFunctor(OptimysticImplementation *_parent)
    {
      parent = _parent;
    }
    
    virtual double calculate(const double &vector[])
    {
      return parent.calculatePS(vector);
    }
};

This class does minimal job. It's only purpose is to accept calls from the swarm and redirect them to OptimysticImplementation, where we'll need to add new function calculatePS. This is done so, because only OptimysticImplementation manages all information required to perform EA testing. We'll consider the function calculatePS in a few minutes, but first let's continue the composition.

Add to OptimysticImplementation:

class OptimysticImplementation: public Optimystic
{
  private:
    SwarmFunctor *functor;

And now we can return back to the method optimizeByPSO which we have left on half way. Let's create an instance of SwarmFunctor and pass it to the swarm. 

      functor = new SwarmFunctor(&this);
    
      swarm.optimize(functor, 100); // TODO: 100 cycles are hardcoded - this should be adjusted
      double result[];
      bool ok = swarm.getSolution(result);
      if(ok)
      {
        for(int i = 0, j = 0; i < n; i++)
        {
          if(parameters[i].getEnabled())
          {
            parameters[i].setValue(result[j]);
            parameters[i].markAsBest();
            j++;
          }
        }
      }

      delete functor;    
      delete swarm;
      
      isOptimizationOn = false;
      callback.onAfterOptimization();
      
      Print("Done in ", DoubleToString((GetTickCount() - trap) * 1.0 / 1000, 3), " seconds");
      
      lastOptimization = TimeCurrent();
      
      return ok;
    }

When the library invokes optimizeByPSO from onTick, we create the Swarm object and SwarmFunctor object taking a back reference to its owner (OptimysticImplementation object) and execute Swarm's optimize passing SwarmFunctor as a parameter. The swarm will call SwarmFunctor's calculate with current parameters, and the latter will in turn call our OptimysticImplementation object to test EA on given parameters. One pictrure is worth a thousand words.

Sequence diagram of EA using Optimystic powered by Particle Swarm

So we need to add calculatePS to the implementation. Most of lines in it are taken from the method optimize used for straightforward optimization.

    // particle swarm worker method
    double calculatePS(const double &vector[])
    {
      int n = parameters.size();
      for(int i = 0, j = 0; i < n; i++)
      {
        if(parameters[i].getEnabled())
        {
          parameters[i].setValue(vector[j++]);
        }
      }

      if(!callback.onApplyParameters()) return 0;
        
      context.initialize(deposit, spread, commission);
      // build balance and equity curves on the fly
      for(int i = rangeInBars; i >= forwardBars; i--)
      {
        context.setBar(i);
        callback.trade(i, iOpen(Symbol(), Period(), i) + spread * Point, iOpen(Symbol(), Period(), i));
        context.activatePendingOrdersAndStops();
        context.calculateEquity();
      }
      context.closeAll();

      // calculate estimators
      context.calculateStats();
        
      double gain;
      if(estimator == ESTIMATOR_CUSTOM)
      {
        gain = callback.onCustomEstimate();
        if(gain == EMPTY_VALUE)
        {
          return 0;
        }
      }
      else
      {
        gain = context.getPerformance(estimator);
      }
        
      // if estimator is better than previous, then save current parameters
      if(gain > best && context.getPerformance(ESTIMATOR_TRADES_COUNT) > 0)
      {
        best = gain;
        bestStats = context.getStatistics();
      }
      callback.onReadyParameters();
      return gain;
    }

Now we're finally done with embedding particle swarm into Optimystic. The complete source code of the updated library is provided below.

Let's compare performance of optimization using our example EA based on MACD. With 3 parameters being optimized the process executes 10 times faster thanks to the particle swarm. 

[brute force log]
Starting on-the-fly optimization at 2016.09.30 04:00
1000 optimization passes completed, 3.291 seconds

[particle swarm log]
Starting on-the-fly optimization at 2016.09.30 04:00
PSO[3] created: 15/3
Variants: 1000, Scheduled count: 16
PSO Processing...
PSO Finished 139 of 240 planned passes: true
Done in 0.312 seconds

You may see that instead of total set of 1000 passes, particle swarm invoked EA 139 times. Resulting parameter sets of two methods may differ of course, but this is also the case when you use built-in genetic optimization.

 

Table of contents 


Files:
macd.mq4 9 kb
Normally trader's activity involves analysis of a lot of data. Part of it, and probably most significant part, are numbers. Time series, economic indicators, trading reports should be studied, classified and analyzed. It's very likely that this is the trading was the first applied field for big data science. When numbers form a massive array with numerous properties, it can be described as a hypercube. And there is a well-known technology named Online Analytical Processing (OLAP) which is aimed at dissecting this cube. Today we'll consider OLAP as a part of MetaTrader infrastructure and provide a simple implementation of multidimensional analysis in MQL. 

Before we start we should decide what data to analyse. There are many options to choose from, such as trading statements, optimization reports, custom indicators readings. Actually this is not so important which one to use, because we're going to design a universal object-oriented framework suitable for any purpose. But since we need something to test the framework on, we should elect a concrete task. One of the most popular tasks is analysis of an account trading history. So let's take it.

For a given trading history, one could desire to see profits splitted by symbols, week days, or buys and sells. Or he could want to compare profitness of several expert advisers by months. And then it could probably become important to combine these two analyzes in one view. All these can be achieved with OLAP.
  

The blueprint

Object-oriented approach implies that we need to decompose the task to a set of simple logically decoupled yet interconnected parts (classes) and think of a general plan how they work together. First class we need is a data record, where our input data will come from. The record can hold, for example, an information about trading operation or about a single pass during optimization.

The record is basically a vector with an arbitrary number of fields. Since this is an abstract thing, we don't need to know what every field means. For every specific use case we can derive a concrete record class which will know every field meaning and process it accordingly.

To read records from some abstract information source (such as account history, csv-file, html-report, or even WebRequest) we need another class - a data adapter. On the abstract base level the class should provide only one basic functionality - to iterate through records. Again, for every specific use case, we can derive a concrete data adapter capable of populating records with real data from underlying source.

All the records should be somehow mapped into a meta cube. We don't yet know how this can be done, but this is the essense of the task - to slice and aggregate the data into a generalized view with various meaningful statistics. As an abstraction, the meta cube provides only basic properties such as number of dimensions, their names, and size of each dimension. Descendant classes should fill the cube with specific statistics. For example, we can imagine such aggregators as sum, or average value of selected field in input records. 

Before values can be aggregated, the mapping of records should be performed, that is each record should get a set of indices uniquely identifing a cell in the meta cube. This subtask can be delegated to a special class - selector. The abstract base class will define an interface to return a range of possible values (for example, splitting records by day of week implies that selector produces week day number, that is 0 - 6), and to map a record into the range. Derived classes will provide concrete implementation for these methods. You may notice, that every selector corresponds to an edge (or dimension) of the meta cube. And the size of every dimension equals to the range of values of corresponding selector. 

In addition, it's sometimes useful to filter out some records. This is why we should provide a filter. It's much like selector with a restriction applied on values (for example, if you have a day of week selector, producing indices 0-6, you can form a filter on it to exclude specific day from calculations).

After the cube is built, we'll need to visualize results. So, let's add a class for output, name it display

Finally, there should be a core class which binds all the abovementioned stuff together. Let's call it analyst.

The whole picture looks like this.

Online Analytical Processing in MetaTrader



The Implementation

Let's start coding the classes described above. First goes the Record.

class Record
{
  private:
    float data[];
    
  public:
    Record(const int length)
    {
      ArrayResize(data, length);
      ArrayInitialize(data, 0);
    }
    
    void set(const int index, float value)
    {
      data[index] = value;
    }
    
    float get(const int index) const
    {
      return data[index];
    }
};

It does nothing more than storing values in the data array (vector). We use float type to save memory. Data cubes can be very large, so using float instead of double allows for sparing 50% of memory at the expense of a bit lower accuracy.

We'll acquire records from various places by mean of DataAdapter.

class DataAdapter
{
  public:
    virtual Record *getNext() = 0;
    virtual int reservedSize() = 0;
};

The method getNext should be called in a loop until it returns NULL (no more records). Until then all the records should be saved somewhere (see details below). The method reservedSize is provided for optimizing memory allocation.

Every dimension of the cube is calculated based on one or more fields of the records. It's convenient to denote every such field as an element in a special enumeration. For example, if we're going to analyze trading history of an account, we can stick to the following enumeration.

// MT4 and MT5 hedge
enum TRADE_RECORD_FIELDS
{
  FIELD_NONE,          // none
  FIELD_NUMBER,        // serial number
  FIELD_TICKET,        // ticket
  FIELD_SYMBOL,        // symbol
  FIELD_TYPE,          // type (OP_BUY/OP_SELL)
  FIELD_DATETIME1,     // open datetime
  FIELD_DATETIME2,     // close datetime
  FIELD_DURATION,      // duration
  FIELD_MAGIC,         // magic number
  FIELD_LOT,           // lot
  FIELD_PROFIT_AMOUNT, // profit amount
  FIELD_PROFIT_PERCENT,// profit percent
  FIELD_PROFIT_POINT,  // profit points
  FIELD_COMMISSION,    // commission
  FIELD_SWAP           // swap
};

And if we'd like to analyze MetaTrader's optimization results, we could use the following enumeration.

enum OPTIMIZATION_REPORT_FIELDS
{
  OPTIMIZATION_PASS,
  OPTIMIZATION_PROFIT,
  OPTIMIZATION_TRADE_COUNT,
  OPTIMIZATION_PROFIT_FACTOR,
  OPTIMIZATION_EXPECTED_PAYOFF,
  OPTIMIZATION_DRAWDOWN_AMOUNT,
  OPTIMIZATION_DRAWDOWN_PERCENT,
  OPTIMIZATION_PARAMETER_1,
  OPTIMIZATION_PARAMETER_2,
  //...
};

For every specific use case we should elaborate specific enumeration. Any one of such enumerations should be used as a template paramater of the templatized class Selector.

template<typename E>
class Selector
{
  protected:
    E selector;
    string _typename;
    
  public:
    Selector(const E field): selector(field)
    {
      _typename = typename(this);
    }
    
    // returns index of cell to store values from the record
    virtual bool select(const Record *r, int &index) const = 0;
    
    virtual int getRange() const = 0;
    virtual float getMin() const = 0;
    virtual float getMax() const = 0;
    
    virtual E getField() const
    {
      return selector;
    }
    
    virtual string getLabel(const int index) const = 0;
    
    virtual string getTitle() const
    {
      return _typename + "(" + EnumToString(selector) + ")";
    }
};

The field selector will hold specific value - an element of enumeration. For example, if TRADE_RECORD_FIELDS is used, one could create selector for buy/sell trade operations like so:

new Selector<TRADE_RECORD_FIELDS>(FIELD_TYPE);

The field _typename is auxiliary. It will be overriden in derived classes to identify selectors by class names, which can be useful in logs or resulting graphs. The field is used in the virtual method getTitle.

The method select is the main point of the class. This is where the incoming Record will be mapped to specific index on the axis formed by current selector. The index should be in range between getMin and getMax values, and overall number of indices is equal to value returned by getRange. If a given record can not be mapped inside the range, select returns false. Otherwise, if mapping is successfull, it returns true. 

The method getLabel returns a user-friendly description of the given index. For example, for buy/sell operations, index 0 should produce "buy" and index 1 - "sell".

Since we're going to concentrate on trading history analisys, let's introduce an intermediate class of selectors applied on the TRADE_RECORD_FIELDS enumeration.

class TradeSelector: public Selector<TRADE_RECORD_FIELDS>
{
  public:
    TradeSelector(const TRADE_RECORD_FIELDS field): Selector(field)
    {
      _typename = typename(this);
    }

    virtual bool select(const Record *r, int &index) const
    {
      index = 0;
      return true;
    }
    
    virtual int getRange() const
    {
      return 1; // this is a scalar by default, returns 1 value
    }
    
    virtual float getMin() const
    {
      return 0;
    }
    
    virtual float getMax() const
    {
      return (float)(getRange() - 1);
    }
    
    virtual string getLabel(const int index) const
    {
      return "scalar" + (string)index;
    }
};

By default, it maps all records into a single cell in the cube. For example, you can get total profit using this selector.

Now let's specialize this selector a bit more for the case of splitting operation by type (buy/sell).

class TypeSelector: public TradeSelector
{
  public:
    TypeSelector(): TradeSelector(FIELD_TYPE)
    {
      _typename = typename(this);
    }

    virtual bool select(const Record *r, int &index) const
    {
      ...
    }
    
    virtual int getRange() const
    {
      return 2; // OP_BUY, OP_SELL
    }
    
    virtual float getMin() const
    {
      return OP_BUY;
    }
    
    virtual float getMax() const
    {
      return OP_SELL;
    }
    
    virtual string getLabel(const int index) const
    {
      static string types[2] = {"buy", "sell"};
      return types[index];
    }
};

We define the class using FIELD_TYPE element in the base constructor. getRange returns 2 because we have only 2 possible values for the type: OP_BUY or OP_SELL. getMin and getMax return appropriate values. What should we write inside select method?

To answer the question we should decide which information is stored in the record. Let's code a class derived from the Record and intended for use with trading history.

class TradeRecord: public Record
{
  private:
    static int counter;

  protected:
    void fillByOrder()
    {
      set(FIELD_NUMBER, counter++);
      set(FIELD_TICKET, OrderTicket());
      set(FIELD_TYPE, OrderType());
      set(FIELD_DATETIME1, OrderOpenTime());
      set(FIELD_DATETIME2, OrderCloseTime());
      set(FIELD_DURATION, OrderCloseTime() - OrderOpenTime());
      set(FIELD_MAGIC, OrderMagicNumber());
      set(FIELD_LOT, (float)OrderLots());
      set(FIELD_PROFIT_AMOUNT, (float)OrderProfit());
      set(FIELD_PROFIT_POINT, (float)((OrderType() == OP_BUY ? +1 : -1) * (OrderClosePrice() - OrderOpenPrice()) / MarketInfo(OrderSymbol(), MODE_POINT)));
      set(FIELD_COMMISSION, (float)OrderCommission());
      set(FIELD_SWAP, (float)OrderSwap());
    }
    
  public:
    TradeRecord(): Record(TRADE_RECORD_FIELDS_NUMBER)
    {
      fillByOrder();
    }
};

The helper method fillByOrder demonstrates how most of the fields can be filled from current order. Of course, the order should be previously selected somewhere in the code. The number of fields TRADE_RECORD_FIELDS_NUMBER can be either hardcoded in a macro definition or determined dynamically from TRADE_RECORD_FIELDS enumeration (you may find details in the source codes attached at the end of the story, which continues in the part 2).

As you see the field FIELD_TYPE is filled by operation code from OrderType. Now we can get back to the TypeSelector's select method.

    virtual bool select(const Record *r, int &index) const
    {
      int t = (int)r.get(selector);
      index = t;
      return index >= getMin() && index <= getMax();
    }

Here, we read the field from incoming record and assign its value (which can be OP_BUY or OP_SELL) as the index. Only market orders are counted, so select returns false for any other types. We'll consider some other selectors later.

It's time to implement a data adapter specific for trading history. This is the class where TradeRecords will be generated based on a real account history. 

class HistoryDataAdapter: public DataAdapter
{
  private:
    int size;
    int cursor;
    
  protected:
    void reset()
    {
      cursor = 0;
      size = OrdersHistoryTotal();
    }
    
  public:
    HistoryDataAdapter()
    {
      reset();
    }
    
    virtual int reservedSize()
    {
      return size;
    }
    
    virtual Record *getNext()
    {
      if(cursor < size)
      {
        while(OrderSelect(cursor++, SELECT_BY_POS, MODE_HISTORY))
        {
          if(OrderType() < 2)
          {
            if(MarketInfo(OrderSymbol(), MODE_POINT) == 0)
            {
              Print("MarketInfo is missing:");
              OrderPrint();
              continue;
            }

            return new TradeRecord();
          }
        }
        return NULL;
      }
      return NULL;
    }
};

The adapter just iterates through all orders in the history and creates TradeRecord for every market order. There must be a core class which instantiates the adapter and invokes its getNext method. Moreover, the core class should store the returned records in an internal array. This is how we came to the class Analyst.

template<typename E>
class Analyst
{
  private:
    DataAdapter *adapter;
    Record *data[];
    int size;
    
  public:
    Analyst(DataAdapter &a): adapter(&a)
    {
      ArrayResize(data, adapter.reservedSize());
    }
    
    ~Analyst()
    {
      int n = ArraySize(data);
      for(int i = 0; i < n; i++)
      {
        delete data[i];
      }
    }
    
    void acquireData()
    {
      Record *record;
      int i = 0;
      while((record = adapter.getNext()) != NULL)
      {
        data[i++] = record;
      }
      ArrayResize(data, i);
      size = i;
    }
};

The class does not actually instantiate the adapter but accepts it as a parameter of constructor. This is a well-known principle of dependency injection. It allows us to decouple Analyst from concrete implementation of a DataAdapter. In other words, we can exchange different classes for history adapter freely without any modification of Analyst.

Analyst is currently ready for populating the internal array with records, but lacks most important part - aggregation itself.

As you remember, aggregators are classes capable of calculating specific statistics for selected record fields. And the base class for aggregators is a meta cube - multidimensional array storing statistics. 

class MetaCube
{
  protected:
    int dimensions[];
    int offsets[];
    double totals[];
    string _typename;
    
  public:
    int getDimension() const
    {
      return ArraySize(dimensions);
    }
    
    int getDimensionRange(const int n) const
    {
      return dimensions[n];
    }
    
    int getCubeSize() const
    {
      return ArraySize(totals);
    }
    
    virtual double getValue(const int &indices[]) const = 0;
};

The array dimensions describes the structure of the MetaCube. Its size should be equal to number of selectors used, and every element should contain the size of corresponding dimension, which comes from selector range. For example, if we want to see profits by day of week, we should craft a selector returning day indices in range 0-6 according to order open (or close) time. Since this is the only selector we use, dimensions will have 1 element, and its value will be 7. If we want to add another selector - say TypeSelector, described above - in order to see profits splitted both by day of week and operation type, then dimensions will contain 2 elements: 7 and 2. This also means that there should be 14 cells with statistics.

The array totals is actually the array with statistics. You may wonder why is it one dimensional while meta cube has been just called multidimensional. This is because we do not know beforehand how many dimensions user decide to add, so we use plain array to store elements belonging to many dimensions. This is just a matter of proper indexing, that is we need to know where every subarray begins. This is done by means of the array offsets

The base class does not resize or initialize the member arrays, because all this stuff will be implemented in derived classes.

Since all aggregators will have many common features, let's pack them into a single base class derived from MetaCube.

template<typename E>
class Aggregator: public MetaCube
{
  protected:
    const E field;

The Aggregator should process specific field of records. For example, this can be profit (FIELD_PROFIT_AMOUNT). We'll initialize the variable field in constructor below.

    const int selectorCount;
    const Selector<E> *selectors[];

The calculations should be performed in a multidimensional space formed by arbitrary number of selectors. We have considered the example of counting profits splitted by day of week and buy/sell operation, which requires 2 selectors. They should be stored in the array selectors. Selectors themselves are passed into the object again via constructor (see below).

  public:
    Aggregator(const E f, const Selector<E> *&s[]): field(f), selectorCount(ArraySize(s))
    {
      ArrayResize(selectors, selectorCount);
      for(int i = 0; i < selectorCount; i++)
      {
        selectors[i] = s[i];
      }
      _typename = typename(this);
    }

As you remember we have one dimensional array totals as a storage of values in the multidimensional space of selectors. To convert indices of multiple dimensions into a single position in the one dimensional array we use the following helper function.

    int mixIndex(const int &k[]) const
    {
      int result = 0;
      for(int i = 0; i < selectorCount; i++)
      {
        result += k[i] * offsets[i];
      }
      return result;
    }

It accepts an array with indices and returns ordinal position. You'll see how the array offsets is filled a few lines below.

This is the most tricky part: initialization of all internal arrays.

    virtual void setSelectorBounds()
    {
      ArrayResize(dimensions, selectorCount);
      int total = 1;
      for(int i = 0; i < selectorCount; i++)
      {
        dimensions[i] = selectors[i].getRange();
        total *= dimensions[i];
      }
      ArrayResize(totals, total);
      ArrayInitialize(totals, 0);
      
      ArrayResize(offsets, selectorCount);
      offsets[0] = 1;
      for(int i = 1; i < selectorCount; i++)
      {
        offsets[i] = dimensions[i - 1] * offsets[i - 1]; // 1, X, Y*X
      }
    }

Finally we came to the task of calculating statistics.

    // build an array with number of dimentions equal to number of selectors
    virtual void calculate(const Record *&data[])
    {
      int k[];
      ArrayResize(k, selectorCount);
      int n = ArraySize(data);
      for(int i = 0; i < n; i++)
      {
        int j = 0;
        for(j = 0; j < selectorCount; j++)
        {
          int d;
          if(!selectors[j].select(data[i], d)) // does record satisfy selector?
          {
            break;                             // skip it, if not
          }
          k[j] = d;
        }
        if(j == selectorCount)
        {
          update(mixIndex(k), data[i].get(field));
        }
      }
    }

This method will be called for array of records. Every record is presented to each selector in the loop, and if it's not rejected by any one of them, then corresponding indices are saved in the local array k. If all selectors determined valid indices for the record, we invoke method update. It accepts an offset of element in the array totals (the offset is calculated by the helper function mixIndex shown above, based on the array of indices), and value from the specified field of the record. If we continue the example of analyzing profits distribution, then the field will be FIELD_PROFIT_AMOUNT and its value comes from OrderProfit, as you remember.

    virtual void update(const int index, const float value) = 0;

The method is a pure virtual and should be overriden in descendant classes.

Finally, aggregator should provide a method to access resulting statistics.

    double getValue(const int &indices[]) const
    {
      return totals[mixIndex(indices)];
    }
};

The base Aggregator class does almost all the job. Now we can implement many specific aggregators with minimal efforts. But apart from this there is still a lot of work, so let's continue with all the routine tasks in the part 2.


Table of contents

In the first part we have described common OLAP principles and designed a set of classes bringing hypercube functionality in MetaTrader.

At the moment we have the base Aggregator class, which does almost all the job. Now we can implement many specific aggregators with minimal efforts. For example, to calculate a sum, we define:

template<typename E>
class SumAggregator: public Aggregator<E>
{
  public:
    SumAggregator(const E f, const Selector<E> *&s[]): Aggregator(f, s)
    {
      _typename = typename(this);
    }
    
    virtual void update(const int index, const float value)
    {
      totals[index] += value;
    }
};

And to calculate average, we code:

template<typename E>
class AverageAggregator: public Aggregator<E>
{
  protected:
    int counters[];
    
  public:
    AverageAggregator(const E f, const Selector<E> *&s[]): Aggregator(f, s)
    {
      _typename = typename(this);
    }
    
    virtual void setSelectorBounds()
    {
      Aggregator<E>::setSelectorBounds();
      ArrayResize(counters, ArraySize(totals));
      ArrayInitialize(counters, 0);
    }

    virtual void update(const int index, const float value)
    {
      totals[index] = (totals[index] * counters[index] + value) / (counters[index] + 1);
      counters[index]++;
    }
};

As far as we have aggregators defined now, we can return to the main class Analyst and complement it. 

template<typename E>
class Analyst
{
  private:
    ...
    Aggregator<E> *aggregator;
    
  public:
    Analyst(DataAdapter &a, Aggregator<E> &g): adapter(&a), aggregator(&g)
    {
      ...
    }
    
    void build()
    {
      aggregator.calculate(data);
    }
};

The method build builds meta cube with statistics using records from the data adapter as input.

The only one last thing which is still missing in the class Analyst is a facility for outputting results. It can be extracted to another class - Display. And it's very simple one.

class Display
{
  public:
    virtual void display(MetaCube *metaData) = 0;
};

It contains a pure virtual method accepting meta cube as data source. Specific ways of visualization and probably additional settings should be introduced in derived classes. 

Now we can finalize the class Analyst.

template<typename E>
class Analyst
{
  private:
    ...
    Display *output;
    
  public:
    Analyst(DataAdapter &a, Aggregator<E> &g, Display &d): adapter(&a), aggregator(&g), output(&d)
    {
      ...
    }
    
    void display()
    {
      output.display(aggregator);
    }
};

For testing purposes we need at least one concrete implementation of a Display. Let's code one for printing results in expert logs - LogDisplay. It will loop through entire meta cube and output all values along with corresponding coordinates. Of course, this is not so descriptive as a graph could be. But building various kinds of 2D or 3D graphs is a long story itself, not to mention a diversity of technologies which can be used for graph generation - objects, canvas, Google charts, etc., so put it out of consideration here. You may find complete source codes, including LogDisplay, attached below.

Finally, having everything in stock, we can plot overall workflow as follows:

  • Create HistoryDataAdapter object;
  • Create several specific selectors and place them in an array;
  • Create specific aggregator object, for example SumAggregator, using the array of selectors and a field, which values should be aggregated;
  • Create LogDisplay object;
  • Create Analyst object using the adapter, the aggregator, and the display;
  • Then call successively:

  analyst.acquireData();
  analyst.build();
  analyst.display();

Don't forget to delete the objects at the end.

We'are almost done. There is one small omission that was intentionally made to simplify the text, and this is the time to elaborate on the matter.

All selector we have discussed so far have had constant ranges. For example, there exist only 7 week days, and market orders are always either buy or sell. But what if the range is not known beforehand? This is a quite often situation.

It's perfectly legal to ask metacube for profits splitted by work symbols, or by magic number (expert advisers). To address such technical requirement, we should stack all unique symbols or magic numbers in an internal array, and then use its size as the range for corresponding selector. 

Let's create a dedicated class Vocabulary for managing such internal arrays and demonstrate how it is used in conjunction with, say, SymbolSelector.

The Vocabulaty implementation is straightforward.

template<typename T>
class Vocabulary
{
  protected:
    T index[];

We reserved the array index for unique values.

  public:
    int get(const T &text) const
    {
      int n = ArraySize(index);
      for(int i = 0; i < n; i++)
      {
        if(index[i] == text) return i;
      }
      return -(n + 1);
    }

We can check if a value is already in the index. If it is, the method get returns existing index. If it's not, the method returns new required size, negated. This is done so for a small optimization used in the following method adding new value into the index.

    int add(const T text)
    {
      int n = get(text);
      if(n < 0)
      {
        n = -n;
        ArrayResize(index, n);
        index[n - 1] = text;
        return n - 1;
      }
      return n;
    }

Finally we should provide methods to get total size of the index and retrieve its values.

    int size() const
    {
      return ArraySize(index);
    }
    
    T operator[](const int position) const
    {
      return index[position];
    }
};

As far as work symbols belong to orders, we embed the vocabulary into TradeRecord.

class TradeRecord: public Record
{
  private:
    ...
    static Vocabulary<string> symbols;

  protected:
    void fillByOrder(const double balance)
    {
      ...
      set(FIELD_SYMBOL, symbols.add(OrderSymbol())); // symbols are stored as indices from vocabulary
    }

  public:
    static int getSymbolCount()
    {
      return symbols.size();
    }
    
    static string getSymbol(const int index)
    {
      return symbols[index];
    }
    
    static int getSymbolIndex(const string s)
    {
      return symbols.get(s);
    }

Now we can implement SymbolSelector.

class SymbolSelector: public TradeSelector
{
  public:
    SymbolSelector(): TradeSelector(FIELD_SYMBOL)
    {
      _typename = typename(this);
    }
    
    virtual bool select(const Record *r, int &index) const
    {
      index = (int)r.get(selector);
      return (index >= 0);
    }
    
    virtual int getRange() const
    {
      return TradeRecord::getSymbolCount();
    }
    
    virtual string getLabel(const int index) const
    {
      return TradeRecord::getSymbol(index);
    }
};

The selector for magic numbers is implemented in a similar way. If you look into the sources you may see that the following selectors are provided: TypeSelector, WeekDaySelector, DayHourSelector, HourMinuteSelector, SymbolSelector, SerialNumberSelector, MagicSelector, ProfitableSelector. Also the following aggregators are available: SumAggregator, AverageAggregator, MaxAggregator, MinAggregator, CountAggregator.

That's all. The meta cube is ready.

Please note, that for simplicity we skipped some nuances of implementation of the classes. For example, we omitted all details about filters, which were announced in the blueprint (in the part 1). Also we needed to cumulate profits and losses in a special balance variable in order to calculate the field FIELD_PROFIT_PERCENT. It was not described in the text. You may find full source codes in the file OLAPcube.mqh attached below.

It's time to try the meta cube in action.  


The Example

Let's create a non-trading expert adviser capable of performing OLAP on MetaTrader's account history (the complete source code - THA.mq4 -  is attached at the bottom).

#property strict

#include <OLAPcube.mqh>

Although meta cube can handle any number of dimensions, let's limit it to 3 dimensions for simplicity. This means that we should allow up to 3 selectors. Types of supported selectors are listed in the enumeration:

enum SELECTORS
{
  SELECTOR_NONE,       // none
  SELECTOR_TYPE,       // type
  SELECTOR_SYMBOL,     // symbol
  SELECTOR_SERIAL,     // ordinal
  SELECTOR_MAGIC,      // magic
  SELECTOR_PROFITABLE, // profitable
  /* all the next require a field as parameter */
  SELECTOR_WEEKDAY,    // day-of-week(datetime field)
  SELECTOR_DAYHOUR,    // hour-of-day(datetime field)
  SELECTOR_HOURMINUTE, // minute-of-hour(datetime field)
  SELECTOR_SCALAR      // scalar(field)
};

It is used to define corresponding inputs:

sinput string X = "————— X axis —————";
input SELECTORS SelectorX = SELECTOR_SYMBOL;
input TRADE_RECORD_FIELDS FieldX = FIELD_NONE /* field does matter only for some selectors */;

sinput string Y = "————— Y axis —————";
input SELECTORS SelectorY = SELECTOR_NONE;
input TRADE_RECORD_FIELDS FieldY = FIELD_NONE;

sinput string Z = "————— Z axis —————";
input SELECTORS SelectorZ = SELECTOR_NONE;
input TRADE_RECORD_FIELDS FieldZ = FIELD_NONE;

The filter is going to be only one (though it's possible to have more), and it's off by default.

sinput string F = "————— Filter —————";
input SELECTORS Filter1 = SELECTOR_NONE;
input TRADE_RECORD_FIELDS Filter1Field = FIELD_NONE;
input float Filter1value1 = 0;
input float Filter1value2 = 0;

Also supported aggregators are listed in another enumeration:

enum AGGREGATORS
{
  AGGREGATOR_SUM,      // SUM
  AGGREGATOR_AVERAGE,  // AVERAGE
  AGGREGATOR_MAX,      // MAX
  AGGREGATOR_MIN,      // MIN
  AGGREGATOR_COUNT     // COUNT
};

And it's used for setting up work aggregator:

sinput string A = "————— Aggregator —————";
input AGGREGATORS AggregatorType = AGGREGATOR_SUM;
input TRADE_RECORD_FIELDS AggregatorField = FIELD_PROFIT_AMOUNT;

All selectors, including the one which is used for optional filter, are initialized in OnInit.

int selectorCount;
SELECTORS selectorArray[4];
TRADE_RECORD_FIELDS selectorField[4];

int OnInit()
{
  selectorCount = (SelectorX != SELECTOR_NONE) + (SelectorY != SELECTOR_NONE) + (SelectorZ != SELECTOR_NONE);
  selectorArray[0] = SelectorX;
  selectorArray[1] = SelectorY;
  selectorArray[2] = SelectorZ;
  selectorArray[3] = Filter1;
  selectorField[0] = FieldX;
  selectorField[1] = FieldY;
  selectorField[2] = FieldZ;
  selectorField[3] = Filter1Field;

  EventSetTimer(1);
  return(INIT_SUCCEEDED);
}

OLAP is performed only once in OnTimer event.

void OnTimer()
{
  process();
  EventKillTimer();
}

void process()
{
  HistoryDataAdapter history;
  Analyst<TRADE_RECORD_FIELDS> *analyst;
  
  Selector<TRADE_RECORD_FIELDS> *selectors[];
  ArrayResize(selectors, selectorCount);
  
  for(int i = 0; i < selectorCount; i++)
  {
    selectors[i] = createSelector(i);
  }
  Filter<TRADE_RECORD_FIELDS> *filters[];
  if(Filter1 != SELECTOR_NONE)
  {
    ArrayResize(filters, 1);
    Selector<TRADE_RECORD_FIELDS> *filterSelector = createSelector(3);
    if(Filter1value1 != Filter1value2)
    {
      filters[0] = new FilterRange<TRADE_RECORD_FIELDS>(filterSelector, Filter1value1, Filter1value2);
    }
    else
    {
      filters[0] = new Filter<TRADE_RECORD_FIELDS>(filterSelector, Filter1value1);
    }
  }
  
  Aggregator<TRADE_RECORD_FIELDS> *aggregator;
  
  // MQL does not support a 'class info' metaclass.
  // Otherwise we could use an array of classes instead of the switch
  switch(AggregatorType)
  {
    case AGGREGATOR_SUM:
      aggregator = new SumAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
      break;
    case AGGREGATOR_AVERAGE:
      aggregator = new AverageAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
      break;
    case AGGREGATOR_MAX:
      aggregator = new MaxAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
      break;
    case AGGREGATOR_MIN:
      aggregator = new MinAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
      break;
    case AGGREGATOR_COUNT:
      aggregator = new CountAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
      break;
  }
  
  LogDisplay display;
  
  analyst = new Analyst<TRADE_RECORD_FIELDS>(history, aggregator, display);
  
  analyst.acquireData();
  
  Print("Symbol number: ", TradeRecord::getSymbolCount());
  for(int i = 0; i < TradeRecord::getSymbolCount(); i++)
  {
    Print(i, "] ", TradeRecord::getSymbol(i));
  }

  Print("Magic number: ", TradeRecord::getMagicCount());
  for(int i = 0; i < TradeRecord::getMagicCount(); i++)
  {
    Print(i, "] ", TradeRecord::getMagic(i));
  }
  
  analyst.build();
  analyst.display();
  
  delete analyst;
  delete aggregator;
  for(int i = 0; i < selectorCount; i++)
  {
    delete selectors[i];
  }
  for(int i = 0; i < ArraySize(filters); i++)
  {
    delete filters[i].getSelector();
    delete filters[i];
  }
}

The helper function createSelector is defined as follows.

Selector<TRADE_RECORD_FIELDS> *createSelector(int i)
{
  switch(selectorArray[i])
  {
    case SELECTOR_TYPE:
      return new TypeSelector();
    case SELECTOR_SYMBOL:
      return new SymbolSelector();
    case SELECTOR_SERIAL:
      return new SerialNumberSelector();
    case SELECTOR_MAGIC:
      return new MagicSelector();
    case SELECTOR_PROFITABLE:
      return new ProfitableSelector();
    case SELECTOR_WEEKDAY:
      return new WeekDaySelector(selectorField[i]);
    case SELECTOR_DAYHOUR:
      return new DayHourSelector(selectorField[i]);
    case SELECTOR_HOURMINUTE:
      return new DayHourSelector(selectorField[i]);
    case SELECTOR_SCALAR:
      return new TradeSelector(selectorField[i]);
  }
  return NULL;
}

All the classes comes from the header file.

If you run the EA on a real account and choose to use 2 selectors - SymbolSelector and WeekDaySelector - you can get results in the log, something like this: 

Symbol number: 4
0] XAUUSD
1] XAGUSD
2] USDRUB
3] CADJPY
Magic number: 1
0] 0
FIELD_PROFIT_AMOUNT [28]
X: SymbolSelector(FIELD_SYMBOL)
Y: WeekDaySelector(FIELD_DATETIME2)
indices: 4 7
Filters: 
0: XAUUSD Sunday
0: XAGUSD Sunday
0: USDRUB Sunday
0: CADJPY Sunday
-2.470000028610229: XAUUSD Monday
3: XAGUSD Monday
-7.600001335144043: USDRUB Monday
0: CADJPY Monday
0: XAUUSD Tuesday
0: XAGUSD Tuesday
-4.25: USDRUB Tuesday
0: CADJPY Tuesday
-1.470000028610229: XAUUSD Wednesday
4.650000095367432: XAGUSD Wednesday
-1.950000107288361: USDRUB Wednesday
0: CADJPY Wednesday
8.590000152587891: XAUUSD Thursday
-15.44999980926514: XAGUSD Thursday
-29.97999978065491: USDRUB Thursday
3.369999885559082: CADJPY Thursday
52.7700012922287: XAUUSD Friday
-76.20000553131104: XAGUSD Friday
12.42000007629395: USDRUB Friday
0: CADJPY Friday
0: XAUUSD Saturday
0: XAGUSD Saturday
0: USDRUB Saturday
0: CADJPY Saturday

In this case, 4 work symbols were traded in the account. The meta cube size is therefore 28. All combinations of work symbols and days of week are listed along with corresponding profit/loss values. Please note, that WeekDaySelector requires a field to be specified explicitly, because every trade can be logged either by open datetime (FIELD_DATETIME1) or close datetime (FIELD_DATETIME2). In this case we used FIELD_DATETIME2.

 

Table of contents 

Files:
THA.mq4 7 kb
OLAPcube.mqh 23 kb