Русский Español Português
preview
Developing a Replay System (Part 33): Order System (II)

Developing a Replay System (Part 33): Order System (II)

MetaTrader 5Examples | 22 April 2024, 15:42
1 176 0
Daniel Jose
Daniel Jose

Introduction

In the previous article Developing a Replay System (Part 32): Order System (I), we began to develop an orders system to be used in the Expert Advisor. We developed a base system class containing only those functions that we really needed to begin with.

One might think, and this is not entirely wrong, that these functions are essentially very basic and cannot completely cover the order system. This is true, but at this early stage we are going to develop a system that will not be used directly in the replay/simulation service. We will develop a system that can work with a real trading server, both on a demo account and on a real one. We will make extensive use of the MetaTrader 5 platform, which will provide us with all the necessary support from the beginning. It is very important to make the system operate flawlessly from the very beginning, when the platform communicates with the server. Because when only replay/simulator is used, we can no longer adapt the Expert Advisor system to our replay/simulator. We must ensure that the replay/simulator matches what the EA is doing. This is why we need to run the EA on a real server first.

This requirement leads to a reduced number of methods and functions in the system, and therefore in the C_Orders class. However, we still remember that we are creating a large system. Make no mistake thinking that the C_Orders class will not be able to meet all our needs. We just need to make the MetaTrader 5 platform give us the maximum support required for working with the trading server. The essence of the maximum support from MetaTrader 5 is to avoid creating things where such creation can be avoided. We should not try to reinvent the wheel but rather use the platform to the maximum so that we put in the least amount of effort when working with the replay/simulator system.

But before continuing with the order system, I would like to show you how to solve one problem. Although, this is not a problem but rather an inconvenience present in MetaTrader 5.


Trying to simplify things

If you are a regular user of MetaTrader 5, then you have probably noticed one thing that, on the one hand, is annoying, and on the other hand, discouraging, to put it mildly. What I mean is most noticeable by people who have switched to MetaTrader 5 from other platforms and, having encountered this operation, feel irritated or reluctant to use it in some aspects, which mainly involve chart analysis.

The problem is that when we do some analysis on the asset chart, sometimes difficulties may occur while changing or deleting graphical objects used for analysis. There are things that you need to know how to configure in MetaTrader 5 in order to really know how to handle things. Let's assume that you have just installed the platform on your computer and have never worked with it. So, this is your first contact with the platform. I want to emphasize one thing: I am talking about someone's first experience with MetaTrader 5.

Here's an interesting question. We can access old objects by double-clicking on them, as long as there is no flag disabling selection (checkbox, see Fig. 01), we can actually access the object.

Figure 01

Figure 01 - Checkbox activated.

However, that is not the point. The problem is that many users, coming from other platforms, are accustomed to clicking only on an object. The object is thus selected and can be changed or removed from the chart. It is not entirely obvious that MetaTrader 5 requires a double click, and it takes some time to find out how to proceed in such cases.

Now let's talk about the details. In MetaTrader 5 it is possible to change this behavior. To access settings, press CTRL + O. This will open the MetaTrader 5 options window. In this window, go to the Charts tab and enable: "Select objects by single mouse click". By doing so, you can use selection in MetaTrader 5 as in other platforms. But even in this case, with this small adjustment, we will still be missing some features. My purpose here is to show how to do it in a different way and with new possibilities.

As a programmer, I found myself in a situation where I can somehow make sure that the user who switches to MetaTrader 5 gets the right and pleasant experience when working with the platform. And this will be implemented in such a way that you only need to press once. However, if the user does not have enough programming experience, the solution may not be that obvious. Also, some people might consider such work unnecessary. However, this will expand our possibilities.

To solve this problem, we will return to the C_Terminal class and add a few lines of code to it. This will be enough to provide an easier experience for new users. Let's look at what should be added. See the code below:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      static string st_str = "";
                                
      switch (id)
      {
         case CHARTEVENT_CHART_CHANGE:
            m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
            m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
            break;
         case CHARTEVENT_OBJECT_CLICK:
            if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
            if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true)
            ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true);
            break;
         case CHARTEVENT_OBJECT_CREATE:
            if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
            st_str = sparam;
            break;
      }
   }

This method is already part of the original code for the C_Terminal class. But it has a few additional lines which are related to two new events. They come from the MetaTrader 5 platform. There is one detail in the code above. But first I can explain the code and then explain the detail. We declare a static local variable, which is used to store the name of the object that we are examining. This variable is initialized as an empty string. This is very important if we want everything to be done correctly. When we click on an object on the chart, its name is passed to our program. Also, the platform passes the CHARTEVENT_OBJECT_CLICK event, with the object name being in the sparam variable. These small details will help us in the next step. Now let's check if the name in our local variable matches the one reported by MetaTrader 5. In the case of the first execution, they will be different; if they are the same, then nothing will happen. But if they are different, then the object that was in the focus of the platform will that focus. This is done in the line where we check and, if necessary, remove focus from the object.

Now please pay attention to the following point: When object properties are changed to look like the one shown in Figure 01, we must understand that the user is consciously saying that this object should not receive attention inadvertently. Either because the object shouldn't be modified, or because it will point to something, and we don't want to change its position or change anything like that. So, when we mark the checkbox as in Fig. 01, we must understand that the object will be ignored. This is exactly what this check does. It checks whether the given object should be ignored. Without this check, the checkbox marked by the user in the object property (as shown in Figure 01) would be ignored and the object would be accessible. Thus, to make an object to receive this focus, we use this particular code. Notice that in this code, we store the name of the object so that when we lose focus, our program knows what the object was. Something similar happens when MetaTrader 5 triggers the CHARTEVENT_OBJECT_CREATE event. However, there is one detail here. Unlike the CHARTEVENT_OBJECT_CLICK event, this object creation event will only occur in our program if we tell MetaTrader 5 that we want to know about the object creation.

Note: In any case MetaTrader 5 will always trigger events. But some of them will only be directed to our code if we inform the platform.

To tell MetaTrader 5 that we want to be notified when an object is created on the chart, we proceed in much the same way as in the case of deleting an object. Here is the code:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      CurrentSymbol();
      m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
      m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
      m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
      m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
      m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
      m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
      m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
      m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
      m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
      m_Infos.ChartMode     = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
      ResetLastError();
   }

This line tells MetaTrader 5 that from now on we want to receive notifications when a new object is created on the chart. Similarly, we need to tell MetaTrader 5 when we no longer want to receive notifications. This is done using the following code:

~C_Terminal()
   {
      ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, false);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false);
   }

Once this line is executed, MetaTrader 5 will no longer notify our code about object creation events. This kind of thing is quite important and interesting because we sometimes want to know when the user adds or removes objects from the chart. And trying to do this 'manually' is quite difficult. However, with MetaTrader 5 this is quite easy to do. You've probably noticed that we can handle the platform to make it act the way we expect it to. This behavior and the fact that we can do it means that we can help new users have a better experience using the platform, which is quite motivating. This is all a matter of adaptation, but adaptation can go faster if you as a programmer can help "non-programmers".

Let's now get back to what we actually came to implement in this article. We still have a lot to do before we can start anything else.


Expanding the order system

The title of this topic may be a bit of an exaggeration, but I want to bring something to our order system that was shown in another series of articles. So, you can use the same concepts and knowledge that we gained there. The latest article in that series is available at this link: Creating an EA that works automatically (Part 15): Automation (VII). In that article, we tested how to convert an EA that was built and managed manually into an automated EA. I am raising this topic now because you may be interested in observing the operation of the EA in replay/simulator in order to be able to make some adjustments to it. Remember that to test the functionality of the EA, the MetaTrader 5 platform offers a strategy tester that does an excellent job. Here we are not creating something in parallel with the tester. The idea is to be able to practice some technique even when the market is closed.

Let's now bring the concepts and ideas covered in the series on creating an automated EA. We will start with the timer class.

Important note: In this first phase, as we use the EA and develop it to be more focused on facilitating access to the trading server, we will have a quick overview of the functions that we will import from that series of articles. For a more detailed and in-depth explanation, please read this series as it may help a lot with some issues.


Controlling time with C_ControlOfTime

C_ControlOfTime is a very interesting class. Despite the rather compact code, it allows us, in a fairly simple way, to make an EA with a certain level of control based on the time when we can trade. Many traders often worry about the first market movements and thus they enter or exit positions prematurely. By making decisions based on emotions where we don't need emotions, we often suffer losses. But if we had stuck to the plan, we would not have traded at that moment. One of the easiest ways to enable this control is to use a schedule. In the series on creating an automated EA, I showed how to implement such a scheduler. So, let's import it to our new EA. We will create the same behavior, which is very good because no one wants to trade outside of the time zone that they consider appropriate and a little safer for trading the market. So, we will make the EA help us with this.

The C_ControlOfTime class starts like this:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_ControlOfTime : protected C_Orders
{
   private :
      struct st_00
      {
         datetime Init,
                  End;
      }m_InfoCtrl[SATURDAY + 1];
//+------------------------------------------------------------------+

Here, in a variable, we will store the time allowed for operations. Perhaps, the most curious part of this code is the use of the SATURDAY definition. For those who have not read the series of articles on creating an automated EA, I will briefly explain what this definition means. By using this definition, we aim to say that our matrix will cover all days of the week. But there is one important detail: we need to add one to the value. This is necessary to ensure that the matrix has the correct length since it starts at ZERO. But we cannot have a matrix with zero elements. We should always start with a value that is greater than zero. That's why we added one. If we didn't do this, we would end up with a matrix of 6 elements, not 7. One more thing: You probably noticed that the C_ControlOfTime class inherits from the C_Orders class. Thus, we expand the capabilities of the C_Orders class. However, this extension will not be actually carried over to the EA, as you will see in subsequent explanations. The reason for this can be better understood from the series of articles on the automated EA.

The next thing in the code is the class constructor. Its code is as follows:

C_ControlOfTime(C_Terminal *arg, const ulong magic)
                :C_Orders(arg, magic)
   {
      for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) ZeroMemory(m_InfoCtrl[c0]);
   }

Here you need to understand two simple things. The first is that we initialize the C_Orders class before the code in the C_ControlOfTime constructor is executed. The second and perhaps more curious feature is the presence of a loop. Interesting is the way it operates. Please note that in this loop we are using an enumeration that starts with SUNDAY, and the variable will increase up to SATURDAY. Is it possible? Yes, we can do it, but we must be careful. The main concern is that SUNDAY should have a value lower than SATURDAY. If this is not the case, then we will have problems accessing the matrix data. Fortunately, the enumeration starts with SUNDAY and progresses day by day to SATURDAY, so we get the days of the week.

The main advantage of using this programming mode is that it improves the level of our code, bringing the MQL5 language very close to natural language. This approach was used and explained in detail in a series of articles about the automated EA. Well, here I showed only a couple of things. Read the previous articles and you will learn how to make your code even more readable. This practice makes your code understandable even for those who do not have sufficient programming knowledge. Remember: You don't program for the machine, you program for other programmers.

Now let's look at two other methods.

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
   {
      string szRes[], sz1[];
      bool bLocal;
                                
      if (_LastError != ERR_SUCCESS) return;
      if ((index > SATURDAY) || (index < SUNDAY)) return;
      if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
      {
         m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
         m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
         bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
         for (char c0 = 0; (c0 <= 1) && (bLocal); c0++)
            if (bLocal = (StringSplit(szRes[0], ':', sz1) == 2))
               bLocal = (StringToInteger(sz1[0]) <= 23) && (StringToInteger(sz1[1]) <= 59);
         if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
      }
      if ((_LastError != ERR_SUCCESS) || (!bLocal))
      {
         Print("Error in the declaration of the time of day: ", EnumToString(index));
         ExpertRemove();
      }
   }

This part was described and discussed in the article Creating an EA that works automatically (Part 10): Automation (II), where I explained in detail how it works. If you need more details about this or if you don't understand the code, read the mentioned article as it will be very helpful. The next method (shown below) was described in the same article. In fact, all the work done here in relation to the C_ControlOfTime class can be seen in the article mentioned above. The only difference is in the constructor. This is because of what we will do next.

virtual const bool CtrlTimeIsPassed(void) final
   {
      datetime dt;
      MqlDateTime mdt;
                                
      TimeCurrent(mdt);
      dt = (mdt.hour * 3600) + (mdt.min * 60);
      return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
   }

With this explained, and if you have read the above-mentioned article, you can understand that the EA gets some additional parameters so that the user can configure the C_ControlOfTime class. But we need to take this functionality to a new class. As in the series of articles about the automatic EA, we will use a proxy class here. This is the subject for the next section.


C_Manager: the managing class

C_Manager is actually a pretty interesting class because it isolates most of the complexity associated with the EA code from the EA itself. That is, the EA will have only a few lines of code, and classes will manage and manipulate everything. This code will differ from the code that we saw in the series of automated EAs. We'll start with something basic. The main problem is that the EA will not actually work in all situations. Initially, we will focus on making the EA operation as secure as possible. Because layer we will need to make it run in a replay/simulator system as well. And this is the most difficult part of the system.

To prevent things from getting extremely complex, we will first make the EA work with what is available in the MetaTrader 5 platform. This means that the order system isn't really all that broad. It cannot be used, for example, in CrossOrder mode, that is, at this early stage we cannot use the order system to send orders for an asset other than the one the EA is working with.

Let's now look at the code for the C_Manager class in its current state of development. The code starts with the following lines:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_ControlOfTime.mqh"
#include "..\System EA\Auxiliar\Study\C_Study.mqh"
//+------------------------------------------------------------------+
#define def_Prefix "Manager"
#define def_LINE_PRICE  def_Prefix + "_PRICE"
#define def_LINE_TAKE   def_Prefix + "_TAKE"
#define def_LINE_STOP   def_Prefix + "_STOP"
//+------------------------------------------------------------------+
#define def_AcessTerminal       (*Terminal)
#define def_InfoTerminal        def_AcessTerminal.GetInfoTerminal()
#define def_AcessMouse          (*Study)
#define def_InfoMouse           def_AcessMouse.GetInfoMouse()
//+------------------------------------------------------------------+

Here we provide a few declarations to make further coding easier. Most likely, these definitions will undergo some changes in the future, given the fact that the system is quite complex.

Let's see where the class begins. Here is its coded:

class C_Manager : public C_ControlOfTime
{
   private :
      struct st00
      {
         double  FinanceStop,
                 FinanceTake;
         uint    Leverage;
         bool    IsDayTrade,
                 AccountHedging;                                         
      }m_Infos;
      struct st01
      {
         color   corPrice,
                 corTake,
                 corStop;
         bool    bCreate;
      }m_Objects;             
//+------------------------------------------------------------------+
      C_Terminal *Terminal;
      C_Study    *Study;

Here we declare private global variables of the class. For better organization, I divided the elements into structures. With these structures, it will be easier to modify and remove parts of the code later. This will definitely happen in future articles. For now, we will try to make the system work in its simplest form, making the most of the capabilities of MetaTrader 5. I repeat this because you should not take this system as final. It is still in its development phase.

Let's look at the first function in the C_Manager class. Its code is below:

bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
   {
      ulong tmp;
                                
      if (!CtrlTimeIsPassed()) return false;
      tmp = C_Orders::CreateOrder(type, Price, m_Infos.FinanceStop, m_Infos.FinanceTake, m_Infos.Leverage, m_Infos.IsDayTrade);
                                
      return tmp > 0;
   }

The function sends a pending order to the trading server. However, note that the order will only be sent if the time tracking system allows it to be sent. If the time is beyond the trading hours according to the schedule, the order will not be sent. Here we only need to indicate the price at which the order will be placed. Regardless of whether we are buying or selling, everything else is filled in according to the global class variables.

Below is another function, which is private to the class:

bool ToMarket(const ENUM_ORDER_TYPE type)
   {
      ulong tmp;
                                
      if (!CtrlTimeIsPassed()) return false;
      tmp = C_Orders::ToMarket(type, m_Infos.FinanceStop, m_Infos.FinanceTake, m_Infos.Leverage, m_Infos.IsDayTrade);
                                
      return tmp > 0;
   }

Here we send a request for execution at the market price, the best available price. In this case, all we need to indicate is whether we will buy or sell. Everything else is done based on the information from the global class variables. You may be thinking: How to initialize these global variables? Should this be done in the EA code? The answer is NO. Global variables present in the class should not be accessed in any way without proper care and knowledge of the class to which they belong.

We have some resources to initialize such variables. For now, we will use the simplest method - the constructor. The use of constructor to initialize class variables is, without a doubt, the best way available. However, we will change this later for reasons of practicality. For now, we can implement what we need in the constructor. Its code is as follows:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
          :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;
      switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
         case ACCOUNT_MARGIN_MODE_EXCHANGE      : szInfo = "EXCHANGE";           break;
      }
      Print("Detected Account ", szInfo);
   }

Although this code seems long, it is actually very simple considering that we will be initializing all the global variables present in the class. All this initialization is actually done based on the parameters provided by the EA code. So, here we initialize the system and check what type of account the EA works on. This will be important later. Now it just provides analytical information for us to understand the type of account. The information is provided in the MetaTrader 5 platform toolbar using a message created in this line.

We also have a destructor:

~C_Manager()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_Prefix);
   }

Although the code contains this line, it will hardly do anything. Well, we can't just expect everything to go smoothly. So, if any problem occurs, the destructor will delete the objects created by the class.


Conclusion

You may have noticed that the knowledge is reusable. We don't create something out of nowhere. We are always reusing and improving things. As a result, they improve over time. But the C_Manager class still has a method that needs to be explained. It is at the current phase of development, as the EA code. But in order to give a more adequate explanation and, most importantly, not make this article long and tedious to read and understand, I will devote the next article to a story about the method and the EA code.

Since the code has not been fully explained and I don't want you to use anything without sufficient knowledge, no codes will be included with this article. Sorry, but I don't think it would be right to do anything different.


Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/11482

Attached files |
Anexo.zip (130.63 KB)
Overcoming ONNX Integration Challenges Overcoming ONNX Integration Challenges
ONNX is a great tool for integrating complex AI code between different platforms, it is a great tool that comes with some challenges that one must address to get the most out of it, In this article we discuss the common issues you might face and how to mitigate them.
Indicator of historical positions on the chart as their profit/loss diagram Indicator of historical positions on the chart as their profit/loss diagram
In this article, I will consider the option of obtaining information about closed positions based on their trading history. Besides, I will create a simple indicator that displays the approximate profit/loss of positions on each bar as a diagram.
Population optimization algorithms: Evolution Strategies, (μ,λ)-ES and (μ+λ)-ES Population optimization algorithms: Evolution Strategies, (μ,λ)-ES and (μ+λ)-ES
The article considers a group of optimization algorithms known as Evolution Strategies (ES). They are among the very first population algorithms to use evolutionary principles for finding optimal solutions. We will implement changes to the conventional ES variants and revise the test function and test stand methodology for the algorithms.
Color buffers in multi-symbol multi-period indicators Color buffers in multi-symbol multi-period indicators
In this article, we will review the structure of the indicator buffer in multi-symbol, multi-period indicators and organize the display of colored buffers of these indicators on the chart.