MQL5 Cookbook: The History of Deals And Function Library for Getting Position Properties

Anatoli Kazharski | 7 May, 2013

Introduction

It is time to briefly summarize the information provided in the previous articles on position properties. In this article, we will create a few additional functions to get the properties that can only be obtained after accessing the history of deals. We will also get familiar with data structures that will allow us to access position and symbol properties in a more convenient way.

Trading systems where position volumes remain the same throughout their existence do not really require the use of functions that will be provided in this article. But if you plan on implementing a money management system and controlling a position lot size in your trading strategy at a later stage, these functions will be indispensable.

Before we start, I would like to make a suggestion to those readers who followed a link to this article, while being first-time visitors to this website, or have just started learning the MQL5 language, to begin with the earlier articles of the "MQL5 Cookbook" series.


Expert Advisor Development

To be able to see the operation of the new functions in the Expert Advisor modified in the previous article called "MQL5 Cookbook: How to Avoid Errors When Setting/Modifying Trade Levels", we will add the possibility of increasing the position volume if a signal for opening occurs again, while the position is already there.

There may be several deals in the position history, and if there were changes in the position volume over the course of trading, there must have also been changes in the current position price. To find out the price of the first entry point, we need to access the history of deals with respect to that specific position. The figure below is a demonstration of the case where a position only has one deal (entry point):

Fig. 1. First deal in the position

Fig. 1. First deal in the position.

The next figure shows a change in the position price following the second deal:

Fig. 2. Second deal in the position

Fig. 2. Second deal in the position.

As demonstrated in the previous articles, standard identifiers allow you to get only the current position price (POSITION_PRICE_OPEN) and the current price of a symbol (POSITION_PRICE_CURRENT) for which a position is opened.

However, in some trading systems we need to know the distance covered by the price from the first entry point, as well as the price of the last deal. All this information is available in the account's history of deals/orders. Below is the list of deals associated with the previous figure:

Fig. 3. The history of deals in the account

Fig. 3. The history of deals in the account.

I believe, the situation is now clear and all the objectives are set. Let us continue to modify the Expert Advisor featured in the previous articles. First, we will add new identifiers numbered 0, 6, 9, 12 and 16 to the enumeration of position properties:

//--- Enumeration of position properties
enum ENUM_POSITION_PROPERTIES
  {
   P_TOTAL_DEALS     = 0,
   P_SYMBOL          = 1,
   P_MAGIC           = 2,
   P_COMMENT         = 3,
   P_SWAP            = 4,
   P_COMMISSION      = 5,
   P_PRICE_FIRST_DEAL= 6,
   P_PRICE_OPEN      = 7,
   P_PRICE_CURRENT   = 8,
   P_PRICE_LAST_DEAL = 9,
   P_PROFIT          = 10,
   P_VOLUME          = 11,
   P_INITIAL_VOLUME  = 12,
   P_SL              = 13,
   P_TP              = 14,
   P_TIME            = 15,
   P_DURATION        = 16,
   P_ID              = 17,
   P_TYPE            = 18,
   P_ALL             = 19
  };

Comments for each of the properties will be given in a structure that will be reviewed a bit below.

Let us increase the number of the external parameters. Now, we will be able to specify:

This is how it is implemented:

//--- External parameters of the Expert Advisor
sinput   long        MagicNumber=777;     // Magic number
sinput   int         Deviation=10;        // Slippage
input    int         NumberOfBars=2;      // Number of Bullish/Bearish bars for a Buy/Sell
input    double      Lot=0.1;             // Lot
input    double      VolumeIncrease=0.1;  // Position volume increase
input    double      StopLoss=50;         // Stop Loss
input    double      TakeProfit=100;      // Take Profit
input    double      TrailingStop=10;     // Trailing Stop
input    bool        Reverse=true;        // Position reversal
sinput   bool        ShowInfoPanel=true;  // Display of the info panel

Please note the parameters whose sinput modifier is set. This modifier allows you to disable the optimization in the Strategy Tester. In fact, when developing a program for your own use, you have a perfect understanding of what parameters will affect the end result, so you simply uncheck them from the optimization. But when it comes to a very large number of parameters, this method allows you to visually separate them from the others as they get grayed out:

Fig. 4. Parameters disabled for optimization are grayed out

Fig. 4. Parameters disabled for optimization are grayed out.

Let us now replace the global variables that stored position and symbol property values with data structures (struct):

//--- Position properties
struct position_properties
  {
   uint              total_deals;      // Number of deals
   bool              exists;           // Flag of presence/absence of an open position
   string            symbol;           // Symbol
   long              magic;            // Magic number
   string            comment;          // Comment
   double            swap;             // Swap
   double            commission;       // Commission   
   double            first_deal_price; // Price of the first deal in the position
   double            price;            // Current position price
   double            current_price;    // Current price of the position symbol      
   double            last_deal_price;  // Price of the last deal in the position
   double            profit;           // Profit/Loss of the position
   double            volume;           // Current position volume
   double            initial_volume;   // Initial position volume
   double            sl;               // Stop Loss of the position
   double            tp;               // Take Profit of the position
   datetime          time;             // Position opening time
   ulong             duration;         // Position duration in seconds
   long              id;               // Position identifier
   ENUM_POSITION_TYPE type;            // Position type
  };
//--- Symbol properties
struct symbol_properties
  {
   int               digits;        // Number of decimal places in the price
   int               spread;        // Spread in points
   int               stops_level;   // Stops level
   double            point;         // Point value
   double            ask;           // Ask price
   double            bid;           // Bid price
   double            volume_min;    // Minimum volume for a deal
   double            volume_max;    // Maximum volume for a deal
   double            volume_limit;  // Maximum permissible volume for a position and orders in one direction
   double            volume_step;   // Minimum volume change step for a deal
   double            offset;        // Offset from the maximum possible price for a transaction
   double            up_level;      // Upper Stop level price
   double            down_level;    // Lower Stop level price
  }

Now, to access a certain element of the structure, we need to create a variable of this structure type. The procedure is similar to creating an object for a trade class that was considered in the article called "MQL5 Cookbook: Analyzing Position Properties in the MetaTrader 5 Strategy Tester".

//--- variables for position and symbol properties
position_properties  pos;
symbol_properties    symb;

You can access the elements in the same manner as when dealing with class methods. In other words, it is enough to put a dot after the name of a structure variable to display the list of elements contained in that specific structure. This is very convenient. In case single-line comments are provided for the fields of the structure (as in our example), they will be shown in a tooltip on the right.

Fig. 5a. List of structure fields for position properties Fig. 5b. List of structure fields for symbol properties

Fig. 5. List of structure fields.

Another important point. In modifying the Expert Advisor, we have changed virtually all its global variables used in many functions, so now we need to replace them with the corresponding structure fields for symbol and position properties. For example, the pos_open global variable that was used to store the flag of presence/absence of an open position has been replaced with the exists field of the position_properties structure type. Therefore, wherever the pos_open variable was used, it needs to be replaced with pos.exists.

It will be a lengthy and exhausting process if you get to do it manually. So it would be better to automate the solution to this task using the MetaEditor features: Find and Replace -> Replace in the Edit menu or the Ctrl+H key combination:


Fig. 6. Finding and replacing the text

Fig. 6. Finding and replacing the text.

We need to find and replace all the global variables for position and symbol properties to further run a test, having compiled the file. If no errors are detected, it will mean that we have done everything right. I will not provide the code here not to make the article unnecessarily long. Besides, a ready to use source code is available at the end of the article for download.

Now that we have sorted thing out with the variables, let us proceed to modifying the existing functions and creating the new ones.

In the external parameters, you can now set the magic number and slippage in points. We therefore also need to make the relevant changes in the code of the Expert Advisor. We will create a user-defined auxiliary function OpenPosition(), where these properties will be set using the functions of the CTrade class prior to sending an order for position opening.

//+------------------------------------------------------------------+
//| Opening a position                                               |
//+------------------------------------------------------------------+
void OpenPosition(double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
   trade.SetExpertMagicNumber(MagicNumber); // Set the magic number in the trading structure
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); // Set the slippage in points
//--- If the position failed to open, print the relevant message
   if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment))
     { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
  }

We only need to make some small changes in the code of the main trading function of the Expert Advisor - TradingBlock(). Below is the part of the function code that has undergone changes:

//--- If there is no position
   if(!pos.exists)
     {
      //--- Adjust the volume
      lot=CalculateLot(Lot);
      //--- Open a position
      OpenPosition(lot,order_type,position_open_price,sl,tp,comment);
     }
//--- If there is a position
   else
     {
      //--- Get the position type
      GetPositionProperties(P_TYPE);
      //--- If the position is opposite to the signal and the position reversal is enabled
      if(pos.type==opposite_position_type && Reverse)
        {
         //--- Get the position volume
         GetPositionProperties(P_VOLUME);
         //--- Adjust the volume
         lot=pos.volume+CalculateLot(Lot);
         //--- Reverse the position
         OpenPosition(lot,order_type,position_open_price,sl,tp,comment);
         return;
        }
      //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume
      if(!(pos.type==opposite_position_type) && VolumeIncrease>0)
        {
         //--- Get the Stop Loss of the current position
         GetPositionProperties(P_SL);
         //--- Get the Take Profit of the current position
         GetPositionProperties(P_TP);
         //--- Adjust the volume
         lot=CalculateLot(Increase);
         //--- Increase the position volume
         OpenPosition(lot,order_type,position_open_price,pos.sl,pos.tp,comment);
         return;
        }

The above code has been enhanced with the block where the direction of the current position is checked against the direction of the signal. If their directions coincide and the position volume increase is enabled in the external parameters (the VolumeIncrease parameter value is greater than zero), we check/adjust a given lot and send the relevant order. Now, all you need to do to send an order to open or reverse a position or to increase the position volume is to write one line of code.

Let us create functions for getting position properties from the history of deals. We will start with a CurrentPositionTotalDeals() function that returns the number of deals in the current position:

//+------------------------------------------------------------------+
//| Returning the number of deals in the current position            |
//+------------------------------------------------------------------+
uint CurrentPositionTotalDeals()
  {
   int    total       =0;  // Total deals in the selected history list
   int    count       =0;  // Counter of deals by the position symbol
   string deal_symbol =""; // symbol of the deal
//--- If the position history is obtained
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      //--- Get the number of deals in the obtained list
      total=HistoryDealsTotal();
      //--- Iterate over all the deals in the obtained list
      for(int i=0; i<total; i++)
        {
            //--- Get the symbol of the deal
            deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
            //--- If the symbol of the deal and the current symbol are the same, increase the counter
            if(deal_symbol==_Symbol)
               count++;
        }
     }
//---
   return(count);
  }

The above code is provided with fairly detailed comments. But we should say a few words about how the history is selected. In our case, we got the list from the point of opening the current position determined by the opening time to the current point of time using the HistorySelect() function. After the history is selected, we can find out the number of deals in the list using the HistoryDealsTotal() function. The rest should be clear from the comments.

The history of a particular position can also be selected by its identifier using the HistorySelectByPosition() function. Here, you need to consider that the position identifier remains the same when the position is reversed, as it sometimes happens in our Expert Advisor. However, the position opening time does change upon reversal, therefore this variant is easier to implement. But if you have to deal with the history of deals that does not apply only to the currently open position, you need to use identifiers. We will return to the history of deals in the future articles.

Let us continue by creating a CurrentPositionFirstDealPrice() function that returns the price of the first deal in the position, i.e. the price of the deal at which the position was opened.

//+------------------------------------------------------------------+
//| Returning the price of the first deal in the current position    |
//+------------------------------------------------------------------+
double CurrentPositionFirstDealPrice()
  {
   int      total       =0;    // Total deals in the selected history list
   string   deal_symbol ="";   // symbol of the deal
   double   deal_price  =0.0;  // Price of the deal
   datetime deal_time   =NULL; // Time of the deal
//--- If the position history is obtained
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      //--- Get the number of deals in the obtained list
      total=HistoryDealsTotal();
      //--- Iterate over all the deals in the obtained list
      for(int i=0; i<total; i++)
        {
         //--- Get the price of the deal
         deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE);
         //--- Get the symbol of the deal
         deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
         //--- Get the time of the deal
         deal_time=(datetime)HistoryDealGetInteger(HistoryDealGetTicket(i),DEAL_TIME);
         //--- If the time of the deal equals the position opening time, 
         //    and if the symbol of the deal and the current symbol are the same, exit the loop
         if(deal_time==pos.time && deal_symbol==_Symbol)
            break;
        }
     }
//---
   return(deal_price);
  }

The principle here is the same as in the previous function. We get the history from the point of position opening and then check the time of the deal and the position opening time at each iteration. Along with the price of the deal, we get the symbol name and the time of the deal. The very first deal is identified when the time of the deal coincides with the position opening time. Since its price has already been assigned to the relevant variable, we only need to return the value.

Let us go on. Sometimes, you may need to get the price of the last deal in the current position. For this purpose, we will create a CurrentPositionLastDealPrice() function:

//+------------------------------------------------------------------+
//| Returning the price of the last deal in the current position     |
//+------------------------------------------------------------------+
double CurrentPositionLastDealPrice()
  {
   int    total       =0;   // Total deals in the selected history list
   string deal_symbol ="";  // Symbol of the deal 
   double deal_price  =0.0; // Price
//--- If the position history is obtained
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      //--- Get the number of deals in the obtained list
      total=HistoryDealsTotal();
      //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal
      for(int i=total-1; i>=0; i--)
        {
         //--- Get the price of the deal
         deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE);
         //--- Get the symbol of the deal
         deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
         //--- If the symbol of the deal and the current symbol are the same, exit the loop
         if(deal_symbol==_Symbol)
            break;
        }
     }
//---
   return(deal_price);
  }

This time the loop has started with the last deal in the list and it is often the case that the required deal is identified at the first loop iteration. But if you trade on several symbols, the loop will continue until the symbol of the deal matches the current symbol.

The current position volume can be obtained using the POSITION_VOLUME standard identifier. To find out the initial position volume (the volume of the first deal), we will create a CurrentPositionInitialVolume() function:

//+------------------------------------------------------------------+
//| Returning the initial volume of the current position             |
//+------------------------------------------------------------------+
double CurrentPositionInitialVolume()
  {
   int             total       =0;           // Total deals in the selected history list
   ulong           ticket      =0;           // Ticket of the deal
   ENUM_DEAL_ENTRY deal_entry  =WRONG_VALUE; // Position modification method
   bool            inout       =false;       // Flag of position reversal
   double          sum_volume  =0.0;         // Counter of the aggregate volume of all deals, except for the first one
   double          deal_volume =0.0;         // Volume of the deal
   string          deal_symbol ="";          // Symbol of the deal 
   datetime        deal_time   =NULL;        // Deal execution time
//--- If the position history is obtained
   if(HistorySelect(pos.time,TimeCurrent()))
     {
      //--- Get the number of deals in the obtained list
      total=HistoryDealsTotal();
      //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal
      for(int i=total-1; i>=0; i--)
        {
         //--- If the order ticket by its position is obtained, then...
         if((ticket=HistoryDealGetTicket(i))>0)
           {
            //--- Get the volume of the deal
            deal_volume=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            //--- Get the position modification method
            deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);
            //--- Get the deal execution time
            deal_time=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
            //--- Get the symbol of the deal
            deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL);
            //--- When the deal execution time is less than or equal to the position opening time, exit the loop
            if(deal_time<=pos.time)
               break;
            //--- otherwise calculate the aggregate volume of deals by the position symbol, except for the first one
            if(deal_symbol==_Symbol)
               sum_volume+=deal_volume;
           }
        }
     }
//--- If the position modification method is a reversal
   if(deal_entry==DEAL_ENTRY_INOUT)
     {
      //--- If the position volume has been increased/decreased
      //    I.e. the number of deals is more than one
      if(fabs(sum_volume)>0)
        {
         //--- Current volume minus the volume of all deals except for the first one
         double result=pos.volume-sum_volume;
         //--- If the resulting value is greater than zero, return the result, otherwise return the current position volume         
         deal_volume=result>0 ? result : pos.volume;
        }
      //--- If there are no more deals, other than the entry,
      if(sum_volume==0)
         deal_volume=pos.volume; // return the current position volume
     }
//--- Return the initial position volume
   return(NormalizeDouble(deal_volume,2));
  }

This function came out more complex than the previous ones. I have tried to take into consideration all possible situations that may result in the wrong value. A careful testing did not reveal any problems. The detailed comments provided in the code should help you get the point.

It will also be useful to have a function that returns the position duration. We will arrange it so as to allow the user to select the appropriate format of the returned value: seconds, minutes, hours or days. For this purpose, let us create another enumeration:

//--- Position duration
enum ENUM_POSITION_DURATION
  {
   DAYS     = 0, // Days
   HOURS    = 1, // Hours
   MINUTES  = 2, // Minutes
   SECONDS  = 3  // Seconds
  };

Below is the code of the CurrentPositionDuration() function responsible for all the relevant calculations:

//+------------------------------------------------------------------+
//| Returning the duration of the current position                   |
//+------------------------------------------------------------------+
ulong CurrentPositionDuration(ENUM_POSITION_DURATION mode)
  {
   ulong     result=0;   // End result
   ulong     seconds=0;  // Number of seconds
//--- Calculate the position duration in seconds
   seconds=TimeCurrent()-pos.time;
//---
   switch(mode)
     {
      case DAYS      : result=seconds/(60*60*24);   break; // Calculate the number of days
      case HOURS     : result=seconds/(60*60);      break; // Calculate the number of hours
      case MINUTES   : result=seconds/60;           break; // Calculate the number of minutes
      case SECONDS   : result=seconds;              break; // No calculations (number of seconds)
      //---
      default        :
         Print(__FUNCTION__,"(): Unknown duration mode passed!");
         return(0);
     }
//--- Return result
   return(result);
  }

Let us create a CurrentPositionDurationToString() function for the info panel where position properties are displayed. The function will convert the position duration in seconds to a format easily understood by the user. The number of seconds will be passed to the function, and the function in its turn will return a string containing the position duration in days, hours, minutes and seconds:

//+------------------------------------------------------------------+
//| Converting the position duration to a string                     |
//+------------------------------------------------------------------+
string CurrentPositionDurationToString(ulong time)
  {
//--- A dash if there is no position
   string result="-";
//--- If the position exists
   if(pos.exists)
     {
      //--- Variables for calculation results
      ulong days=0;
      ulong hours=0;
      ulong minutes=0;
      ulong seconds=0;
      //--- 
      seconds=time%60;
      time/=60;
      //---
      minutes=time%60;
      time/=60;
      //---
      hours=time%24;
      time/=24;
      //---
      days=time;
      //--- Generate a string in the specified format DD:HH:MM:SS
      result=StringFormat("%02u d: %02u h : %02u m : %02u s",days,hours,minutes,seconds);
     }
//--- Return result
   return(result);
  }

Everything is set and ready now. I am not going to provide the GetPositionProperties() and GetPropertyValue() function codes that need to be modified in accordance with all the above changes. If you read all the previous articles of the series, you should not find any difficulty doing this by yourself. Whatever the case, the source code file is attached to the article.

As a result, the info panel should appear as shown below:

Fig. 7. Demonstration of all position properties on the info panel

Fig. 7. Demonstration of all position properties on the info panel.

Thus, we now have the function library for getting position properties and we will probably continue to work on it in the future articles, as and when required.


Optimizing Parameters and Testing Expert Advisor

As an experiment, let us try to optimize the parameters of the Expert Advisor. Although what we currently have cannot yet be called a fully-featured trading system, the result we will achieve will open our eyes in regard to some things and enhance our experience as developers of trading systems.

We will make the Strategy Tester settings as shown below:

Fig. 8. Strategy Tester settings for parameter optimization

Fig. 8. Strategy Tester settings for parameter optimization.

Settings of the external parameters of the Expert Advisor should be as follows:

Fig. 9. Expert Advisor parameter settings for optimization

Fig. 9. Expert Advisor parameter settings for optimization.

Following the optimization, we sort the achieved results by the maximum recovery factor:

Fig. 10. Results sorted by the maximum recovery factor

Fig. 10. Results sorted by the maximum recovery factor.

Let us now test the very top set of parameters, with the Recovery Factor value being equal to 4.07. Even given the fact that the optimization has been performed for EURUSD, we can see the positive results for many symbols:

Results for EURUSD:

Fig. 11. Results for EURUSD

Fig. 11. Results for EURUSD.

Results for AUDUSD:

Fig. 12. Results for AUDUSD

Fig. 12. Results for AUDUSD.

Results for NZDUSD:

Fig. 13. Results for NZDUSD

Fig. 13. Results for NZDUSD.


Conclusion

Virtually any idea can be developed and enhanced. Every trading system should be very carefully tested before being rejected as defective. In the future articles, we will have a look at various mechanisms and schemes that may play a very positive role in customizing and adapting almost any trading system.