MQL5 Cookbook: The History of Deals And Function Library for Getting Position Properties
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.
The next figure shows a change in the position price following the second deal:
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.
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:
- MagicNumber - a unique ID of the Expert Advisor (magic number);
- Deviation - slippage;
- VolumeIncrease - a value by which the position volume will be increased;
- InfoPanel - a parameter that allows you to enable/disable the display of the info panel.
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.
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. 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.
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.
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.
Settings of the external parameters of the Expert Advisor should be as follows:
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.
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.
Results for AUDUSD:
Fig. 12. Results for AUDUSD.
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.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/644
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use