Trade Events in MetaTrader 5

MetaQuotes | 13 February, 2011

Introduction

All commands to perform trade operations are passed to the trade server from the MetaTrader 5 client terminal through sending requests. Each request should be correctly filled according to the requested operation; otherwise it won't pass the primary validation and won't be accepted by the server for further processing.

Requests accepted by the trade server are stored in the form of orders that can be either pending or instantly executed by the market price. Orders are stored on the server until they are filled or canceled. The result of an order execution is a deal.

A deal changes the trade position by a given symbol, it can open, close, increase, decrease or reverse the position. Therefore, an open position is always a result of performing one or more deals. More detailed information is given in the Orders, Positions, and Deals in MetaTrader 5 article.

This article describes concept, terms and processes that flow within the period from sending a request to moving of it in the trade history after it is processed.


Passing of Request from Client Terminal to Trade Server

To perform a trade operation you should send an order to the trade system. A request is always sent to trade server through submitting an order from the client terminal. The structure of a request must be filled correctly, regardless of how you trade - manually or using an MQL5 program.

To perform a trade operation manually, you should open the dialog window of filling a trade request by pressing the F9 key. When trading automatically through MQL5, requests are sent using the OrderSend() function. Since a lot of incorrect requests can cause an undesirable overloading of the trade server, each request must be checked before it is sent using the OrderCheck() function. The result of checking a request is placed to a variable described by the MqlTradeCheckresult structure.

Important: Each request is checked for correctness in the client terminal before it is sent to the trade server. Deliberately incorrect requests (to buy a million lots or buy by a negative price) are not passed outside the client terminal. It is done to protect the trade servers from a mass of incorrect request caused by a mistake in a MQL5 program.

Once a request arrives to the trade server, it passes the primary check:

An incorrect request that doesn't pass the primary check on the server is rejected. The client terminal is always informed about the result of checking of a request by sending a response. The response of the trade server can be taken from a variable of the MqlTradeResult type, which is passed as the second parameter in the OrderSend() function when sending a request.

Sending Trade Requests to Trade Server from Client Terminal

If a request passes the primary check for correctness, it will be placed to the request awaiting to be processed. As a result of processing a request, an order (command to perform a trade operation) is created in the trade server base. However, there are two types of requests that do not result in creation of an order:

  1. a request to change a position (change its Stop Loss and/or Take Profit).
  2. a request to modify a pending order (its price levels and expiration time.

The client terminal receives a message that the request is accepted and placed in the trade subsystem of the MetaTrader 5 platform. The server places the accepted request to the request queue for further processing which may result in:

The lifetime of a request in the server's queue has a limit of three minutes. Once the period is exceeded, the request is removed from the queue of requests.


Sending Trade Events from Trade Server to Client Terminal

The event model and the functions of event handling are implemented in the MQL5 language. It means that in a response to any predefined event the MQL5 execution environment calls the appropriate function - the event handler. For processing of trade events there is the predefined function OnTrade(); the code for working with orders, positions and deals must be placed within it. This function is called only for Expert Advisors, it won't be used in indicators and scripts even if you add there a function with the same name and type.

The trade events are generated by the server in case of:

Note that one operation can cause several events to occur. For example, triggering of a pending order leads to occurring of two events:

  1. appearing of a deal that is written to the trade history.
  2. moving of the pending order from the list active ones to the list of history orders (the order is moved to history).

Another example of multiple events is performing of several deals on the basis of a single order, in case the required volume cannot be obtained from a single opposite offer. The trade server creates and sends the messages about each event to the client terminal. That is why the OnTrade() function can be called for several time for a seemingly single event. This is a simple example of the procedure of processing of order in the trade subsystem of the MetaTrader 5 platform.

Here is an example: while a pending order for buying 10 lots of EURUSD waits to be executed, opposite offers for selling of 1, 4 and 5 lots appear. Those three requests together give the required volume of 10 lots, so they are executed one by one, if the fill policy allows performing trade operation in parts.

As a result of execution of 4 orders, the server will perform 3 deals of 1, 4 and 5 lots on the basis of existing opposite requests. How many trade events will be generated in this case? The first opposite request for selling one lot will lead to execution of deal of 1 lot. This is the first Trade event (1 lot deal). But the pending order for buying of 10 lots is also changed; now it's the order for buying of 9 lots of EURUSD. The change of volume of the pending order is the second Trade event (change of volume of a pending order).

Generation of Trade Events

For the second deal of 4 lots the other two Trade events will be generated, a message about each of them will be sent to the client terminal that initiated the initial pending order for buying of 10 lots of EURUSD.

The last deal of 5 lots will lead to occurring of three trade events:

  1. the deal of 5 lots,
  2. the change of volume,
  3. moving of the order to the trade history.

As a result of execution of the deal, the client terminal receives 7 trade events Trade one after another (it is assumed that connection between the client terminal and the trade server is stable and no messages are lost). Those messages must be processed in an Expert Advisor using the OnTrade() function.

Important: Each message about a trade even Trade may appear as a result of one or several requests. Each request can lead to occurring of several trade events. You cannot rely on the statement "One request - one Trade event", since the processing of events may be performed in several stages and each operation may change the state of orders, positions and the trade history.


Processing of Orders by Trade Server

All orders that wait for their execution will be moved to the history in the end - either the condition for their execution will be satisfied, or they will be canceled. There are several variants of an order canceling:

Regardless of the reason why an active order is moved to the history, the message about the change is sent to the client terminal. Messages about the trade event are not too all the client terminals, but to ones connected to the corresponding account.


Important: The fact of accepting of a request by the trade server doesn't always lead to execution of the requested operation. It means that the request has passed the validation after when it came to the trade server.

That is why the documentation of the OrderSend() function says:

Returned value

In case of a successful basic check of a request the OrderSend() function returns true - this is not a sign of successful execution of a trade operation. For a more detailed description of the functions execution result, analyze the fields of the structure MqlTradeResult.


Updating of Trade and History in Client Terminal

Messages about trade events and changes in the trade history come through separate channels. When sending a request for buying using the OrderSend() function, you can get the ticket of the order, which is created as a result of successful validation of request. At the same time, the order itself might not appear in the client terminal and an attempt to select it using the OrderSelect() may fail.

All the messages from the trade server arrive to the client terminal independently

At the figure above, you can see how the trade server tells the order ticket to the MQL5 program, but the message about the trade event Trade (appearing of new order) has not arrived yet. The message about the change of the list of active orders has not arrived as well.

There can be a situation, when the Trade message about appearing of a new order arrives to the program when a deal on its basis has been already performed, therefore the order is already absent in the list of active orders, it is in the history. This is a real situation, since the speed of processing of requests is much higher comparing to the current speed of delivering a message through a network.


Handling of Trade Events in MQL5

All operations on the trade server and sending of messages about trade events are performed asymmetrically. There is only one sure method to find out what exactly has been changed on the trade account. This method is to memorize the trade state and trade history and then compare it to the new state.

The algorithm of tracking the trade events in Expert Advisors is the following:

  1. declare the counters of order, positions and deal on the global scope.
  2. determine the depth of trade history that will be requested to the MQL5 program cache. The more history we load to the cache, the more resources of the terminal and computer are consumed.
  3. initialize the counters of orders, positions and deals in the OnInit function.
  4. determine the handler functions in which will request the trade history to the cache.
  5. There, after loading of the trade history, we're also going to find what has happened to the trade account by comparing the memorized and the current state.

This is the simplest algorithm, it allows discovering whether the number of open positions (order, deals) was changed and what is the direction of the change. If there are changes, then we can further get more detailed information. If the number of orders is not changed, but the orders themselves are modified, it needs a different approach; therefore, this variant is not covered in this article.

Changes of the counter can be checked in the OnTrade() and OnTick() functions of an Expert Advisor.

Let's write a program example step by step.

1. The counter of order, deals and positions on the global scope.

int          orders;            // number of active orders
int          positions;         // number of open positions
int          deals;             // number of deals in the trade history cache
int          history_orders;    // number of orders in the trade history cache
bool         started=false;     // flag of initialization of the counters

2. The depth of the trade history to be loaded in the cache is set in the input variable days (load the trade history for the number of days specified in this variable).

input    int days=7;            // depth of the trade history in days

//--- set the limit of the trade history on the global scope
datetime     start;             // start date of the trade history in cache
datetime     end;               // end date of the trade history in cache

3. Initialization of the counters and the limits of the trade history. Take the initialization of the counters out to the InitCounters() function for better readability of the code:

int OnInit()
  {
//---
   end=TimeCurrent();
   start=end-days*PeriodSeconds(PERIOD_D1);
   PrintFormat("Limits of the trade history to be loaded: start - %s, end - %s",
               TimeToString(start),TimeToString(end));
   InitCounters();
//---
   return(0);
  }

The InitCounters() function tries to load the trade history in the cache, and in case of success, it initializes all the counters. Also, if the history is loaded successfully, the value of the global variable 'started' is set to 'true', what indicates that the counters has been successfully initialized.

//+------------------------------------------------------------------+
//|  initialization of the counters of positions, orders and deals   |
//+------------------------------------------------------------------+
void InitCounters()
  {
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %s to %s to the cache. Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }
//--- get current values
   orders=OrdersTotal();
   positions=PositionsTotal();
   deals=HistoryDealsTotal();
   history_orders=HistoryOrdersTotal();
   started=true;
   Print("The counters of orders, positions and deals are successfully initialized");
  }

4. Check of changes on the trade account state is performed in the OnTick() and OnTrade()handlers. The 'started' variable is checked first - if its value is 'true', the SimpleTradeProcessor() function is called, otherwise the function of initialization of the counters InitCounters() is called.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }
//+------------------------------------------------------------------+
//| called when the Trade event occurs                               |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }

5. The SimpleTradeProcessor() function checks whether the number of orders, deals and positions has been changed. After conducting all the checks, we call the CheckStartDateInTradeHistory() function that moves the 'start' value closer to the current moment if it is necessary.

//+------------------------------------------------------------------+
//| simple example of processing changes in trade and history        |
//+------------------------------------------------------------------+
void SimpleTradeProcessor()
  {
   end=TimeCurrent();
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %s to %s to the cache. Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }

//--- get current values
   int curr_orders=OrdersTotal();
   int curr_positions=PositionsTotal();
   int curr_deals=HistoryDealsTotal();
   int curr_history_orders=HistoryOrdersTotal();

//--- check whether the number of active orders has been changed
   if(curr_orders!=orders)
     {
      //--- number of active orders is changed
      PrintFormat("Number of orders has been changed. Previous number is %d, current number is %d",
                  orders,curr_orders);
     /*
       other actions connected with changes of orders
     */
      //--- update value
      orders=curr_orders;
     }

//--- change in the number of open positions
   if(curr_positions!=positions)
     {
      //--- number of open positions has been changed
      PrintFormat("Number of positions has been changed. Previous number is %d, current number is %d",
                  positions,curr_positions);
      /*
      other actions connected with changes of positions
      */
      //--- update value
      positions=curr_positions;
     }

//--- change in the number of deals in the trade history cache
   if(curr_deals!=deals)
     {
      //--- number of deals in the trade history cache has been changed
      PrintFormat("Number of deals has been changed. Previous number is %d, current number is %d",
                  deals,curr_deals);
      /*
       other actions connected with change of the number of deals
       */
      //--- update value
      deals=curr_deals;
     }

//--- change in the number of history orders in the trade history cache
   if(curr_history_orders!=history_orders)
     {
      //--- the number of history orders in the trade history cache has been changed
      PrintFormat("Number of orders in the history has been changed. Previous number is %d, current number is %d",
                  history_orders,curr_history_orders);
     /*
       other actions connected with change of the number of order in the trade history cache
      */
     //--- update value
     history_orders=curr_history_orders;
     }
//--- check whether it is necessary to change the limits of trade history to be requested in cache
   CheckStartDateInTradeHistory();
  }

The CheckStartDateInTradeHistory() function calculates the start date of request of the trade history for the current moment (curr_start) and compares it to the 'start' variable. If the difference between them is greater than 1 day, then 'start' is corrected and the counters of the history orders and deals are updated.

//+------------------------------------------------------------------+
//|  Changing start date for the request of trade history            |
//+------------------------------------------------------------------+
void CheckStartDateInTradeHistory()
  {
//--- initial interval, as if we started working right now
   datetime curr_start=TimeCurrent()-days*PeriodSeconds(PERIOD_D1);
//--- make sure that the start limit of the trade history period has not gone 
//--- more than 1 day over intended date
   if(curr_start-start>PeriodSeconds(PERIOD_D1))
     {
      //--- we need to correct the date of start of history loaded in the cache
      start=curr_start;
      PrintFormat("New start limit of the trade history to be loaded: start => %s",
                  TimeToString(start));

      //--- now load the trade history for the corrected interval again
      HistorySelect(start,end);

      //--- correct the counters of deals and orders in the history for further comparison
      history_orders=HistoryOrdersTotal();
      deals=HistoryDealsTotal();
     }
  }

The full code of the Expert Advisor DemoTradeEventProcessing.mq5 is attached to the article.


Conclusion

All operations in the on-line trading platform MetaTrader 5 are performed asynchronously, and the messages about all changes on a trade account are sent independently from each other. Therefore, there is no point in trying to track single events basing on the rule "One request - one trade event" If you need to accurately determine what exactly is changed when a Trade even comes, then you should analyze all your deals, positions and orders at each call of the OnTrade handler by comparing their current state with the previous one.