Trade Events in MetaTrader 5
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.
Once a request arrives to the trade server, it passes the primary check:
- whether you have enough assets to perform the trade operation.
- whether the specified price are correct: open prices, Stop Loss, Take Profit, etc.
- whether the specified price is present in the price flow for instant execution.
- whether the Stop Loss and Take Profit levels are absent in the Market Execution mode.
- whether the volume is correct: minimum and maximum volume, step, maximum volume of the position (SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX, SYMBOL_VOLUME_STEP and SYMBOL_VOLUME_LIMIT).
- state of the symbol: quote or trade session, possibility of trading by the symbol, a specific mode of trading (e.g. only closing of positions), etc.
- state of the trade account: different limitations for specific types of accounts.
- other checks, depending on requested trade operation.
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.
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:
- a request to change a position (change its Stop Loss and/or Take Profit).
- 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:
- placing a pending order.
- execution of an instant order by the market price.
- modification of an order or position.
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:
- change of active orders,
- change of positions,
- change of deals,
- change of trade history.
Note that one operation can cause several events to occur. For example, triggering of a pending order leads to occurring of two events:
- appearing of a deal that is written to the trade history.
- 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).
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:
- the deal of 5 lots,
- the change of volume,
- 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.
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:
- performing of a deal on the basis of the order.
- rejection of the order by a dealer.
- canceling of the order on the trader's demand (manual request or an automatic one from a MQL5 program).
- expiration of the order, which is determined either by the trader when sending the request or by trade conditions of the given trade system.
- lack of assets on the trade account for performing the deal at the moment when conditions of its execution are satisfied.
- order is canceled due to the filling policy (a partially filled order is canceled).
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.
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.
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:
- declare the counters of order, positions and deal on the global scope.
- 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.
- initialize the counters of orders, positions and deals in the OnInit function.
- determine the handler functions in which will request the trade history to the cache.
- 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.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/232
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
see the last two lines?
should they be:
history_orders=HistoryOrdersTotal(); // okay, looks correct
deals=HistoryDealsTotal(); //a typing error, perhaps?
Thanks a lot. This really helped me solve some confusions.
Regards,
Umer Aziz