Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
MQL5 Cookbook: Analyzing Position Properties in the MetaTrader 5 Strategy Tester

MQL5 Cookbook: Analyzing Position Properties in the MetaTrader 5 Strategy Tester

MetaTrader 5Examples | 25 March 2013, 13:36
9 032 0
Anatoli Kazharski
Anatoli Kazharski

Introduction

In this article, we will modify the Expert Advisor created in the previous article "MQL5 Cookbook: Position Properties on the Custom Info Panel" and address the following issues:

  • Checking for new bar events on the current symbol;
  • Getting data from bars;
  • Including a trade class of the Standard Library to a file;
  • Creating a function to search for trading signals;
  • Creating a function for executing trading operations;
  • Determining trade events in the OnTrade() function.

In fact, each of the above issues may deserve an article of its own, yet in my opinion such an approach would only complicate the study of the language.

I will use very simple examples to show you how these features can be implemented. In other words, the implementation of each of the above listed tasks will literally fit into one simple and straightforward function. When developing a certain idea in the future articles of the series, we will gradually make these functions more complex, as necessary and to the extent required by the task at hand.

First, let us copy the Expert Advisor from the previous article as we will need all its functions.


Developing an Expert Advisor

We begin with including the CTrade class from the Standard Library to our file. This class has all the necessary functions for executing trading operations. For a start, we can easily use them, without even looking inside, which is what we are going to do.

To include the class, we need to write the following:

//--- Include a class of the Standard Library
#include <Trade/Trade.mqh>

You can place this code at the very beginning of the file to be able to easily find it afterwards, e.g. after the #define directive. The #include command denotes that the Trade.mqh file needs to be taken from <MetaTrader 5 terminal directory>\MQL5\Include\Trade\. The same approach can be used to include any other file that contains functions. This is especially useful when the amount of the project code grows bigger and gets difficult to navigate in.

Now, we need to create an instance of the class in order to get access to all its functions. This can be done by writing the name of the instance after the name of the class:

//--- Load the class
CTrade trade;

In this version of the Expert Advisor, we are going to use only one trade function out of all functions available in the CTrade class. It is the PositionOpen() function which is used to open a position. It can also be used for reversal of an existing open position. How this function can be called from the class will be shown later on in this article when creating a function responsible for execution of trading operations.

Further, we add two dynamic arrays at global scope. These arrays will take on bar values.

//--- Price data arrays
double               close_price[]; // Close (closing prices of the bar)
double               open_price[];  // Open (opening prices of the bar)

Next, create a CheckNewBar() function using which the program will check for new bar events as trading operations will only be executed on completed bars.

Below is the code of the CheckNewBar() function with detailed comments:

//+------------------------------------------------------------------+
//| CHECKING FOR THE NEW BAR                                         |
//+------------------------------------------------------------------+
bool CheckNewBar()
  {
//--- Variable for storing the opening time of the current bar
   static datetime new_bar=NULL;
//--- Array for getting the opening time of the current bar
   static datetime time_last_bar[1]={0};
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(_Symbol,Period(),0,1,time_last_bar)==-1)
     { Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError())+""); }
//--- If this is a first function call
   if(new_bar==NULL)
     {
      // Set the time
      new_bar=time_last_bar[0];
      Print(__FUNCTION__,": Initialization ["+_Symbol+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(time_last_bar[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false); // Return false and exit 
     }
//--- If the time is different
   if(new_bar!=time_last_bar[0])
     {
      new_bar=time_last_bar[0]; // Set the time and exit 
      return(true); // Store the time and return true
     }
//--- If we have reached this line, then the bar is not new, return false
   return(false);
  }

As you can see in the above code, the CheckNewBar() function returns true if the bar is new or false if there is no new bar yet. This way you can control the situation when trading/testing, only executing trading operations on completed bars.

At the very beginning of the function, we declare a static variable and a static array of the datetime type. Static local variables retain their values even after the function is exited. At every subsequent function call, such local variables will contain the values they took at the previous call of the function.

Further, please note the CopyTime() function. It helps us get the time of the last bar in the time_last_bar array. Be sure to check out the function syntax in MQL5 Reference.

One can also notice the user-defined TimeframeToString() function that has never been mentioned in this series of articles before. It converts time frame values to a string that is clear to the user:

string TimeframeToString(ENUM_TIMEFRAMES timeframe)
  {
   string str="";
   //--- If the passed value is incorrect, take the time frame of the current chart
   if(timeframe==WRONG_VALUE || timeframe == NULL)
      timeframe = Period();
   switch(timeframe)
     {
      case PERIOD_M1  : str="M1";  break;
      case PERIOD_M2  : str="M2";  break;
      case PERIOD_M3  : str="M3";  break;
      case PERIOD_M4  : str="M4";  break;
      case PERIOD_M5  : str="M5";  break;
      case PERIOD_M6  : str="M6";  break;
      case PERIOD_M10 : str="M10"; break;
      case PERIOD_M12 : str="M12"; break;
      case PERIOD_M15 : str="M15"; break;
      case PERIOD_M20 : str="M20"; break;
      case PERIOD_M30 : str="M30"; break;
      case PERIOD_H1  : str="H1";  break;
      case PERIOD_H2  : str="H2";  break;
      case PERIOD_H3  : str="H3";  break;
      case PERIOD_H4  : str="H4";  break;
      case PERIOD_H6  : str="H6";  break;
      case PERIOD_H8  : str="H8";  break;
      case PERIOD_H12 : str="H12"; break;
      case PERIOD_D1  : str="D1";  break;
      case PERIOD_W1  : str="W1";  break;
      case PERIOD_MN1 : str="MN1"; break;
     }
//---
   return(str);
  }

How the CheckNewBar() function is used will be shown later on in the article when we have all other necessary functions ready. Let us now look at the GetBarsData() function that takes on values of the requested number of bars.

//+------------------------------------------------------------------+
//| GETTING BAR VALUES                                               |
//+------------------------------------------------------------------+
void GetBarsData()
  {
//--- Number of bars for getting their data in an array
   int amount=2;
//--- Reverse the time series ... 3 2 1 0
   ArraySetAsSeries(close_price,true);
   ArraySetAsSeries(open_price,true);
//--- Get the closing price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyClose(_Symbol,Period(),0,amount,close_price)<amount)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the opening price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyOpen(_Symbol,Period(),0,amount,open_price)<amount)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
  }

Let us take a closer look at the above code. First, in the amount variable, we specify the number of bars whose data we need to get. Then we set the array indexing order so that the value of the last (current) bar is in the zero index of the array, using the ArraySetAsSeries() function. E.g., if you want to use the value of the last bar in your calculations, it can be written as follows, if exemplified by the opening price: open_price[0]. The notation for the second to last bar will similarly be: open_price[1].

The mechanism of getting closing and opening prices is similar to that of the CheckNewBar() function where we had to get the time of the last bar. It is just that in this case we use the CopyClose() and CopyOpen() functions. Similarly, CopyHigh() and CopyLow() are used to get the high and low bar prices, respectively.

Let us move on and consider a very simple example showing how to determine signals for opening/reversal of a position. Price arrays store data for two bars (current bar and the previous completed one). We will use data from the completed bar.

  • A Buy signal occurs when the closing price is above the opening price (bullish bar);
  • A Sell signal occurs when the closing price is below the opening price (bearish bar).

The code for the implementation of these simple conditions is provided below:

//+------------------------------------------------------------------+
//| DETERMINING TRADING SIGNALS                                      |
//+------------------------------------------------------------------+
int GetTradingSignal()
  {
//--- A Buy signal (0) :
   if(close_price[1]>open_price[1])
      return(0);
//--- A Sell signal (1) :
   if(close_price[1]<open_price[1])
      return(1);
//--- No signal (3):
   return(3);
  }

As you can see, it is very simple. One can easily figure out how to handle more complex conditions in a similar manner. The function returns zero if a completed bar is up or one if a completed bar is down. If for whatever reason there is no signal, the function will return 3.

Now we only need to create a TradingBlock() function for the implementation of trading activities. Below is the code of the function with detailed comments:

//+------------------------------------------------------------------+
//| TRADING BLOCK                                                    |
//+------------------------------------------------------------------+
void TradingBlock()
  {
   int               signal=-1;           // Variable for getting a signal
   string            comment="hello :)";  // Position comment
   double            start_lot=0.1;       // Initial volume of a position
   double            lot=0.0;             // Volume for position calculation in case of reverse position
   double            ask=0.0;             // Ask price
   double            bid=0.0;             // Bid price
//--- Get a signal
   signal=GetTradingSignal();
//--- Find out if there is a position
   pos_open=PositionSelect(_Symbol);
//--- If it is a Buy signal
   if(signal==0)
     {
      //--- Get the Ask price
      ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      //--- If there is no position
      if(!pos_open)
        {
         //--- Open a position. If the position failed to open, print the relevant message
         if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,start_lot,ask,0,0,comment))
           { Print("Error opening a BUY position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
        }
      //--- If there is a position
      else
        {
         //--- Get the position type
         pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         //--- If it is a SELL position
         if(pos_type==POSITION_TYPE_SELL)
           {
            //--- Get the position volume
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            //--- Adjust the volume
            lot=NormalizeDouble(pos_volume+start_lot,2);
            //--- Open a position. If the position failed to open, print the relevant message
            if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,ask,0,0,comment))
              { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
           }
        }
      //---
      return;
     }
//--- If there is a Sell signal
   if(signal==1)
     {
      //-- Get the Bid price
      bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      //--- If there is no position
      if(!pos_open)
        {
         //--- Open a position. If the position failed to open, print the relevant message
         if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,start_lot,bid,0,0,comment))
           { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
        }
      //--- If there is a position
      else
        {
         //--- Get the position type
         pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         //--- If it is a BUY position
         if(pos_type==POSITION_TYPE_BUY)
           {
            //--- Get the position volume
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            //--- Adjust the volume
            lot=NormalizeDouble(pos_volume+start_lot,2);
            //--- Open a position. If the position failed to open, print the relevant message
            if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,bid,0,0,comment))
              { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
           }
        }
      //---
      return;
     }
  }

I believe everything should be clear up to the point where a position is opened. As you can see in the above code, the (trade) pointer is followed by a dot which in turn is followed by the PositionOpen() method. This is how you can call a certain method from a class. After you put a dot, you will see a list containing all class methods. All you need is to select the required method from the list:

Fig. 1. Calling a class method.

Fig. 1. Calling a class method.

There are two major blocks in the TradingBlock() function - to buy and to sell. Right after determining the direction of the signal, we get the ask price in case of a Buy signal and the bid price in case of a Sell signal.

All prices/levels used in trade orders must be normalized using the NormalizeDouble() function, otherwise an attempt to open or modify a position will lead to an error. The use of this function is also advisable when calculating the lot. Further, please note that Stop Loss and Take Profit parameters have zero values. More information on setting trading levels will be provided in the next article of the series.

So now that all user-defined functions are ready, we can arrange them in the correct order:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialize the new bar
   CheckNewBar();
//--- Get position properties and update the values on the panel
   GetPositionProperties();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Print the deinitialization reason to the journal
   Print(GetDeinitReasonText(reason));
//--- When deleting from the chart
   if(reason==REASON_REMOVE)
      //--- Delete all objects relating to the info panel from the chart
      DeleteInfoPanel();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- If the bar is not new, exit
   if(!CheckNewBar())
      return;
//--- If there is a new bar
   else
     {
      GetBarsData();  // Get bar data
      TradingBlock(); // Check the conditions and trade
     }
//--- Get the properties and update the values on the panel
   GetPositionProperties();
  }

There is only one thing left to consider - determining trade events using the OnTrade() function. Here, we will only briefly touch upon it to give you a general idea. In our case, we need to implement the following scenario: when opening/closing/modifying a position manually, the values in the list of position properties on the info panel need to be updated as soon as the operation is completed, rather than upon receiving a new tick. For this purpose, we only need to add the following code:

//+------------------------------------------------------------------+
//| TRADE EVENT                                                      |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Get position properties and update the values on the panel
   GetPositionProperties();
  }

Basically, everything is ready and we can proceed to testing. The Strategy Tester allows you to quickly run a test in the visualization mode and find errors, if any. The use of the Strategy Tester can also be seen as beneficial due to the fact that you can keep on developing your program even at weekends when markets are closed.

Set up the Strategy Tester, enable the visualization mode and click Start. The Expert Advisor will start trading in the Strategy Tester and you will see a picture similar to the one shown below:

Fig. 2. Visualization mode in the MetaTrader 5 Strategy Tester.

Fig. 2. Visualization mode in the MetaTrader 5 Strategy Tester.

You can suspend testing in the visualization mode at any time and continue testing step by step by pressing F12. The step will be equal to one bar if you set the Strategy Tester to the Opening prices only mode, or one tick if you selected the Every tick mode. You can also control the testing speed.

To make sure that the values on the info panel get updated right after opening/closing a position manually or adding/modifying Stop Loss/Take Profit levels, the Expert Advisor should be tested in real time mode. Not to wait too long, simply run the Expert Advisor on a 1-minute time frame so that trading operations are executed every minute.

Besides that, I have added another array for names of position properties on the info panel:

// Array of position property names
string pos_prop_texts[INFOPANEL_SIZE]=
  {
   "Symbol :",
   "Magic Number :",
   "Comment :",
   "Swap :",
   "Commission :",
   "Open Price :",
   "Current Price :",
   "Profit :",
   "Volume :",
   "Stop Loss :",
   "Take Profit :",
   "Time :",
   "Identifier :",
   "Type :"
  };

In the previous article, I mentioned that we would need this array to reduce the SetInfoPanel() function code. You can now see how this can be done If you have not yet implemented or figured it out on your own. The new implementation of the list of creating the objects relating to position properties is as follows:

//--- List of the names of position properties and their values
   for(int i=0; i<INFOPANEL_SIZE; i++)
     {
      //--- Property name
      CreateLabel(0,0,pos_prop_names[i],pos_prop_texts[i],anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[i],2);
      //--- Property value
      CreateLabel(0,0,pos_prop_values[i],GetPropertyValue(i),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[i],2);
     }

At the beginning of the SetInfoPanel() function, you can notice the following line:

//--- Testing in the visualization mode
   if(MQL5InfoInteger(MQL5_VISUAL_MODE))
     {
      y_bg=2;
      y_property=16;
     }

It conveys to the program that Y-coordinates of the objects on the info panel need to be adjusted if the program is currently being tested in the visualization mode. This is due to the fact that when testing in the Strategy Tester's visualization mode, the name of the Expert Advisor is not displayed in the top right corner of the chart as in real time. Therefore the unnecessary indentation can so be deleted.


Conclusion

We are done for now. In the next article, we will focus on setting and modifying trading levels. Below you can download the source code of the Expert Advisor, PositionPropertiesTesterEN.mq5.

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

MQL5 Market Results for Q1 2013 MQL5 Market Results for Q1 2013
Since its founding, the store of trading robots and technical indicators MQL5 Market has already attracted more than 250 developers who have published 580 products. The first quarter of 2013 has turned out to be quite successful for some MQL5 Market sellers who have managed to make handsome profit by selling their products.
Jeremy Scott - Successful MQL5 Market Seller Jeremy Scott - Successful MQL5 Market Seller
Jeremy Scott who is better known under Johnnypasado nickname at MQL5.community became famous offering products in our MQL5 Market service. Jeremy has already made several thousands of dollars in the Market and that is not the limit. We decided to take a closer look at the future millionaire and receive some pieces of advice for MQL5 Market sellers.
MQL5 Cloud Network: Are You Still Calculating? MQL5 Cloud Network: Are You Still Calculating?
It will soon be a year and a half since the MQL5 Cloud Network has been launched. This leading edge event ushered in a new era of algorithmic trading - now with a couple of clicks, traders can have hundreds and thousands of computing cores at their disposal for the optimization of their trading strategies.
MQL5 Cookbook: Position Properties on the Custom Info Panel MQL5 Cookbook: Position Properties on the Custom Info Panel
This time we will create a simple Expert Advisor that will get position properties on the current symbol and display them on the custom info panel during manual trading. The info panel will be created using graphical objects and displayed information will be refreshed at every tick. This is going to be much more convenient than all the time having to manually run the script described in the previous article of the series called "MQL5 Cookbook: Getting Position Properties".