The Fundamentals of Testing in MetaTrader 5
Why do We Need a Strategy Tester
The idea of automated trading is appealing by the fact that the trading robot can work non-stop for 24 hours a day, seven days a week. The robot does not get tired, doubtful or scared, it's is totally free from any psychological problems. It is sufficient enough to clearly formalize the trading rules and implement them in the algorithms, and the robot is ready to work tirelessly. But first, you must make sure that the following two important conditions are met:
- The Expert Advisor performs trading operations in accordance with the rules of the trading system;
- The trading strategy, implemented in the EA, demonstrates a profit on the history.
To get answers to these questions, we turn to the Strategy Tester, included in the MetaTrader 5 client terminal.
Tick Generation Modes
An Expert Advisor is a program, written in MQL5, that is run each time in response to some external event. The EA has a corresponding function (event handler) for each pre-defined event.
The NewTick event (price change) is the main event for the EA and, therefore, we need to generate a tick sequence to test the EA. There are 3 modes of tick generation implemented in the Strategy Tester of MetaTrader 5 client terminal:
- Every tick
- 1 Minute OHLC (OHLC prices with minute bars)
- Open prices only
The basic and the most detailed is the "Every tick" mode, the other two modes are the simplifications of the basic one, and will be described in comparison to the "Every tick" mode. Consider all three modes in order to understand the differences between them.
"Every Tick"
The historical quotes data for financial instruments is transferred from the trading server to the MetaTrader 5 client terminal in the form of packed minute bars. Detailed information on the occurrence of requests and the construction of the required time-frames can be obtained from the Organizing Data Access chapter of MQL5 Reference.
The minimal element of the price history is the minute bar, from which you can obtain information on the four values of the price:
- Open - the price at which the minute bar was opened;
- High - the maximum that was achieved during this minute bar;
- Low - the minimum that was achieved during this minute bar;
- Close - the closing price of the bar.
The new minute bar is not opened at the moment when the new minute begins (number of seconds becomes equal to 0), but when a tick occurs - a price change by at least one point. The figure shows the first minute bar of the new trading week, which has the opening time of 2011.01.10 00:00. The price gap between Friday and Monday, which we see on the chart, is common, since currency rates fluctuates even on weekends in response to incoming news.
Fig. 1. The price gap between Friday and Monday
For this bar, we only know that the minute bar was opened on January 10th 2011 at 00 hours 00 minutes, but we know nothing about the seconds. It could have been opened at 00:00:12 or 00:00:36 (12 or 36 seconds after the start of a new day) or any other time within that minute. But we do know that the Open price of EURUSD was at 1.28940 at the opening time of the new minute bar.
We also don't know to within a second when the tick, corresponding to the closing price of the considered minute bar, was received. We known only one thing - the last Close price of the minute bar. For this minute, the price was 1.28958. The time of the appearance of High and Low prices is also unknown, but we know that the maximum and minimum prices were on the levels of 1.28958 and 1.28940, respectively.
To test the trading strategy, we need a sequence of ticks, on which the work of the Expert Advisor will be simulated. Thus, for every minute bar, we know the 4 control points, where the price has definitely been. If a bar has only 4 ticks, then this is enough information to perform a testing, but usually the tick volume is greater than 4.
Hence, there is a need to generate additional control points for ticks, which occurred between the Open, High, Low, and Close prices. The principles of the "Every tick" ticks generation mode is described in the The Algorithm of Ticks’ Generation within the Strategy Tester of the MetaTrader 5 Terminal a figure from which is presented below.
Fig. 2. Ticks generation algorithm
When testing in the "Every tick" mode, the OnTick() function of the EA will be called at every control point. Each control point is a tick from a generated sequence. The EA will receive the time and price of the simulated tick, just as it would when working online.
Important: the "Every tick" testing mode is the most accurate, but at the same time, the most time consuming. For an initial testing of the majority of trading strategies, it is usually sufficient to use one of the other two testing modes.
"1 Minute OHLC"
The "Every tick" mode is the most accurate of the three modes, but at the same time, is the slowest. The running of the OnTick() handler occurs at every tick, while tick volume can be quite large. For a strategy, in which the tick sequence of price movement throughout the bar, does not matter, there is a faster and rougher simulation mode - "1 minute OHLC".
In the "1 minute OHLC" mode, the tick sequence is constructed only by the OHLC prices of the minute bars, the number of the generated control points is significantly reduced - hence, so is the testing time. The launch of the OnTick () function is performed on all control points, which are constructed by the prices of OHLC minute bars.
The refusal to generate additional intermediate ticks between the Open, High, Low, and Close prices, leads to an appearance of rigid determinism in the development of prices, from the moment that the Open price is determined. This makes it possible to create a "Testing Grail", which shows a nice upward graph of the testing balance.
An example of such Grail is presented in the Code Base - Grr-al.
Fig. 3. The Grr-al Expert Advisor, it uses the special features of OHLC prices
The figure shows a very attractive graph of this EA testing. How was it obtained? We know 4 prices for a minute bar, and we also know that the first is the Open price, and the last is the Close price. We have the High and Low prices between them, and the sequence of their occurrence is unknown, but it is known, that the High price is greater than or equal to the Open price (and the Low price is less than or equal to the Open price).
Is sufficient enough to determine the time of receiving the Open price, and then analyze the next tick, to determine whether the High or the Low price comes before it. If the price is below the Open price, then we have a Low price and buy at this tick, the next tick will correspond to the High price, at which we will closing the buy and open for sell. The next tick is the last one, this is the Close price, and we close the sale on it.
If after the price, we receive a tick with a price greater than the opening price, then the sequence of deals is reversed. Process a minute bar in this "cheat" mode, and wait for the next one.
When testing such EA on the history, everything goes smoothly, but once we launch it online, the truth begins to get revealed - the balance line remains steady, but heads downwards. To expose this trick, we simply need to run the EA in the "Every tick" mode.
Note: If the test results of the EA in the rough testing modes ("1 minute OHLC" and "Open Prices only") seem too good, make sure to test it in the "Every tick" mode.
"Open Prices Only"
In this mode ticks are generated based on the OHLC prices of the timeframe selected for testing. The OnTick() function of the Expert Advisor runs only at the beginning of the bar at the Open price. Due to this feature, stop levels and pending may trigger at a price that differs from the specified one (especially when testing on higher timeframes). Instead, we have an opportunity to quickly run an evaluation test of the Expert Advisor.
An exception in the generation of ticks in the "Open Price Only" mode is the W1 and MN1 periods: for these timeframes ticks are generated for the OHLC prices of each day, not OHLC prices of the week or month.
- The calculation of margin requirements;
- The triggering of Stop Loss and Take Profit levels;
- The triggering of pending orders;
- The removal of expired pending orders.
If there are no open positions or pending orders, we don't need to perform these checks on hidden ticks, and the increase of speed may be quiet substantial. This "Open prices only" mode is well suited for testing strategies, which process deals only at the opening of the bar and do not use pending orders, as well as StopLoss and TakeProfit orders. For the class of such strategies, the necessary accuracy of testing is preserved.
Let's use the Moving Average Expert Advisor from the standard package as an example of an EA, which can be tested in any mode. The logic of this EA is built in such a way that all of the decisions are made at the opening of the bar, and deals are carried out immediately, without the use of pending orders.
Run a testing of the EA on EURUSD H1 on an interval from 2010.01.09 to 2010.31.12, and compare the graphs. The figure shows the balance graph from the test report for all of the three modes.
Fig. 4. The testing graph of the Moving Average.mq5 EA from the standard package does not depend on the testing mode (click on the image to zoom-in)
As you can see, the graphs on different testing modes are exactly the same for the Moving Average EA from the standard package.
There are some limitations on the "Open Prices Only" mode:
- You cannot use the Random Delay execution mode.
- In the tested Expert Advisor, you cannot access data of the timeframe lower than that used for testing/optimization. For example, if your run testing/optimization on the H1 period, you can access data of H2, H3, H4 etc., but not M30, M20, M10 etc. In addition, the higher timeframes that are accessed must be multiple of the testing timeframe. For example, if you run testing in M20, you cannot access data of M30, but it is possible to access H1. These limitations are connected with the impossibility to obtain data of lower or non-multiple timeframes out of the bars generated during testing/optimization.
- Limitations on accessing data of other timeframes also apply to other symbols whose data are used by the Expert Advisor. In this case the limitation for each symbol depends on the first timeframe accessed during testing/optimization. Suppose, during testing on EURUSD H1, an Expert Advisor accesses data of GBPUSD M20. In this case the Expert Advisor will be able to further use data of EURUSD H1, H2, etc., as well as GBPUSD M20, H1, H2 etc.
Note: The "Open prices only" mode has the fastest testing time, but it is not suitable for all of the trading strategies. Select the desired test mode based on the characteristics of the trading system.
To conclude the section on the tick generation modes, let's consider a visual comparison of the different tick generation modes for EURUSD, for two M15 bars on an interval from 2011.01.11 21:00:00 - 2011.01.11 21:30:00.
The ticks were saved into different files using the WriteTicksFromTester.mq5 EA and the ending of these files names are specified in filenamEveryTick, filenameOHLC and filenameOpenPrice input-parameters.
Fig. 5. We can specify the starting and the ending dates of the ticks (the variables start and end) for the WriteTicksFromTester Expert Advisor
To obtain three files with three tick sequences (for each of the following modes "Every tick", "1 minute OHLC" and "Open prices only), the EA was launched three times in the corresponding modes, in single runs. Then, the data from these three files were displayed on the chart using the TicksFromTester.mq5 indicator. The indicator code is attached to this article.
Fig. 6. The tick sequence in the Strategy Tester of the MetaTrader 5 terminal in three different testing modes
By default, all of the file operations in the MQL5 language are made within the "file sandbox", and during testing the EA has access only to its own "file sandbox". In order for the indicator and the EA to work with files from one folder during testing, we used the flag FILE_COMMON. An example of code from the EA:
//--- open the file file=FileOpen(filename,FILE_WRITE|FILE_CSV|FILE_COMMON,";"); //--- check file handle if(file==INVALID_HANDLE) { PrintFormat("Error in opening of file %s for writing. Error code=%d",filename,GetLastError()); return; } else { PrintFormat("The file will be created in %s folder",TerminalInfoString(TERMINAL_COMMONDATA_PATH)); }
For reading the data in the indicator, we also used the flag FILE_COMMON. This allowed us to avoid manually transferring the necessary files from one folder to another.
//--- open the file int file=FileOpen(fname,FILE_READ|FILE_CSV|FILE_COMMON,";"); //--- check file handle if(file==INVALID_HANDLE) { PrintFormat("Error in open of file %s for reading. Error code=%d",fname,GetLastError()); return; } else { PrintFormat("File will be opened from %s",TerminalInfoString(TERMINAL_COMMONDATA_PATH)); }
Simulation of spread
The price difference between the Bid and the Ask prices is called the spread. During testing, the spread is not modeled but is taken from historical data. If the spread is less than or equal to zero in the historical data, then the spread for the time of the requested historical data is used by testing agent.
In the Strategy Tester, the spread is always considered floating. That is the SymbolInfoInteger(symbol, SYMBOL_SPREAD_FLOAT) always returns true.
In addition, the historical data contains tick values and trading volumes. For the storage and retrieval of data we use a special MqlRates structure:
struct MqlRates { datetime time; // opening bar time double open; // opening price Open double high; // the highest price High double low; // the lowest price Low double close; // the closing price Close long tick_volume; // the tick volume int spread; // spread long real_volume; // market volume };
The Global Variables of the Client Terminal
During testing, the global variables of the client terminal are also emulated, but they are not related to the current global variables of the terminal, which can be seen in the terminal using the F3 button. It means that all operations with the global variables of the terminal, during testing, take place outside of the client terminal (in the testing agent).
The Calculation of Indicators During Testing
In the real-time mode, the indicator values are calculated at every tick. The Strategy Tester adopted a cost-effective model for calculating indicators - indicators are recalculated only immediately before the running of the EA. It means that the recalculation of the indicators is done before the call of the OnTick(), OnTrade() and OnTimer() functions.
It does not matter whether or not there is a call for the indicator in a specific event handler, all of the indicators, the handles of which were created by the iCustom() or IndicatorCreate() functions will be recalculated before calling the event handler.
Consequently, when testing in the "Every tick" mode, the calculation of the indicators takes place before the call of OnTick() function.
If the timer is on in the EA, using the EventSetTimer() function, then the indicators will be recalculated before each call of the OnTimer() handler. Therefore, the testing time can be greatly increased with the use of an indicators, written in a non-optimal way.
Loading History during Testing
The history of a tested symbol is synchronized and loaded by the terminal from the trade server before starting the testing process. During the first time, the terminal loads all available history of a symbol in order not to request it later. Further only the new data are loaded.
A testing agent receives the history of a tested symbol from the client terminal right after the start of testing. If data of other instruments are used in the process of testing (for example, it is a multicurrency Expert Advisor), the testing agent requests the required history from the client terminal during the first call to such data. If historical data are available in the terminal, they are immediately passed to the testing agent. If data are not available, the terminal requests and downloads them from the server, and then passes to the testing agent.
Data of additional instruments is also required for calculating cross-rates for trade operations. For example, when testing a strategy on EURCHF with the deposit currency in USD, prior to processing the first trading operation, the testing agent requests the history data of EURUSD and USDCHF from the client terminal, though the strategy does not contain direct use call of these symbols.
Before testing a multi-currency strategy, it is recommended to download all the necessary historical data to the client terminal. This will help to avoid delays in testing/optimization associated with download of the required data. You can download history, for example, by opening the appropriate charts and scrolling them to the history beginning. An example of forced loading of history into the terminal is available in the Organizing Access to Data section of the MQL5 Reference.
Multi-Currency Testing
The Strategy Tester allows us to perform a testing of strategies, trading on multiple symbols. Such EAs are conventionally referred to as multi-currency Expert Advisors, since originally, in the previous platforms, testing was performed only for a single symbol. In the Strategy Tester of the MetaTrader 5 terminal, we can model trading for all of the available symbols.
The tester loads the history of the used symbols from the client terminal (not from the trade server!) automatically during the first call of the symbol data.
The testing agent downloads only the missing history, with a small margin to provide the necessary data on the history, for the calculation of the indicators at the starting time of testing. For the time-frames D1 and less, the minimum volume of the downloaded history is one year.
Thus, if we run a testing on an interval 2010.11.01-2010.12.01 (testing for an interval of one month) with a period of M15 (each bar is equal to 15 minutes), then the terminal will be requested the history for the instrument for the entire year of 2010. For the weekly time-frame, we will request a history of 100 bars, which is about two years (a year has 52 weeks). For testing on a monthly time-frame the agent will request the history of 8 years (12 months x 8 years = 96 months).
If there isn't necessary bars, the starting date of testing will be automatically shifted from past to present to provide the necessary reserve of bars before the testing.
During testing, the "Market Watch" is emulated as well, from which one can obtain information on symbols.
By default, at the beginning of testing, there is only one symbol in the "Market Watch" of the Strategy Tester - the symbol that the testing is running on. All of the necessary symbols are connected to the "Market Watch" of the Strategy Tester (not terminal!) automatically when referred to.
Prior to starting testing of a multicurrenncy Expert Advisor, it is necessary to select symbols required for testing in the "Market Watch" of the terminal and load the required data. During the first call of a "foreign" symbol, its history will be automatically synchronized between the testing agent and the client terminal. A "foreign" symbol is the symbol other than that on which testing is running.
Referral to the data of an "other" symbol occurs in the following cases:
- When using the technical indicators function and IndicatorCreate() on the symbol/timeframe;
- The request to the "Market Watch" data for the other symbol:
- Request of the time-series for a symbol/timeframe by using the following functions:
At the moment of the first call to an "other" symbol, the testing process is stopped and the history is downloaded for the symbol/timeframe, from the terminal to the testing agent. At the same time, the generation of tick sequence for this symbol is made.
An individual tick sequence is generated for each symbol, according to the selected tick generation mode. You can also request the history explicitly for the desired symbols by calling the SymbolSelect() in the OnInit() handler - the downloading of the history will be made immediately prior to the testing of the Expert Advisor.
Thus, it does not require any extra effort to perform multi-currency testing in the MetaTrader 5 client terminal. Just open the charts of the appropriate symbols in the client terminal. The history will be automatically uploaded from the trading server for all the required symbols, provided that it contains this data.
Simulation of Time in the Strategy Tester
During testing, the local time TimeLocal() is always equal to the server time TimeTradeServer(). In turn, the server time is always equal to the time corresponding to the GMT time - TimeGMT(). This way, all of these functions display the same time during testing.
The lack of a difference between the GMT, the Local, and the server time in the Strategy Tester is done deliberately in case there is no connection to the server. The test results should always be the same, regardless of whether or not there is a connection. Information about the server time is not stored locally, and is taken from the server.
The OnTimer() Function in the Strategy Tester
MQL5 provides the opportunity for handling timer events. The call of the OnTimer() handler is done regardless of the test mode.
It means that if a test is running in the "Open prices only" mode for the period H4, and the EA has a timer set to a call per second, then at the opening of each H4 bar, the OnTick() handler will be called one time, and the OnTimer() handler will be called 14400 times (3600 seconds * 4 hours). The amount by which the testing time of the EA will be increased depends on the logic of the EA.
To check the dependence of the testing time from the given frequency of the timer, a simple EA, without any trading operations, was written.
//--- input parameters input int timer=1; // timer value, sec input bool timer_switch_on=true; // timer on //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- run the timer if timer_switch_on==true if(timer_switch_on) { EventSetTimer(timer); } //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- stop the timer EventKillTimer(); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- // take no actions, the body of the handler is empty } //+------------------------------------------------------------------+
Testing time measurements were taken at different values of the timer parameter (periodicity of the Timer event). On the obtained data, we plot a testing time as function of Timer period.
Fig. 7. Testing time as a function of Timer period
It can be clearly seen that the smaller is the parameter timer, during the initialization of the EventSetTimer(Timer) function, the smaller is the period (Period) between the calls of the OnTimer() handler, and the larger is the testing time T, under the same other conditions.
The Sleep() Function in the Strategy Tester
The Sleep() function allows the EA or script to suspend the execution of the mql5-program for a while, when working on the graph. This can be useful when requesting data, which is not ready at the time of the request and you need to wait until it is ready. A detailed example of using the Sleep() function can be found in the section Data access arrangement.
The testing process is not lingered by the Sleep() calls.When you call the Sleep(), the generated ticks are "played" within a specified delay, which may result in the triggering of pending orders, stops, etc. After a Sleep() call, the simulated time in the Strategy Tester increases by an interval, specified in the parameter of the Sleep function.
If as a result of the execution of the Sleep() function, the current time in the Strategy Tester went over the testing period, then you will receive an error "Infinite Sleep loop detected while testing". If you receive this error, the test results are not rejected, all of the computations are performed in their full volume (the number of deals, subsidence, etc.), and the results of this testing are passed on to the terminal.
The Sleep() function will not work in OnDeinit(), since after it is called, the testing time will be guaranteed to surpass the range of the testing interval.
Fig. 8. The scheme of using the Sleep() function in the Strategy Tester of the MetaTrader 5 terminal
Using the Strategy Tester for Optimization Problems in Mathematical Calculations
The tester in the MetaTrader 5 terminal can be used, not only to testing trading strategies, but also for mathematical calculations. To use it, it's necessary to select the "Math calculations" mode:
In this case, only three functions will be called: OnInit(), OnTester(), OnDeinit(). In "Math calculations" mode the Strategy Tester doesn't generate any ticks and download the history.
The Strategy Tester works in "Math calculations" mode also if you specify the starting date greater than ending date.
When using the tester to solve mathematical problems, the uploading of the history and the generation of ticks does not occur.
A typical mathematical problem for solving in the MetaTrader 5 Strategy Tester - searching for an extremum of a function with many variables.
To solve it we need to:
- The calculation of function value should be located in OnTester() function;
- The function parameters must be defined as input-variables of the Expert Advisor;
Compile the EA, open the "Strategy Tester" window. In the "Input parameters" tab, select the required input variables, and define the set of parameter values by specifying the start, stop and step values for each of the function variables.
Select the optimization type - "Slow complete algorithm" (full search of parameters space) or "Fast genetic based algorithm". For a simple search of the extremum of the function, it is better to choose a fast optimization, but if you want to calculate the values for the entire set of variables, then it is best to use the slow optimization.
Select "Math calculation" mode and using the "Start" button, run the optimization procedure. Note that when optimization the Strategy Tester will search for the maximum values of the function. To find a local minimum, return the inverse of the computed function value from the OnTester function:
return(1/function_value);
It is necessary to check that the function_value is not equal to zero, since otherwise we can obtain a critical error of dividing by zero.
There is another way, it is more convenient and does not distort the results of optimization, it was suggested by the readers of this article:
return(-function_value);
This option does not require the checking of the function_value_ for being equal to zero, and the surface of the optimization results in a 3D-representation has the same shape, but is mirrored of the original.
As an example, we provide the sink() function:
The code of the EA for finding the extremum of this function is placed into the OnTester():
//+------------------------------------------------------------------+ //| Sink.mq5 | //| Copyright 2011, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //--- input parameters input double x=-3.0; // start=-3, step=0.05, stop=3 input double y=-3.0; // start=-3, step=0.05, stop=3 //+------------------------------------------------------------------+ //| Tester function | //+------------------------------------------------------------------+ double OnTester() { //--- double sink=MathSin(x*x+y*y); //--- return(sink); } //+------------------------------------------------------------------+Perform an optimization and see the optimization results in the form of a 2D graph.
Fig. 9. The results of the full optimization of the sink (x*x+y*y) function as 2D-graph
The better the value is for a given pair of parameters (x, y), the more saturated the color is. As was expected from the view of the form of the sink() formula, its values forms concentric circles with a center at (0,0). One can see in the 3D-graph, that the sink() function has no single global extremum:
The Synchronization of Bars in the "Open prices only" mode
The tester in the MetaTrader 5 client terminal allows us to check the so-called "multi-currency" EAs. A multi-currency EA - is an EA that trades on two or more symbols.
The testing of strategies, that are trading on multiple symbols, imposes a few additional technical requirements on the tester:
- The generation of ticks for these symbols;
- The calculation of indicator values for these symbols;
- The calculation of margin requirements for these symbols;
- Synchronization of generated tick sequences for all trading symbols.
The Strategy Tester generates and plays a tick sequence for each instrument in accordance with the selected trading mode. At the same time, a new bar for each symbol is opened, regardless of how the bar opened on another symbol. This means that when testing a multi-currency EA, a situation may occur (and often does), when for one instrument a new bar has already opened, and for the other it has not. Thus, in testing, everything happens just like in actuality.
This authentic simulation of the history in the tester does not cause any problems as long as the "Every tick" and "1 minute OHLC" testing modes are used. For these modes, enough ticks are generated for one candlestick, to be able to wait until the synchronization of bars from different symbols takes place. But how do we test multi-currency strategies in the "Open prices only" mode, if the synchronization of bars on trading instruments is mandatory? In this mode, the EA is called only on one tick, which corresponds to the time of the opening of the bars.
We'll illustrate it on an example: if we are testing an EA on the EURUSD, and a new hourly candlestick has opened on EURUSD, then we can easily recognize this fact - in testing in the "Open prices only" mode", the event NewTick corresponds to the moment of the bar opening on the testing period. But there is no guarantee that the new candlestick opened on the symbol USDJPY, which is used in the EA.
Under normal circumstances, it is sufficient enough to complete the work of the OnTick() function and to check for the emergence of a new bar on USDJPY at the next tick. But when testing in the "Open prices only" mode, there will be no other tick, and so it may seem that this mode is not fit for testing multi-currency EAs. But this is not so - do not forget that the tester in MetaTrader 5 behaves just as it would in real life. You can wait until a new bar is opened on another symbols using the function Sleep()!
The code of the EA Synchronize_Bars_Use_Sleep.mq5, which shows an example of the synchronization of bars in the "Open prices only" mode:
//+------------------------------------------------------------------+ //| Synchronize_Bars_Use_Sleep.mq5 | //| Copyright 2011, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //--- input parameters input string other_symbol="USDJPY"; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- check symbol if(_Symbol==other_symbol) { PrintFormat("You have to specify the other symbol in input parameters or select other symbol in Strategy Tester!"); //--- forced stop testing return(-1); } //--- return(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- static variable, used for storage of last bar time static datetime last_bar_time=0; //--- sync flag static bool synchonized=false; //--- if static variable isn't initialized if(last_bar_time==0) { //--- it's first call, save bar time and exit last_bar_time=(datetime)SeriesInfoInteger(_Symbol,Period(),SERIES_LASTBAR_DATE); PrintFormat("The last_bar_time variable is initialized with value %s",TimeToString(last_bar_time)); } //--- get open time of the last bar of chart symbol datetime curr_time=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE); //--- if times aren't equal if(curr_time!=last_bar_time) { //--- save open bar time to the static variable last_bar_time=curr_time; //--- not synchronized synchonized=false; //--- print message PrintFormat("A new bar has appeared on symbol %s at %s",_Symbol,TimeToString(TimeCurrent())); } //--- open time of the other symbol's bar datetime other_time; //--- loop until the open time of other symbol become equal to curr_time while(!(curr_time==(other_time=(datetime)SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE)) && !synchonized)) { PrintFormat("Waiting 5 seconds.."); //--- wait 5 seconds and call SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE) Sleep(5000); } //--- bars are synchronized synchonized=true; PrintFormat("Open bar time of the chart symbol %s: is %s",_Symbol,TimeToString(last_bar_time)); PrintFormat("Open bar time of the symbol %s: is %s",other_symbol,TimeToString(other_time)); //--- TimeCurrent() is not useful, use TimeTradeServer() Print("The bars are synchronized at ",TimeToString(TimeTradeServer(),TIME_SECONDS)); } //+------------------------------------------------------------------+
Notice the last line in the EA, which displays the current time when the fact of synchronization was established:
Print("The bars synchronized at ",TimeToString(TimeTradeServer(),TIME_SECONDS));
To display the current time we used the TimeTradeServer () function rather than TimeCurrent (). The TimeCurrent() function returns the time of the last tick, which does not change after using Sleep(). Rub the EA in the "Open prices only" mode, and you will see a message about the synchronization of the bars.
Use the TimeTradeServer() function instead of the TimeCurrent(), if you need to obtain the current server time, and not the time of the last tick arrival.
There is another way to synchronize bars - using a timer. An example of such an EA is Synchronize_Bars_Use_OnTimer.mq5, which is attached to this article.
The IndicatorRelease() function in the Tester
After completing a single testing, a chart of the instrument is automatically opened, which displays the completed deals and the indicators used in the EA. This helps to visually check the entry and exit points, and compare them with the values of the indicators.
Note: indicators, displayed on the chart, which automatically opens after the completion of the testing, are calculated anew after the completion of testing. Even if these indicators were used in the tested EA.
But in some cases, the programmer may want to hide the information on which indicators were involved in the trading algorithms. For example, the code of the EA is rented or sold as an executable file, without the provision of the source code. For this purpose, the IndicatorRelease() function is suitable.
If the terminal sets a template with the name tester.tpl in the directory/profiles/templates of the client terminal, then it will be applied to the opened chart. In its absence, the default template is applied. (default.tpl).
The IndicatorRelease() function is originally intended for releasing the calculating portion of the indicator, if it is no longer needed. This allows you to save both, the memory and the CPU resources, because each tick calls for an indicator calculation. Its second purpose - is to prohibit the showing of an indicator on the testing chart, after a single test run.
To prohibit the showing of the indicator on the chart after testing, call the IndicatorRelease() with the handle of the indicator in the handler OnDeinit(). The OnDeinit() function is always called after the completion and before the showing of the testing chart.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- bool hidden=IndicatorRelease(handle_ind); if(hidden) Print("IndicatorRelease() successfully completed"); else Print("IndicatorRelease() returned false. Error code ",GetLastError()); }
Event Handling in the Tester
The presence of the OnTick() handler in the EA is not mandatory in order for it to be subjected to testing on historical data in the MetaTrader 5 tester. It is sufficient enough for the EA ti contain at least one of the following function-handlers:
- OnTick() - Event handler of a new tick arrival;
- OnTrade() - Trading event handler;
- OnTimer() - Event handler of a signal arrival from the timer;
- OnChartEvent () - a handler for client events.
When testing in an EA, we can handle custom events using the OnChartEvent() function, but in the indicators, this function can not be called in the tester. Even if the indicator has the OnChartEvent() event handler and this indicator is used in the tested EA, the indicator itself will not receive any custom events.
During testing, an Indicator can generate custom events using the EventChartCustom() function, and the EA can process this event in the OnChartEvent().
Testing Agents
Testing in the MetaTrader 5 client terminal is carried out using test agents. Local agents are created and enabled automatically. The default number of local objects corresponds to the number of cores in a computer.
Each testing agent has its own copy of the global variables, which is not related to the client terminal. The terminal itself is the dispatcher, which distributes the tasks to the local and remote agents. After executing a task on the testing of an EA, with the given parameters, the agent returns the results to the terminal. With a single test, only one agent is used.
The agent stores the history, received from the terminal, in separate folders, by the name of the instrument, so the history for EURUSD is stored in a folder named EURUSD. In addition, the history of the instruments is separated by their sources. The structure for storing the history looks the following way:
For example, the history for EURUSD from the server MetaQuotes-Demo can be stored in the folder tester_catalog\Agent-127.0.0.1-3000\bases\MetaQuotes-Demo\EURUSD.
A local agent, after the completion of testing, goes into a standby mode, awaiting for the next task for another 5 minutes, so as not to waste time on launching for the next call. Only after the waiting period is over, the local agent shuts down and unloads from the CPU memory.
In case of an early completion of the testing, from the user's side (the "Cancel" button), as well as with the closing of the client terminal, all local agents immediately stop their work and are unloaded from the memory.
The Data Exchange between the Terminal and the Agent
When you run a test, the client terminal prepares for send the agent a number of parameter blocks: - Input parameters for testing (simulation mode, the interval of testing, instruments, optimization criterion, etc.)
- The list of the selected symbols in the "Market Watch"
- The specification of the testing symbol (the contract size, the allowable margins from the market for setting a StopLoss and Takeprofit, etc)
- The tested Expery Advisor and the values of its input parameters
- Information about additional files (libraries, indicators, data files - # property tester_ ...)
tester_indicator
Name of a custom indicator in the format of "indicator_name.ex5". Indicators that require testing are defined automatically from the call of the iCustom() function, if the corresponding parameter is set through a constant string. For all other cases (use of the IndicatorCreate() function or use of a non-constant string in the parameter that sets the indicator name) this property is required
tester_file
File name for a tester with the indication of extension, in double quotes (as a constant string). The specified file will be passed to tester. Input files to be tested, if there are necessary ones, must always be specified.
tester_library
Library name with the extension, in double quotes. A library can have extension dll or ex5. Libraries that require testing are defined automatically. However, if any of libraries is used by a custom indicator, this property is required
For each block of parameters, a digital fingerprint in the form of MD5-hash is created, which is sent to the agent. MD5-hash is unique for each set, its volume is many more times smaller than the amount of information on which it is calculated.
The agent receives a hash of blocks and compares them with those that it already has. If the fingerprint of the given parameter block is not present in the agent, or the received hash is different from the existing one, the agent requests this block of parameters. This reduces the traffic between the terminal and the agent.
After the testing, the agent returns to the terminal all of the results of the run, which are shown in the tabs "Test Results" and "Optimization Results": the received profit, the number of deals, the Sharpe coefficient, the result of the OnTester() function, etc.
During optimizing, the terminal hands out testing tasks to the agents in small packages, each package contains several tasks (each task means single testing with a set of input parameters). This reduces the exchange time between the terminal and the agent.
The agents never record to the hard disk the EX5-files, obtained from the terminal (EA, indicators, libraries, etc.) for security reasons, so that a computer with a running agent could not use the sent data. All other files, including DLL, are recorded in the sandbox. In remote agents you can not test EAs using DLL.
The testing results are added up by the terminal into a special cache of results (the result cache), for a quick access to them when they are needed. For each set of parameters, the terminal searches the result cache for already available results from the previous runs, in order to avoid re-runs. If the result with such a set of parameters is not found, the agent is given the task to conduct the testing.
All traffic between the terminal and the agent is encrypted.
Using the Shared Folder of All of the Client Terminals
All testing agents are isolated from each other and from the client terminal: each agent has its own folder in which its logs are recorded. In addition, all file operations during the testing of the agent occur in the folder agent_name/MQL5/Files. However, we can implement the interaction between the local agents and the client terminal through a shared folder for all of the client terminals, if during the file opening you specify the flag FILE_COMMON:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- the shared folder for all of the client terminals common_folder=TerminalInfoString(TERMINAL_COMMONDATA_PATH); //--- draw out the name of this folder PrintFormat("Open the file in the shared folder of the client terminals %s", common_folder); //--- open a file in the shared folder (indicated by FILE_COMMON flag) handle=FileOpen(filename,FILE_WRITE|FILE_READ|FILE_COMMON); ... further actions //--- return(0); }
Using DLLs
To speed up the optimization we can use not only local, but also remote agents. In this case, there are some limitations for remote agents. First of all, remote agents do not display in their logs the results of the execution of the Print() function, messages about the opening and closing of positions. A minimum of information is displayed in the log to prevent incorrectly written EAs from trashing up the computer, on which the remove agent is working, with messages.
A second limitation - the prohibition on the use of DLL when testing EAs. DLL calls are absolutely forbidden on remote agents for security reasons. On local agent, DLL calls in tested EAs are allowed only with the appropriate permission "Allow import DLL".
Fig. 10. The option "Allow import DLL" in mql5-programs
Note: when using the received from EAs (scripts, indicators) that require the allowing of DLL calls, you should be aware of the risks, which you assume when allowing this option in the settings of the terminal. Regardless of how the EA will be used - for testing or for running on a chart.
Conclusion
The article deals with the basics, the knowledge of which will help you to quickly master the testing of EAs in the MetaTrader 5 client terminal:
- Three modes of tick generation;
- Calculation of indicator values during testing;
- Multi-currency testing;
- Simulation of time during testing;
- The work of the OnTimer(), OnSleep(), and IndicatorRelease() functions in the Strategy Tester;
- The work of testing agents during DLL calls;
- Using the shared folder for all of the client terminals;
- Synchronization of bars in the "Open prices only" mode;
- Event handling.
The main task of the Strategy Tester of the client terminal is to ensure the required accuracy of the data with the minimum effort from the programmer of MQL5. Developers have done a lot so that you did not have to rewrite your code just to test your trading strategy on historical data. It is suffice enough to know the basics of testing, and a correctly written EA will work equally as well in the tester and in the on-line mode on the chart.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/239
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
I was reading through this wonderful article and it looks there was a typo error somewhere.
I am thinking, you wanted to type 'if(hidden) Print......' instead of 'if(hided) Print...' so that the code looks like below
Thanks for the article.
Sam
Great article, thanks!
I was reading through this wonderful article and it looks there was a typo error somewhere.
manual trading is tiring. I hope to find a robot soon that will bring me more than it cost me