Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Orders, Positions and Deals in MetaTrader 5

Orders, Positions and Deals in MetaTrader 5

MetaTrader 5Trading | 1 February 2011, 16:13
98 169 9
MetaQuotes
MetaQuotes

Trading terms

The ultimate goal of a trader is to extract profits through the means of trading operations on the financial markets. This article describes the terms and processes of the MetaTrader 5 trading platform, the knowledge of which is necessary for a proper understanding of the work of trade functions of the MQL5 language.

  • Orders — are the trade operation requests, received by the trading server, formulated in compliance to the MetaTrader 5 platform requirements. If the request is incorrect, it will not appear on the trading platform in the form of an order. Orders can be of immediate execution, such as to buy or sell a certain volume, at the current market price on a specified financial instrument. Another type of orders - are pending orders, which contain an order to commit a trading operation under the presence of certain condition. Pending orders may also contain a time restriction on their actions - the order expiration date.

    Orders and positions in the MetaTrader 5 terminal

    The placed (pending) orders, that are waiting for the conditions of their execution or cancellation, are shown on the "Trade" tab in the terminal. These orders can be modified or canceled. The placing, cancellation, and modification of orders is done using the OrderSend() function. If the order was canceled or reached the order expiration time, or if the order was executed, it moves into the orders history. Executed and canceled orders are shown in the "History" tab of the client terminal. Orders from the history are not available for modification.

  • Deals - are the result of an execution of an order (a command to commit the deal). Each deal is based on a one particular order, but a single order can generate a set of deals. For example, an order to buy 10 lots may be carried out by several successive deals with partial filling. The deals are always stored in the trading history and cannot be modified. In the terminal, the deals are displayed in the "History" tab.

    Transactions in the MetaTrader 5 terminal

  • Positions are the contracts, bought or sold on a financial instrument. A long position (Long) is formed as a result of buys in anticipation of a price increase, a short position (Short) is the result of the sale of asset, in the anticipation of future price decrease. For each account, for every financial instrument, there can be only one position. For each symbol, at any given time, there can be only one open position - long or short.

    Historical orders in the MetaTrader 5 terminal

    The position volume may increase as a result of a new trading operation in the same direction. Meaning that the volume of the long positions will be increased after the new buy (Buy deal), and will be decreased after the sale (Sell deal). The position is closed, if the volume of commitments became equal to zero as a result of trading operation. Such operation is called closing the position.

Note: Active orders and positions are always displayed on the "Trade" tab, and the deals and orders from the history are always reflected in the "History" tab. The active order from the "Trade" tab should not be confused with the historical orders from the "History" tab.


How the terminal receives and stores the trade information from the server

The terminal stores the trading history in a special base, and receives only the missing history of deals and completed orders on the trading account, at each connection to the trading server. This is done to save up on traffic. When closing the MetaTrader 5 client terminal or changing the current active account, the entire history is recorded on the hard disk and read from at the time of the next launch of the terminal.

All databases are recorded to the disk in an encrypted form and the encryption key is dependent to the computer on which the terminal is installed. This protects the user of the terminal against unauthorized access to his data, in case of copying.

During the connection to the account, the terminal loads the saved account base, with the history of the account, and sends the trading server a request to synchronize its own database of history with the history of the account on the trading server. Further, after a successful connection to the account, the trading server sends the terminal a report about the ongoing trading events, related to this account.

Trading event are the following changes in the account:

  • withdrawal and balance operations.
  • charge of commissions, swaps and taxes.
  • placing, deletion and modification of orders.
  • the execution of deals based on orders.
  • opening and closing of positions.
  • change in the volume and direction of positions.

In the case of termination of the connection with the trading server, the terminal periodically attempts to reconnect. After the reconnection with the server, the terminal requests all of the recent changes in the trading history to maintain the integrity of data in its own database of the history.

The trading history, displayed in the "History" tab of the terminal, is taken from the base of the terminal history, and the changes of the period, displayed in the terminal of history can only increase the range of the history, stored in this database. Decreasing the period of the displayed history does not lead to a physical removal of the history from the base of the terminal.

Installation of the interval of the displayed trading history

This means that the installation of a shorter interval of the displayed history, does not reduce the depth of the stored trading history. But if we specify a wider interval range, for the display in the "History" tab, then such an action could lead to a request, from the trading server, of a more profound history, if the terminal's own base does not yet have the requested data for that period.

The general scheme of interaction between the terminal and the MetaTrader 5 trading server is demonstrated at following Figure:

The client terminal sends a synchronization request to its own trading history base, during the start of the terminal, during the reconnection with the server after a connection failure, during a switch from one account to another, and during the direct request for the missing trading history.

In its turn, the trading server independently, without any requests from the terminal, sends a client messages about the trading events, taking place on the account: the changes of the state of orders and positions, the conduction of deals based on orders, the charging of commissions, the balance and withdrawal of money, and so on.


Access to the trading history from the MQL5-program

The terminal can operate simultaneously with a number of indicators, scripts, and EAs, and all of these programs can request the information they need about trading: orders, deals, and positions. The direct work of the mql5-program with the database of the terminal is excluded, due to the considerations of overall stability, security and performance.

Each mql5-program by request receives for its work a "model" of the trading environment in its cache. A cache is a special area of memory for a fast access to data. For example, before beginning processing the order, the order needed must be obtained in the cache of the mql5-program. All of the further work, when referring to the order, will be made with the cached copy of that order.

The work with the positions, deals, and orders from the history is carried out in a similar way. The general scheme of obtaining the trading information from the MQL5 program is shown at figure:

Before the data about the trading history becomes available for the processing of mql5-program, they must be requested from the terminal database. After the request, the obtained data will be placed in its own cache of the mql5-program.

Note: the data in the cache is not automatically synchronized with the terminal database, and therefore, it must be constantly updated to maintain an appropriate status of the data in the cache.

There is a possibility of consequences if the cache is used improperly.

  • if the data requested could not be obtained, the cache will be empty and will not contain the necessary data.
  • if the data in the cache required updates, but the updating was not requested, then working with such data can lead to unpredictable results. For example, the data on the current position has not been updated, and the program does not know anything about the open position for the given symbol and about the growing loss for it.


The function for working with the cache

The trading history may contain thousands of executed orders and deals that are not needed for current work of the mql5-program. Therefore, the work with cache is built on the principle of requests the cache always contains the information that was uploaded at the last connection to the database of the terminal. If you need to obtain the whole history of orders and deals, you need to explicitly request it by specifying the desired interval.

For each type of information, an independent cache is formed. The data about the orders is stored in the order's cache, the information about the positions is stored in the position's cache, the data on deals and orders is stored in the respective instances of the cache's history.

Before requesting the information from the cache, it needs to be filled.

Note: Any request for filling the cache previously clears it, regardless of the result of the requests' execution.

The trading functions can be separated into two categories: the functions for filling the cache and the functions for reading the information from the cache.


The function for filling the cache

For processing the trading history, it must first be obtained and located in the appropriate cache. Functions that form a cache can be divided into two subgroups.

The function for filling the trading cache (active orders and positions):

  • The OrderSelect(ticket) - copies the active order by its ticket (from the terminal base) into the cache of the current order for the further request of its properties using the OrderGetDouble(), OrderGetInteger() and OrderGetString() functions.
  • The OrderGetTicket(index) - copies, from the terminal base of the active order, by its index in the orders list of orders terminal base into the cache of the current orders for further request to the properties using the OrderGetDouble(), OrderGetInteger() and OrderGetString() functions. The total number of orders in the base of the terminal can be obtained using the OrdersTotal() function.
  • The PositionSelect(symbol) - copies the open position by the symbol name (from the base of the terminal) into the cache for the further request of its properties using the PositionGetDouble(), PositionGetInteger() and PositionGetString() functions.
  • The PositionGetSymbol(index) - copies the open position by its index in the position list (from the base of the terminal) of the terminal base into the cache for the further request of its properties using the PositionGetDouble(), PositionGetInteger() and PositionGetString() functions. The total number of positions in the base of the terminal can be obtained by the PositionsTotal() function.

The function of filling the history cache:

  • The HistoryOrderSelect(ticket) - copies the history order by its ticket into the cache of the history orders (from the base of the terminal) for the further calls to its properties by the HistoryOrderGetDouble(), HistoryOrderGetInteger() and HistoryOrderGetString() functions.
  • The HistoryDealSelect(ticket) - copies the deal by its ticket into the deals cache (from the base of the terminal) for the further calls to its properties by the HistoryDealGetDouble(), HistoryDealGetInteger() and HistoryDealGetString() functions .

We need to separately consider the two functions, which affect the available, in the cache, trading history in general:

  • The HistorySelect(start, end) - fills the history cache with deals and orders for the specified interval of the server's time. From the results of the execution of this function, depends the values, that are returned from HistoryDealsTotal() and HistoryOrdersTotal().
  • The HistorySelectByPosition (position_ID) - fills the history cache with deals and orders, having the specified identifier position. The result of the execution of this function, also affects the HistoryDealsTotal() and HistoryOrdersTotal().


OrderSelect and OrderGetTicket

The OrderSelect(ticket) and OrderGetTicket() general functions work in the same way, - they fill the cache of active orders with one single order. The OrderSelect(ticket) is intended for the case where a ticket order is known in advance. The OrderGetTicket(), in conjunction with OrdersTotal() allows for the examination of all of the available orders in the base terminal of orders.

After a call to any of these functions, the cache of the active orders contains the information of only one order, if the order is successfully selected. Otherwise, there is nothing in the cache of active orders. The result of the execution of the function OrdersTotal() does not change - it always returns the actual number of active orders in the base of the terminal, regardless of whether the cache is full.


PositionSelect and PositionGetSymbol

Just like for the orders, these two functions also work in the same way for positions - they fill the cache of positions with a single position. The PositionGetSymbol(index) requires the number in the list of the positions base, as a parameter, and the PositionSelect(symbol) fills the cache based on the symbol name, on which the position is opened. The name of the symbol, in turn, can be obtained by the PositionGetSymbol(index) function.

After performing any of these functions, the cache of positions contains data only on one position, if the function is executed successfully. Otherwise, there is nothing in the cache of positions. The result of the execution of the PositionsTotal() function does not depend on whether the cache is filled, - it always returns the actual number of open positions in the base terminal for all symbols.


HistoryOrderSelect

The HistoryOrderSelect(ticket) chooses into the cache the historical order from the base of the terminal by its ticket. The function is intended for being used when the ticket of the required order is known in advance.

If the execution is successful, the cache will contain a single order, and the HistoryOrdersTotal() function return a single unit. Otherwise, the cache of historical orders will be empty and the HistoryOrdersTotal() function will return a zero.


HistoryDealSelect

The HistoryDealSelect(ticket) selects the deal from the base terminal based by its ticket. The function is intended for being used when the ticket of the deal is known in advance.

If the execution is successful, the cache will contain a single deal, and the HistoryDealsTotal() function will return 1. Otherwise, the cache of deal will be empty and the HistoryDealsTotal() function will return a zero.


The function for obtaining information from the cache

Before requesting information about the properties of the position, deal or order, it is necessary to update the corresponding cache of the mql5-program. This is due to the fact that the requested information may have already been updated, and this means that the copy, stored in the cache, is already outdated.

  • Orders

    In order to obtain information on active orders, it must first be copied into the cache of active orders of one of the two functions: OrderGetTicket() or OrderSelect(). It is for the order, which is stored in the cache, that the property values will be given out, when the corresponding functions are called:

    1. OrderGetDouble(type_property)
    2. OrderGetInteger(type_property)
    3. OrderGetString(type_property)

These functions obtain all of the data from the cache, therefore, in order to guarantee the obtainment of accurate data for the order, it is recommended to call the function that fills the cache.

  • Positions

    For obtaining the information about a position, it must be previously selected and copied into the cache, using one of the two functions: PositionGetSymbol or PositionSelect. It is from this cache, that the property values of position will be given out, when the corresponding functions are called:

    1. PositionGetDouble(type_property)
    2. PositionGetInteger(type_property)
    3. PositionGetString(type_property)

Since these functions receive all of their data from the cache, then in order to guarantee the obtainment of accurate data for the position, it is recommended to call the function that fills the cache of positions.

  • Historical orders

    For obtaining information about an order from the history, it is required to first create the cache of historical orders, using one of the three functions: HistorySelect(start, end), HistorySelectByPosition() or HistoryOrderSelect(ticket). If the implementation is successful, the cache will store the number of orders, returned by the HistoryOrdersTotal() function. The access to the properties of these orders is carried out by each element on the ticket, using the appropriate function:

    1. HistoryOrderGetDouble(ticket_order, type_property)
    2. HistoryOrderGetInteger(ticket_order, type_property)
    3. HistoryOrderGetString(ticket_order, type_property)

The ticket of the historical order can be found out using the HistoryOrderGetTicket(index) function, by its index in the cache of historical orders. In order to have a guaranteed receipt of accurate data on the order, it is recommended to call the function that fills the cache of historical orders.

  • Deals

    For obtaining information about a specific deal in the history, it is needed to first create the deals cache, using one of the three functions: HistorySelect (start, end), HistorySelectByPosition() or HistoryDealSelect (ticket). If the function implementation is successful, the cache will store the deal in the amount returned by the function HistoryDealsTotal(). Access to the properties of these deals is carried out, based on the ticket, using the appropriate functions:

    1. HistoryDealGetDouble(ticket_deals, type_property)
    2. HistoryDealGetInteger(ticket_deals, type_property)
    3. HistoryDealGetString(ticket_deals, type_property)

The ticket of the deals can be obtained, using the HistoryDealGetTicket(index) function, by its index in the cache of deals. In order to have a guaranteed receipt of accurate data about the deal, it is recommended to call the function that fills the deals cache.


The function for obtaining the ticket from the cache history

The HistoryOrderGetTicket (index) return the ticket of the historical order, by its index from the cache of the historical orders (not from the terminal base!). The obtained ticket can be used in the HistoryOrderSelect (ticket) function, which clears the cache and re-fill it with only one order, in the case of success. Recall that the value, returned from HistoryOrdersTotal() depends on the number of orders in the cache.

The HistoryDealGetTicket(index) returns the ticket of the deal by its index from the cache of deals. The ticket of the deal can be used by the function HistoryDealSelect(ticket), which clears the cache, and re-fills the cache with only one deal, in the case of success. The value, returned by the HistoryDealsTotal() function depends on the number of deals in the cache.

Note: Before calling the HistoryOrderGetTicket (index) and HistoryDealGetTicket (index) functions, you need to fill the history cache with historical orders and deals in a sufficient volume. To do this, use one of the functions: HistorySelect (start, end), HistorySelectByPosition (position_ID), HistoryOrderSelect (ticket), and HistoryDealSelect (ticket).


Obtaining information using active orders

Checking the current active orders is a standard procedure. If it is needed to obtain information about some specific order, then, knowing its ticket, this can be done using the function OrderSelect(ticket).

bool selected=OrderSelect(ticket);
if(selected)
  {
   double price_open=OrderGetDouble(ORDER_PRICE_OPEN);
   datetime time_setup=OrderGetInteger(ORDER_TIME_SETUP);
   string symbol=OrderGetString(ORDER_SYMBOL);
   PrintFormat("Ордер #%d for %s was set at %s",ticket,symbol,TimeToString(time_setup));
  }
else
  {
   PrintFormat("Error selecting order with ticket %d. Error %d",ticket, GetLastError());
  }

In the above example, it is assumed that the ticket of the order is known in advance, for example, it is obtained from global variable. In general cases, however, the ticket information is absent, and thus we need to turn to the help of the OrderGetTicket(index) function, which also selects one order and places it into the cache, but only the order number, in the list of current orders, needs to be specified as the parameter.

The overall algorithm for working with orders (analogous with deals and positions) is the following:

  1. Obtain the total number of orders, using the OrdersTotal() function.
  2. Organize the loop through a search of all of the orders, by their indexes in the list.
  3. Copy, one by one, each order into the cache, using the OrderGetTicket() function.
  4. Obtain the correct order data from the cache, using the OrderGetDouble(), OrderGetInteger(), and OrderGetString() functions. If needed, analyze the obtained data and take the appropriate actions.

Here is a brief example of such an algorithm:

input long my_magic=555;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- obtain the total number of orders
   int orders=OrdersTotal();
//--- scan the list of orders
   for(int i=0;i<orders;i++)
     {
      ResetLastError();
      //--- copy into the cache, the order by its number in the list
      ulong ticket=OrderGetTicket(i);
      if(ticket!=0)// if the order was successfully copied into the cache, work with it
        {
         double price_open  =OrderGetDouble(ORDER_PRICE_OPEN);
         datetime time_setup=OrderGetInteger(ORDER_TIME_SETUP);
         string symbol      =OrderGetString(ORDER_SYMBOL);
         long magic_number  =OrderGetInteger(ORDER_MAGIC);
         if(magic_number==my_magic)
           {
            //  process the order with the specified ORDER_MAGIC
           }
         PrintFormat("Order #%d for %s was set out %s, ORDER_MAGIC=%d",ticket,symbol,TimeToString(time_setup),magic_number);
        }
      else         // call OrderGetTicket() was completed unsuccessfully
        {
         PrintFormat("Error when obtaining an order from the list to the cache. Error code: %d",GetLastError());
        }
     }
  }


Obtaining the information on open positions

The constant monitoring of open positions is not just a standard procedure, but it should certainly be implemented in each instance. For obtaining information on specific positions, it is sufficient enough to know the name of the instrument by which it is open. To do this, use the function PositionSelect(symbol). For those cases, in which the EA is working on only one symbol (on the symbol of the chart, to which it is attached), the name of the symbol can be obtained by a Symbol() function or from a predefined variable _Symbol.

//--- we will look for the position by the symbol of the chart, on which the EA is working
   string symbol=Symbol();
//--- attempt to get the position
   bool selected=PositionSelect(symbol);
   if(selected) // if the position is selected
     {
      long pos_id            =PositionGetInteger(POSITION_IDENTIFIER);
      double price           =PositionGetDouble(POSITION_PRICE_OPEN);
      ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      long pos_magic         =PositionGetInteger(POSITION_MAGIC);
      string comment         =PositionGetString(POSITION_COMMENT);
      PrintFormat("Position #%d by %s: POSITION_MAGIC=%d, price=%G, type=%s, commentary=%s",
                 pos_id, symbol, pos_magic, price,EnumToString(type), comment);
     }

   else        // if selecting the position was unsuccessful
     {
      PrintFormat("Unsuccessful selection of the position by the symbol %s. Error",symbol,GetLastError());
     }
  }

In a general case, the information on the symbol can be obtained, using the PositionGetSymbol (index) function, which selects one position and places it into the cache. As a parameter, it is necessary to specify the index of position in the list of open positions. This is best done through a search of all positions in the loop.

The overall algorithm for working with positions:

  1. Obtain the total number of positions, using the PositionsTotal() function.
  2. Organize the loop through searching all of the positions by their indexes in the list.
  3. Copy, one by one, each position into the cache, using the PositionGetSymbol() function.
  4. Obtain the required position data from the cache using the PositionGetDouble(), PositionGetInteger(), and PositionGetString() functions. If needed, analyze the obtained data and take the appropriate actions.

An example of such an algorithm:

#property script_show_inputs

input long my_magic=555;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- obtain the total number of positions
   int positions=PositionsTotal();
//--- scan the list of orders
   for(int i=0;i<positions;i++)
     {
      ResetLastError();
      //--- copy into the cache, the position by its number in the list
      string symbol=PositionGetSymbol(i); //  obtain the name of the symbol by which the position was opened
      if(symbol!="") // the position was copied into the cache, work with it
        {
         long pos_id            =PositionGetInteger(POSITION_IDENTIFIER);
         double price           =PositionGetDouble(POSITION_PRICE_OPEN);
         ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         long pos_magic         =PositionGetInteger(POSITION_MAGIC);
         string comment         =PositionGetString(POSITION_COMMENT);
         if(pos_magic==my_magic)
           {
           //  process the position with a specified POSITION_MAGIC
           }
         PrintFormat("Position #%d by %s: POSITION_MAGIC=%d, price=%G, type=%s, commentary=%s",
                     pos_id,symbol,pos_magic,price,EnumToString(type),comment);
        }
      else           // call to PositionGetSymbol() was unsuccessful
        {
         PrintFormat("Error when receiving into the cache the position with index %d."+
                     " Error code: %d", i, GetLastError());
        }
     }
  }


Rules for working with the history cache

Often, the code for working with the history cache is written by the programmer in such a way, that it works smoothly only if the history contains 5-10 deals and orders. A typical example of a wrong approach - loading the entire trading history into the cache, and processing it in a loop, searching through all of the orders and trades:

//---
   datetime start=0;           // initial time set to 1970 year
   datetime end=TimeCurrent();  // the ending time set to the current server time
//--- request into the cache of the program the entire trading history
   HistorySelect(start,end);
//--- obtain the number of all of the orders in the history
   int history_orders=HistoryOrdersTotal();
//--- now scan through all of the orders
   for(int i=0;i<history_orders;i++)
     {
     //  processing each order in the history
     }   

    ...
       
//--- obtain the number of all deals in the history
   int deals=HistoryDealsTotal();
//--- now scan through all of the deals
   for(int i=0;i<deals;i++)
     {
     //  process each deal in the history
     }

The attempt to handle all of the trading history, in the majority of cases, is wrong. When the number of processed deals/orders becomes around thousands and tens of thousands, the work of the program drastically slows down.

Note: always cautiously refer to all of the cases of calling the HistorySelect() function! Unthoughtful and excessive loading of all of the available trading history into the cache of the mql5-program, degrades its performance.

This is primarily important for testing - the user discovers that the tester suddenly becomes thoughtful, and begins looking for the reasons for this in the client terminal. Therefore, firstly always think about optimizing the code of the program MQL5 (EA and indicators, which are called from the EA). Do not rely on the fact that the computer is made of iron and has many kernels.

For the proper work of the EA and the indicator online, this is just as important. A non-optimal code of the program can paralyze the work of even the most powerful computer.

The correct algorithm for working with the trading history:

  1. Determine the need for requesting the trading history into the cache. If this is not necessary, then do not perform the following actions.
  2. Determine the final date of the trading history (perhaps the history up to the moment is not necessary).
  3. Calculate the initial date of the trading history, starting from the ending date. Usually, the EAs require the trading history, no deeper than a single day or week.
  4. Obtain the tickets of deals and historical orders for obtaining the properties, by the known tickets:
    • HistoryOrderGetDouble()
    • HistoryOrderGetInteger()
    • HistoryOrderGetString()
    • HistoryDealGetDouble()
    • HistoryDealGetInteger()
    • HistoryDealGetString()
  5. If the tickets are not known, and if it is necessary, organize a cycle through sorting.
  6. In the loop, obtain the ticket for each deal/order from the cache of the trading history, by the index (HistoryOrderGetTicket(Index) and HistoryDealGetTicket(Index)).
  7. Obtain the necessary properties of orders and deals by the known ticket (see point 4).

An example of a code for this algorithm:

//--- the variable, which is set in true only during the change in the trading history
   bool TradeHistoryChanged=false;
//--- here we check for the changes in the history and put out the TradeHistoryChanged=true if needed
//... the needed code

//--- check  if there are changes in the trading history or not
   if(!TradeHistoryChanged) return;

//--- if the history has changed, then it makes sense to load it into the cache 
//--- the ending time set for the current server time
   datetime end=TimeCurrent();
//--- the beginning time is set to 3 days ago
   datetime start=end-3*PeriodSeconds(PERIOD_D1);
//--- request in the cache of the program, the trading history for the last 3 days
   HistorySelect(start,end);
//--- obtain the number of orders in the cache of the history
   int history_orders=HistoryOrdersTotal();
//--- now scan through the orders
   for(int i=0;i<history_orders;i++)
     {
      //--- obtain the ticket of the historical order
      ulong ticket=HistoryOrderGetTicket(i);
      //--- work with this order - receive its problems
      long order_magic=HistoryOrderGetInteger(ticket,ORDER_MAGIC);
      // obtain the rest of the properties for the order by the ticket
      // ...
     }

The basic idea presented by this example - is that first you must verify the fact of changes taking place in the trading history. One of the options is to, inside the OnTrade() function, set for the global variable TradeHistoryChanged, the value of true, since the Trade event always returns with any type of trading event.

If the trading history has not changed, then there is no need to upload the trading history into the cache again, and waste resources of the CPU. This is logical and does not require any explanation. If the trading history has changed, then we upload only the necessary part of it, and go through each deal/order only once. Avoid unnecessary repeat cycles.

Note: each request to the cache of the entire trading history, done by the function HistorySelect(), and each cycle of processing deals and orders from the history, have to be grounded. Otherwise, your computer's resources will be spent inefficiently.

Examples of correct and incorrect work with the trading history are attached to this article, as files WrongWorkWithHistory.mq5 and RightWorkWithHistory.mq5.


Obtaining information by orders from the history

Working with historical orders is almost no different from working with the active orders, with but one exception. If the number of active orders in the cache of the mql5-program can not be more than one, then the result HistoryOrdersTotal(), and the number of historical orders in the cache, depends on how much trading history has been loaded by the HistorySelect(start, end), HistorySelectByPosition() or HistoryOrderSelection() function.

Note: If the trading history has not been loaded into the cache of the mql5-program by one of the functions HistorySelect(), HistorySelectByPosition() or HistoryOrderSelect(), then working with historical orders and deals is impossible. Be sure to request the required history of deals and orders before receiving the data on trading history.

For example, we provide a script, which searches for the last order of the last day, and displays information for it.

// --- determining the time intervals of the required trading history
   datetime end=TimeCurrent();                // current server time
   datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning for 24 hours ago
//--- request in the cache of the program the trading history for a day
   HistorySelect(start,end);
//--- receive the number of orders in the history
   int history_orders=HistoryOrdersTotal();
//--- obtain the ticket of the order, which has the last index in the list, from the history
   ulong order_ticket=HistoryOrderGetTicket(history_orders-1);
   if(order_ticket>0) // obtain in the cache the historical order, work with it
     {
      //--- order status
      ENUM_ORDER_STATE state=(ENUM_ORDER_STATE)HistoryOrderGetInteger(order_ticket,ORDER_STATE);
      long order_magic      =HistoryOrderGetInteger(order_ticket,ORDER_MAGIC);
      long pos_ID           =HistoryOrderGetInteger(order_ticket,ORDER_POSITION_ID);
      PrintFormat("Order #%d: ORDER_MAGIC=#%d, ORDER_STATE=%d, ORDER_POSITION_ID=%d",
                  order_ticket,order_magic,EnumToString(state),pos_ID);
     }
   else              // unsuccessful attempt to obtain the order
     {
      PrintFormat("In total, in the history of %d orders, we couldn't select the order"+
                  " with the index %d. Error %d",history_orders,history_orders-1,GetLastError());
     }

In more general cases, it is needed to sort through the orders in the loop from the cache, and analyze them. The general algorithm will be as follows:

  1. Determine the time ranges of the sufficient history, if the history is loaded by the function HistorySelect() - it is not recommended to load the entire trading history into the cache.
  2. Load into the cache of the program, the trading history HistorySelect(), HistorySelectByPosition() or HistoryOrderSelect (ticket) functions.
  3. Obtain the total number of orders in the cache, using the HistoryOrdersTotal().
  4. Organize the cycle by a search through all of the orders by their indexes in the list.
  5. Obtain a ticket of the orders in the cache, using the HistoryOrderGetTicket() function .
  6. Obtain the data of the order from the cache, by using the HistoryOrderGetDouble(), HistoryOrderGetInteger(), and HistoryOrderGetString() functions. If needed, analyze the obtained data and take the appropriate actions.

An example of such an algorithm:

#property script_show_inputs

input long my_magic=999;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
// --- setting the time intervals of the required trading history
   datetime end=TimeCurrent();                // the current server time
   datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning for 24 hours ago
//--- request into the cache of the program the needed interval of the trading history
   HistorySelect(start,end);
//--- obtain the number of orders in history
   int history_orders=HistoryOrdersTotal();
//--- now scroll through all of the orders
   for(int i=0;i<history_orders;i++)
     {
      //--- obtain the ticket of the order by its number in the list
      ulong order_ticket=HistoryOrderGetTicket(i);
      if(order_ticket>0) //  obtain in the cache, the historical order, and work with it
        {
         //--- time of execution
         datetime time_done=HistoryOrderGetInteger(order_ticket,ORDER_TIME_DONE);
         long order_magic  =HistoryOrderGetInteger(order_ticket,ORDER_MAGIC);
         long pos_ID       =HistoryOrderGetInteger(order_ticket,ORDER_POSITION_ID);
         if(order_magic==my_magic)
           {
           //  process the position with the set ORDER_MAGIC
           }
         PrintFormat("Order #%d: ORDER_MAGIC=#%d, time_done %s, ORDER_POSITION_ID=%d",
                     order_ticket,order_magic,TimeToString(time_done),pos_ID);
        }
      else               // unsuccessful attempt to obtain the order from the history
        {
         PrintFormat("we were not able to select the order with the index %d. Error %d",
                     i,GetLastError());
        }
     }
  }
Note: always cautiously refer to all of the cases of calling the function HistorySelect()! Unthoughtful and excessive loading of all of the available trading history into the cache of the mql5-program, degrades its performance.


Obtaining information on deals from the history

Processing of deals has the same features as the processing of historical orders. The number of deals in the trading history and the result of the execution of HistoryDealsTotal(), depends on how much of the trading history has been loaded into the cache by the HistorySelect(start, end) or HistorySelectByPosition() function.

To fill the cache with only one deal by its ticket, use the HistoryDealSelect(ticket) function.

// --- determining the time intervals of the required trading history
   datetime end=TimeCurrent();                // current sever time
   datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning for 24 hours ago
//--- request in the cache of the program the needed interval of the trading history
   HistorySelect(start,end);
//--- obtain the number of deals in history
   int deals=HistoryDealsTotal();
//--- obtain the ticket for the deal, which has the last index in the list
   ulong deal_ticket=HistoryDealGetTicket(deals-1);
   if(deal_ticket>0) // we obtained in the cache of the deal, and work with it
     {
      //--- the ticket order, based on which the deal was made
      ulong order     =HistoryDealGetInteger(deal_ticket,DEAL_ORDER);
      long order_magic=HistoryDealGetInteger(deal_ticket,DEAL_MAGIC);
      long pos_ID     =HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID);
      PrintFormat("Deal #%d for the order #%d with the ORDER_MAGIC=%d  that participated in the position",
                  deals-1,order,order_magic,pos_ID);
     }
   else              // unsuccessful attempt of obtaining a deal
     {
      PrintFormat("In total, in the history %d of deals, we couldn't select a deal"+
                  " with the index %d. Error %d",deals,deals-1,GetLastError());
     }

In more general cases, it is needed to search in the deal loop from the cache, and make analyze them. The general algorithm will be as follows:

  1. Determine the boundaries of the sufficient history, if history loads by the HistorySelect(start, end) function - then it is not recommended to load the entire history of trade into the cache.
  2. Load into the cache of the program, the trading history of the functions HistorySelect() or HistorySelectByPosition().
  3. Obtain the total number of deals in the history, using the HistoryDealsTotal() function.
  4. Organize the cycle by searching through all of the deals, by their numbers in the list.
  5. Determine the ticket of the next deal in the cache, by using the HistoryDealGetTicket().
  6. Obtain the information about the deal from the cache, by using the functions HistoryDealGetDouble(), HistoryDealGetInteger(), and HistoryDealGetString(). If needed, analyze the obtained data and take the appropriate actions.

An example of such an algorithm for calculating the profits and losses:

input long my_magic=111;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
// --- determine the time intervals of the required trading history
   datetime end=TimeCurrent();                 // current server time
   datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning time to 24 hours ago

//--- request in the cache of the program the needed interval of the trading history
   HistorySelect(start,end);
//--- obtain the number of deals in the history
   int deals=HistoryDealsTotal();

   int returns=0;
   double profit=0;
   double loss=0;
//--- scan through all of the deals in the history
   for(int i=0;i<deals;i++)
     {
      //--- obtain the ticket of the deals by its index in the list
      ulong deal_ticket=HistoryDealGetTicket(i);
      if(deal_ticket>0) // obtain into the cache the deal, and work with it
        {
         string symbol             =HistoryDealGetString(deal_ticket,DEAL_SYMBOL);
         datetime time             =HistoryDealGetInteger(deal_ticket,DEAL_TIME);
         ulong order               =HistoryDealGetInteger(deal_ticket,DEAL_ORDER);
         long order_magic          =HistoryDealGetInteger(deal_ticket,DEAL_MAGIC);
         long pos_ID               =HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID);
         ENUM_DEAL_ENTRY entry_type=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket,DEAL_ENTRY);

         //--- process the deals with the indicated DEAL_MAGIC
         if(order_magic==my_magic)
           {
            //... necessary actions
           }

         //--- calculate the losses and profits with a fixed results
         if(entry_type==DEAL_ENTRY_OUT)
          {
            //--- increase the number of deals 
            returns++;
            //--- result of fixation
            double result=HistoryDealGetDouble(deal_ticket,DEAL_PROFIT);
            //--- input the positive results into the summarized profit
            if(result>0) profit+=result;
            //--- input the negative results into the summarized losses
            if(result<0) loss+=result;
           }
        }
      else // unsuccessful attempt to obtain a deal
        {
         PrintFormat("We couldn't select a deal, with the index %d. Error %d",
                     i,GetLastError());
        }
     }
   //--- output the results of the calculations
   PrintFormat("The total number of %d deals with a financial result. Profit=%.2f , Loss= %.2f",
               returns,profit,loss);
  }
Note: always cautiously refer to all of the cases of calling the function HistorySelect()! Unthoughtful and excessive loading of all of the available trading history into the cache of the mql5-program, degrades its performance.


Obtaining in the cache of the history by the identifier of the position (POSITION_IDENTIFIER)

The HistorySelectByPosition (position_ID) function just like the HistorySelect (start, end) function fills the cache with deals and orders from the history, but only under one condition - they must have the specified identifier of the position (POSITION_IDENTIFIER). The identifier of the position - is a unique number, which is automatically assigned to each re-opened position, and does not change throughout its life. Meanwhile. it must be kept in mind, that the change of the position (shift of the type of the position from POSITION_TYPE_BUY to POSITION_TYPE_SELL) does not change the identifier of the position.

Each open position is the result of one or more deals on that instrument. Therefore, to analyze the position changes, during its lifetime, each deal and order, based on which the deal was done, is assigned an identifier to the position, in which this deal participated. Thus, knowing the identifier of the current open positions, we can reconstruct the entire history - find all of the orders and deals that have changed it.

The HistorySelectByPosition(position_ID) function serves to spare the programmer from having to write their own code for iterating through the entire trading history in search of such information. A typical algorithm for working with this function:

  1. Obtain the right position identifier.
  2. Obtain, using the function HistorySelectByPosition(), into the cache of the trading history, all of the orders and deals, the identifier of which, equals the identifier of the current position.
  3. Process the trading history according to the algorithm.


Conclusion

The entire trading subsystem platform MetaTrader 5 is well thought out and user-friendly More-ever, the abundance of trading functions, allows us to solve each specific problem in the most efficient way.

But even despite the fact that the specialized trading classes from the standard library, allow us not to worry about too many nuances, and write programs on a high level, without going into implementation, the understanding of the basics, will allow us to create more reliable and efficient trading EAs.

All of the given examples can be found in the files, attached to this article.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/211

Last comments | Go to discussion (9)
lukee_wu
lukee_wu | 17 Jan 2018 at 13:12
MetaQuotes Software Corp.:

New article Orders, Positions, and Deals in MetaTrader 5 is published:

Author: MetaQuotes


very helpful!!!!!!!thanks!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1

Rahul Dhangar
Rahul Dhangar | 30 Dec 2020 at 03:54

Thanks for such an informative article. @MetaQuotes

Query: How can I collectively loop through both : open orders and open positions in a single for() loop (in a similar way how we loop all orders in MQL4 and then check if an order is already executed or a pending order) ?

Vladimir Karputov
Vladimir Karputov | 30 Dec 2020 at 07:58
Rahul Dhangar :

Thanks for such an informative article. @MetaQuotes

Query: How can I collectively loop through both : open orders and open positions in a single for() loop (in a similar way how we loop all orders in MQL4 and then check if an order is already executed or a pending order) ?

To calculate POSITIONS and PENDING ORDERS you must use two independent cycles. One cycle enumerates POSITIONS, and the second cycle enumerates PENDED ORDERS. Example: Calculate Positions and Pendong Orders

Carlos Camargo
Carlos Camargo | 29 Sep 2021 at 17:15

Hi, folks!

It would be helpful that @MetaQuotes upgrade this article with Trade Classes (CAccountInfo, CSymbolInfo, COrderInfo, CHistoryOrderInfo, CPositionInfo, CDealInfoCTrade, CTerminalInfo). Develop EA under Object-Oriented paradigm could modify (and simplify) this operations of synchronize cache and to get data over symbols, orders, positions, deals, trades, etc.

Am I right?

Ahmed Elnashar
Ahmed Elnashar | 16 Oct 2021 at 12:16

if you please how to calculate order commision with profit to be like this

" Profit += profit + swap + commision "

Exposing C# code to MQL5 using unmanaged exports Exposing C# code to MQL5 using unmanaged exports
In this article I presented different methods of interaction between MQL5 code and managed C# code. I also provided several examples on how to marshal MQL5 structures against C# and how to invoke exported DLL functions in MQL5 scripts. I believe that the provided examples may serve as a basis for future research in writing DLLs in managed code. This article also open doors for MetaTrader to use many libraries that are already implemented in C#.
How to Copy Trading from MetaTrader 5 to MetaTrader 4 How to Copy Trading from MetaTrader 5 to MetaTrader 4
Is it possible to trade on a real MetaTrader 5 account today? How to organize such trading? The article contains the theory of these questions and the working codes used for copying trades from the MetaTrader 5 terminal to MetaTrader 4. The article will be useful both for the developers of Expert Advisors and for practicing traders.
MQL5 Wizard: How to Create a Risk and Money Management Module MQL5 Wizard: How to Create a Risk and Money Management Module
The generator of trading strategies of the MQL5 Wizard greatly simplifies testing of trading ideas. The article describes how to develop a custom risk and money management module and enable it in the MQL5 Wizard. As an example we've considered a money management algorithm, in which the size of the trade volume is determined by the results of the previous deal. The structure and format of description of the created class for the MQL5 Wizard are also discussed in the article.
Moving Mini-Max: a New Indicator for Technical Analysis and Its Implementation in MQL5 Moving Mini-Max: a New Indicator for Technical Analysis and Its Implementation in MQL5
In the following article I am describing a process of implementing Moving Mini-Max indicator based on a paper by Z.G.Silagadze 'Moving Mini-max: a new indicator for technical analysis'. The idea of the indicator is based on simulation of quantum tunneling phenomena, proposed by G. Gamov in the theory of alpha decay.