OOP in MQL5 by Example: Processing Warning and Error Codes

26 May 2010, 12:19
8 443

Brief Introduction to OOP

Before we start developing, let's get acquainted with some features of the OOP, which will be used in this article. 

Of course, we will use structures and classes. These are the basics of object oriented languages. What is the structure, what is the class and how do they differ?

A structure is a construction that allows containing a set of variables and functions of different types (except void).

A class as well as the structure is a set of data fields. But a class is a more complicated and "Flexible" construction. Classes are the basic concept of the OOP. Differences of classes and structures are described in the documentation. I will repeat:

  • The keyword class is used in declaration;
  • By default, the access specifier of all the class members is private, unless otherwise indicated. Data members of structures are of public access types by default, unless otherwise indicated;
  • Class objects always have a table of virtual functions, even if virtual functions are not declared in the class. Structures cannot have virtual functions;
  • Operator new can be applied to class objects, this operator can't be applied to structures;
  • Classes can be inherited only from classes,structures can be inherited only from structures.


Now let's consider classes. All class fields are divided into two types. These are data members (variables, arrays, etc.) and functions defined inside a class.

Data members are usually called class properties, because when an object is created from a class, data members reflect exactly properties of this object. For example, if this is a geometric shape - circle, the properties will contain its radius, line width, shape color, i.e. the object properties.

Functions defined inside a class are called methods. They are used both for working with class properties and implementing any other algorithms, which are programmed in them.

The object oriented programming has the notion of incapsulation. This is the possibility to hide data and implementation of an object from the direct influence of a user (application programmer). A programmer gets only documentation that describes using what methods one can change object properties, while changing object properties directly is impossible.

Such protection measures are necessary in cases, when a number of checks are required before changing a property value. All required checks are implemented in methods, and in case they are performed successfully, they allow changing the property value. And in the case when a user has direct access to the properties, these checks are not performed, and as a result the property value can be set incorrectly, and the MQL program will not work appropriately.

For any of class properties and methods, we can set an access level using three modifiers: private, protected and public.

If the private modifier is used for a class field, access to this field is possible only using methods of the same class, thus it cannot be modified from the outside. Modifier protected also imposes some restrictions on access to the field from the outside, but it makes it possible to access the class fields for the methods of the same class and for methods of subclasses. On the contrary, public removes all access restrictions and opens a free access to the class fields.

Creating an Include mqh-file

The class that we are going to write should be located in a separate mqh file, so that it can be included into our programs (Expert Advisors, scripts, indicators)

To create this file, let's use the MQL5 Wizard. In menu File -> Create select Include file (*.mqh) and go Next. In the appeared window, enter the file name (I've called it ControlErrors) and press Done. A template of a mqh file will be opened. We'll continue working in this file.

Getting Started

Now you know all theoretical fundamentals of the OOP that can be useful in the process of studying this article. So, let's move on.

Let's consider the class code with the declaration of all its properties and methods:

class ControlErrors

   // Flags determining what types of statements should be introduced
   bool _PlaySound;    // Play or don't play a sound when an error occurs
   bool _PrintInfo;    // Enter error data the the journal of Expert Advisors
   bool _AlertInfo;    // Generate Alert alert with error data
   bool _WriteFile;    // Write reports on errors onto a file or not
   // A structure for storing error data and elements that use this structure
   struct Code
      int code;      // Error code
      string desc;   // Description of an error code
   Code Errors[];    // Array that contains error codes and their descriptions
   Code _UserError;  // Stores information about a custom error
   Code _Error;      // Stores information about the last error of any type   
   // Different service properties
   short  _CountErrors;     // Number of errors stored in array Errors[]
   string _PlaySoundFile;   // File that will be played for an alert sound
   string _DataPath;        // Path to the log storing directory

   // Constructor
   // Methods for setting flags
   void SetSound(bool value);          // Play or don't play a sound when an error occurs
   void SetPrint(bool value);          // Enter error data the the journal of Expert Advisors or not
   void SetAlert(bool value);          // Generate an Alert message or not
   void SetWriteFlag(bool flag);       // Set the writing flag. true - keep logs, false - do not keep
   // Methods for working with errors
   int  mGetLastError();            // Returns contents of the system variable _LastError
   int  mGetError();                // Returns code of the last obtained error
   int  mGetTypeError();            // Returns error type (Custom = 1 ore predefined = 0)
   void mResetLastError();          // Resets the contents of the system variable _LastError
   void mSetUserError(ushort value, string desc = "");   // Sets the custom error
   void mResetUserError();          // Resets class fields that contain information about the custom error
   void mResetError();              // Resets the structure that contains information about the last error
   string mGetDesc(int nErr = 0);   // Returns error description by the number, or that of the current error of no number
   int Check(string st = "");       // Method to check the current system state for errors
   // Alert methods(Alert, Print, Sound)
   void mAlert(string message = "");
   void mPrint(string message = "");
   void mSound();
   // Various service methods
   void SetPlaySoundFile(string file); // Method sets the file name to play an sound
   void SetWritePath(string path);     // Set the path to store logs  
   int mFileWrite();                   // Record into a file the available information about the last error

Class Properties

First comes declaration of class properties, modifier private is applied to all the properties, so working with properties directly outside the class is impossible. The properties are divided into three classes for convenience:

  1. Flags that determine what types of reports should be kept. All these flag can accept only two values: true - means that this type of reports (notifications) is enabled and false - means this type is disabled.
    • _PlaySound - variable that disables enables playing of a selected melody or sound when an error occurs.
    • _PrintInfo - is responsible for adding error details into the Expert Advisor journal.
    • _AlertInfo - enables or disables output of Alert with error information.
    • _WriteFile - enables or disables recording of the error information into a file.
  2. Structure for storing error data and elements that use this structure.
    • Code - structure per se. It was created for the convenience of error data storing in an array.
    • Errors - an array of Code type, i.e. each array element is a Code structure.
    • _UserError - a variable of Code type. It is sued for working with custom errors.
    • _Error - a variable of Code type. The last occurred error is placed into this variable and further work with the error is implemented via this variable.
  3. Other service properties.
    • _CountErrors - the variable contains the number of errors, whose data should be stored in array Errors. It is used for specifying the array size.
    • _PlaySoundFile - it contains the file name that will be played for an alert sound.
    • _DataPath - it contains the path and name of the log file error data is written to.

I think, it's all clear with the first group of properties: they enable or disable keeping certain reports. In the second group the Code structure is of interest. What is it and why is exactly this structure used as the array elements? It's all easy! It's much more convenient to store all the required data in a single array element, than to set separate arrays for an error code and its description. A structure is used for implementing such a possibility. All the required fields are declared inside a structure. In our case they are:

  • code - field of type int that contains the error code;
  • desc - field of type string. It contains the error description.

Actually, the structure is a compound data type, i.e. it can be used for declaring variable and arrays, which was done. As a result, each variable of Code type will contain fields of this structure. As well as each array element of Code type contains two fields for storing the code and its description. Thus, quite a convenient way for storing object data of different types in a single location is implemented in MQL5.

Then come variables _UserError and _Error. They both contain information about the last occurred error, though _UserError stores information about custom errors, while _Error - about all errors.

And the third group of properties. Here I included all the remaining properties, which cannot be included to the first or second group. They are three. First - _CountErrors, it contains the number of errors whose information is stored in array _Errors. This property is used for setting the size of array _Errors in the constructor and in some methods for calling array elements. The second property is _PlaySoundFile. It stores the name of a sound file that is played when an error occurs. The third property is _DataPath. It stores the path and name of the file for keeping logs.

Class Methods. Constructor

Next is the description of the constructor and class methods. Let's start considering the constructor and try to understand what it is. Like methods, this is a common function defined inside a class, but possessing some specific features:

  • A constructor name mahces the class name.
  • A constructor doesn't have a return value (type void is specified).
  • A constructor doesn't have input parameters.

Usually class members are initialized in constructors. For example, in our class constructor, all flags to disable report keeping are set, names of a sound file and a log file are specified, the size of the _Errors array is set and this arrays is filled with data. Below I will publish only part of the constructor code, because it is too large and of the same type - the main part is occupied by filling out of the _Errors array with codes and their descriptions. The full code is attached to the article.

void ControlErrors::ControlErrors(void)
   _CountErrors = 150;
   ArrayResize(Errors, _CountErrors);

   // Return codes of a trade server
   Errors[0].code = 10004;Errors[0].desc = "Requote";
   Errors[1].code = 10006;Errors[1].desc = "Request rejected";
   Errors[2].code = 10007;Errors[2].desc = "Request canceled by trader";
   Errors[3].code = 10008;Errors[3].desc = "Order placed";
   Errors[4].code = 10009;Errors[4].desc = "Request is completed";
   Errors[5].code = 10010;Errors[5].desc = "Request is partially completed";
   Errors[6].code = 10011;Errors[6].desc = "Request processing error";
   Errors[7].code = 10012;Errors[7].desc = "Request canceled by timeout";
   Errors[8].code = 10013;Errors[8].desc = "Invalid request";
   Errors[9].code = 10014;Errors[9].desc = "Invalid volume in the request";
   Errors[10].code = 10015;Errors[10].desc = "Invalid price in the request";
   Errors[11].code = 10016;Errors[11].desc = "Invalid stops in the request";
   Errors[12].code = 10017;Errors[12].desc = "Trade is disabled";
   Errors[13].code = 10018;Errors[13].desc = "Market is closed";
   Errors[14].code = 10019;Errors[14].desc = "There is not enough money to fulfill the request";
   Errors[15].code = 10020;Errors[15].desc = "Prices changed";
   Errors[16].code = 10021;Errors[16].desc = "There are no quotes to process the request";
   Errors[17].code = 10022;Errors[17].desc = "Invalid order expiration date in the request";
   Errors[18].code = 10023;Errors[18].desc = "Order state changed";
   Errors[19].code = 10024;Errors[19].desc = "Too frequent requests";
   Errors[20].code = 10025;Errors[20].desc = "No changes in request";
   Errors[21].code = 10026;Errors[21].desc = "Autotrading disabled by server";
   Errors[22].code = 10027;Errors[22].desc = "Autotrading disabled by client terminal";
   Errors[23].code = 10028;Errors[23].desc = "Request locked for processing";
   Errors[24].code = 10029;Errors[24].desc = "Order or position frozen";
   Errors[25].code = 10030;Errors[25].desc = "Invalid order filling type";

   // Common Errors
   Errors[26].code = 4001;Errors[26].desc = "Unexpected internal error";
   Errors[27].code = 4002;Errors[27].desc = "Wrong parameter in the inner call of the client terminal function";
   Errors[28].code = 4003;Errors[28].desc = "Wrong parameter when calling the system function";
   Errors[29].code = 4004;Errors[29].desc = "Not enough memory to perform the system function";
   Errors[30].code = 4005;Errors[30].desc = "The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes";
   Errors[31].code = 4006;Errors[31].desc = "Array of a wrong type, wrong size, or a damaged object of a dynamic array";
   Errors[32].code = 4007;Errors[32].desc = "Not enough memory for the relocation of an array, or an attempt to change the size of a static array";
   Errors[33].code = 4008;Errors[33].desc = "Not enough memory for the relocation of string";
   Errors[34].code = 4009;Errors[34].desc = "Not initialized string";
   Errors[35].code = 4010;Errors[35].desc = "Invalid date and/or time";
   Errors[36].code = 4011;Errors[36].desc = "Requested array size exceeds 2 GB";
   Errors[37].code = 4012;Errors[37].desc = "Wrong pointer";
   Errors[38].code = 4013;Errors[38].desc = "Wrong type of pointer";
   Errors[39].code = 4014;Errors[39].desc = "System function is not allowed to call";


Please note that the implementation description is performed outside the class! Only methods are declared inside the class! Though this is not required. If you want - you can describe the body of each method in the class, but in my opinion, it is inconvenient and difficult to understand.

As I said, only headers of method functions are declared in the class body, while description is performed outside the class. When describing a method, you must specify which class it belongs to. The context allowing operation :: is used for it. As seen from the above code, first the return type of a method is specified (for a constructor it is void), then comes the class name (name of the context the method belongs to), and the class name is followed by the context allowing operation and then comes the method name with its input parameters. After all this, the algorithm description for the method starts.

First in the constructor all flags are set and the sound and log files are specified: 


Each of these methods works with a certain class property. This is done intentionally for the case of a necessity to filter values set by a user for properties. For example, you can set a certain mask, to which a custom set file name and path should correspond. If there is now correspondence to mask, the user will be informed about it. 

As you might have mentioned, all flags take the false value. I.e. no reports will be kept by defaults, when a class sample is created. A user should select which reports should be kept and activate them using the same methods "Set" in function OnInit(). Likewise, you can change the name and path to the log file ( the path is set relative to directory 'MetaTrader 5\MQL5\Files\') and the sound file (the path is set relative to directory 'MetaTrader 5\Sounds\').

After the flags are set, we initialize variable _CountErrors, assigning value 150 to it (information about 149 arrays will be stored in the array) and then set the required array size using function ArrayResize(). After that we begin filling the array.

Flag Setting Methods

The constructor description is followed by the description of flag setting methods and of setting sound and log files names:

void ControlErrors::SetAlert(bool value)
   _AlertInfo = value;

void ControlErrors::SetPrint(bool value)
   _PrintInfo = value;

void ControlErrors::SetSound(bool value)
   _PlaySound = value;

void ControlErrors::SetWriteFlag(bool flag)
   _WriteFile = flag;

void ControlErrors::SetWritePath(string path)
   _DataPath = path;

void ControlErrors::SetPlaySoundFile(string file)
   _PlaySoundFile = file;

As seen from the code, it is a simple assigning passed to the parameter method, class property. Flags do not require any checks, because can take only two values. However, file names and paths need to be checked before assigning.

Calls of these methods, as well as of all others, looks as follows: 

type Class_name::Function_Name(parameters_description)
   // function body

Next is the description of methods of working with errors, and the first of them are mGetLastError() and mResetLastError().

Methods mGetLastError() and mResetLastError() 

The method name mGetLastError() speaks for itself. It duplicates the function GetLastError(). But besides the call of GetLastError(), a description is searched for the obtained error code in array _Errors, and the error details (code and its description) are saved in variable _Error, so that further to use the saved value insted of calling GetLastError() every time.

The method code:

int ControlErrors::mGetLastError(void)
   _Error.code = GetLastError();
   _Error.desc = mGetDesc(_Error.code);
   return _Error.code;

Method mResetLastError() duplicates function ResetLastError():

void ControlErrors::mResetLastError(void)

Methods for Working with the Last Error Message

These are two methods: mGetError() and mResetError().

Method mGetError() returns the code contained in _Error.code:

int ControlErrors::mGetError(void)
   return _Error.code;

Method mResetError() resets the contents of variable _Error:

void ControlErrors::mResetError(void)
   _Error.code = 0;
   _Error.desc = "";

Error Type Determining Method mGetTypeError()

The next method is mGetTypeError(). It checks if the last occurred error is a custom one, or it is predefined (is contained in the _Errors array).

The method code:

int ControlErrors::mGetTypeError(void)
   if (mGetError() < ERR_USER_ERROR_FIRST)
      return 0;
   else if (mGetError() >= ERR_USER_ERROR_FIRST)
      return 1;
   return -1;

Constant ERR_USER_ERROR_FIRST has the value 65536. From these codes custom defined errors begin. So in the method body, the last received error code is checked. If the method returns zero, this is a predefined error. If one is returned, it is a custom error.

Methods for Working with Custom Errors

In MQL5, users can set their own errors in the course of the program. In order for custom codes could be assigned appropriate descriptions, property _UserError is available in the class. Two methods are used for working with this property.

Method mSetUserError() is used for setting a code and describing the custom error:

void ControlErrors::mSetUserError(ushort value, string desc = "")
   _UserError.code = value;
   _UserError.desc = desc;

First, function SetUserError() sets the predefined variable _LastError to the value equal to ERR_USER_ERROR_FIRST + value. And then value and its appropriate assigned description are saved in variable _UserError.

The second method mResetUserError() resets the fields of variable _UserError:

void ControlErrors::mResetUserError(void)
   _UserError.code = 0;
   _UserError.desc = "";

This method can work only with variable _UserError. To reset the value of the system variable _LastError, another method is used: mResetLastError(), it is described above.

Method for Obtaining Error Code Description

There is also a special method mGetDesc() in the class, which being called will return the error code description from array Errors, or from field desc of variable _UserError, if the error was set by a user:

string ControlErrors::mGetDesc(int nErr=0)
   int ErrorNumber = 0;
   string ReturnDesc = "";
   ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
   ErrorNumber = (nErr>0)?nErr:ErrorNumber;
   if ((ErrorNumber > 0) && (ErrorNumber < ERR_USER_ERROR_FIRST))
      for (int i = 0;i<_CountErrors;i++)
         if (Errors[i].code == ErrorNumber)
            ReturnDesc = Errors[i].desc;
   else if (ErrorNumber > ERR_USER_ERROR_FIRST)
      ReturnDesc = (_UserError.desc=="")?"Cusrom error":_UserError.desc;
   if (ReturnDesc == ""){return "Unknown error code: "+(string)ErrorNumber;}
   return ReturnDesc;

This method has one parameter nErr. It is equal to zero by default. If during the method call a value is set to the parameter, the description will be searched for the set value. If the parameter is not set, the description will be searched for the last received error code.

In the beginning, two variables are declared in the method: ErrorNumber - using this variable the error code will be processed; and ReturnDesc - description obtained for ErrorNumber will be stored in it. In the next two lines, when assigning a value to ErrorNumber, the conditional operator ?: is used. This is a simplified analog of the if-else construction.

ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
ErrorNumber = (nErr>0)?nErr:ErrorNumber;

In the first line we define: if an error was fixed, i.e. mGetError() returned a non-zero result, then the obtained error code (value returned by the mGetError() method) will be assigned to variable ErrorNumber; otherwise the value of the variable ErrorNumber will be assigned. In the second line the same check is performed, but for the parameter of method mGetError(). If the value of nErr is non-zero, it is assigned to variable ErrorNumber.

Once we receive the error code, begin to search descriptions for this code. If the obtained code is greater than zero and less than ERR_USER_ERROR_FIRST, i.e. not a custom error, we search for its description in a loop. And if the obtained code is greater than ERR_USER_ERROR_FIRST, we take a description from field desc of variable _UserError.

At the end of we check if the description was found. If not, return a message about an unknown error code.

Signal Methods

Signal methods include mAlert(), mPrint() and mSound(). In their arrangement, these methods are very similar:

void ControlErrors::mAlert(string message="")
   if (_AlertInfo == true)
      if (message == "")
         if (mGetError() > 0)
            Alert("Error №",mGetError()," - ",mGetDesc());

void ControlErrors::mPrint(string message="")
   if (_PrintInfo == true)
      if (message == "")
         if (mGetError() > 0)
            Print("Error №",mGetError()," - ",mGetDesc());

void ControlErrors::mSound(void)
   if (_PlaySound == true)

In all the three methods, in the beginning the flag allowing reports and signals is checked. Then, in the methods mAlert() and mPrint(), the input parameter message is checked for the message that should be displayed in the Alert dialog or added to the journal. If a message is set in message, and the last error code is greater than zero, then it is displayed. If not, then a standard message is displayed. Method mSound() does not have any parameters, so after checking the flag, immediately the function PlaySound() is called in it to produce a sound.

Method Check()

This method simply calls all the functions of this class in the correct sequence, thus the occurrence of a new error is checked, all allowed reports are issued, and immediately after that the error code with its description is deleted. Thus method Check() performs a comprehensive check:

int ControlErrors::Check(string st="")
   int errNum = 0;
   errNum = mGetLastError();
   return errNum;

Check() has one parameter of string type. This is a custom message that is passed to methods mAlert() and mPrint() to be written in reports.

Methods for Writing Messages to a Log File

This method is called mFileWrite(). If keeping a log file is allowed and the path to the file is specified correctly - this method makes the record in the specified file.

int ControlErrors::mFileWrite(string message = "")
   int      handle  = 0,
            _return = 0;
   datetime time    = TimeCurrent();
   string   text    = (message != "")?message:time+" - Error №"+mGetError()+" "+mGetDesc();
   if (_WriteFile == true)
      handle = FileOpen(_DataPath,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI);
      if (handle != INVALID_HANDLE)
         ulong size = FileSize(handle);
         _return = FileWrite(handle,text);
   return _return;

At the beginning  four variables are declared: handle - to store the handle of an open file, _return - to store the return value, time, that maintains the current time (time to record to the file) and text - the message text that will be written to the file. The method mFileWrite() has one input parameter - message, in which the user can pass any string that should be written to the file.

This feature can be used for recording indicator values, prices and other required data in certain moments.

After variables are declared, flag _WriteFile is checked. And if keeping of a log file is allowed, the file is opened for re-writing using function FileOpen(). The first parameter of FileOpen() is property DataPath, which contains the file name and path to it. The second parameter is a set of flags that determine the mode of working with flags. In our case four flags are used:

  • FILE_READ and FILE_WRITE together direct to open a non-empty file with possibility to add data to it.
  • FILE_TXT shows that work will be performed with a simple text file.
  • FILE_ANSI indicates that data will be written to the file as strings of ANSI type (single-byte symbols).

On the next step we check if the file was opened successfully or not. If not, the handle will have the value INVALID_HANDLE and the method operation ends here. But if it succeeded, we obtain the file size using FileSize(), then using FileSeek() we move the position of the file pointer to the file end and add a messages to the file end using function FileWrite(). After that close this file using function FileClose().

Pass handle of the file whose size we need to return, as an input parameter to function FileSize(). This is the only parameter of this function.

Three parameters need to be specified for the operation of FileSeek():

  • Handle of  the file we work with.
  • Shift of the file pointer.
  • Reference point for the shift. It takes one of values of ENUM_FILE_POSITION.

At least two parameters are required for the work of function FileWrite(). This is the handle of a file, to which we need to write text data. The second one is a text line that needs to be written and all the next text lines that will be written to the file. The number of parameters must not exceed 63.

Function FileClose() also needs the file handle for closing it.


Now I'd like to add a few common examples of using the class that we've written. Let's begin with the object creation and allow keeping necessary reports.

So, let's create a class object:

#include <ControlErrors.mqh>

ControlErrors mControl;

Before we create an object, we need to add to the Expert Advisor the file that contains the class description. This is done at the very beginning of the program via directive #include. And only after that the object is created - it looks the same as creating a new variable. But instead of the data type the class name is inserted. 

Now let's create ettor reports that we want to receive. This is done in function OnInit(): 

int OnInit()

By default, when an object is created, all the allowing flags are set to false, i.e. all of the reports are disabled. That's why in OnInit() it's not required to call methods with the false value, because it is done in the above example (method SetSound()). These methods can also be called in other program parts. For example, if you need to disable report keeping under certain conditions, you can program these conditions and set flags to the required values when the conditions are fulfilled.

And one more thing that needs to be mentioned here is the call of methods during program running and "catching" of errors. This part is not difficult, because you can use here the single method Check(), setting all flags before that:


This method, as stated above, will identify the code of the occurred error, will call all the methods that keep reports and then will reset values of all variables that contain information about the last error. If the error processing way offered by Check() doesn't suit for some reasons, you can generate your own reports using the available class methods.

Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/70

Attached files |
controlerrors.mqh (19.82 KB)
MQL for "Dummies": How to Design and Construct Object Classes MQL for "Dummies": How to Design and Construct Object Classes

By creating a sample program of visual design, we demonstrate how to design and construct classes in MQL5. The article is written for beginner programmers, who are working on MT5 applications. We propose a simple and easy grasping technology for creating classes, without the need to deeply immerse into the theory of object-oriented programming.

Creating Active Control Panels in MQL5 for Trading Creating Active Control Panels in MQL5 for Trading

The article covers the problem of development of active control panels in MQL5. Interface elements are managed by the event handling mechanism. Besides, the option of a flexible setup of control elements properties is available. The active control panel allows working with positions, as well setting, modifying and deleting market and pending orders.

The Algorithm of Ticks' Generation within the Strategy Tester of the MetaTrader 5 Terminal The Algorithm of Ticks' Generation within the Strategy Tester of the MetaTrader 5 Terminal

MetaTrader 5 allows us to simulate automatic trading, within an embedded strategy tester, by using Expert Advisors and the MQL5 language. This type of simulation is called testing of Expert Advisors, and can be implemented using multithreaded optimization, as well as simultaneously on a number of instruments. In order to provide a thorough testing, a generation of ticks based on the available minute history, needs to be performed. This article provides a detailed description of the algorithm, by which the ticks are generated for the historical testing in the MetaTrader 5 client terminal.

New Opportunities with MetaTrader 5 New Opportunities with MetaTrader 5

MetaTrader 4 gained its popularity with traders from all over the world, and it seemed like nothing more could be wished for. With its high processing speed, stability, wide array of possibilities for writing indicators, Expert Advisors, and informatory-trading systems, and the ability to chose from over a hundred different brokers, - the terminal greatly distinguished itself from the rest. But time doesn’t stand still, and we find ourselves facing a choice of MetaTrade 4 or MetaTrade 5. In this article, we will describe the main differences of the 5th generation terminal from our current favor.