- Generating ticks in tester
- Time management in the tester: timer, Sleep, GMT
- Testing visualization: chart, objects, indicators
- Multicurrency testing
- Optimization criteria
- Getting testing financial statistics: TesterStatistics
- OnTester event
- Auto-tuning: ParameterGetRange and ParameterSetRange
- Group of OnTester events for optimization control
- Sending data frames from agents to the terminal
- Getting data frames in terminal
- Preprocessor directives for the tester
- Managing indicator visibility: TesterHideIndicators
- Emulation of deposits and withdrawals
- Forced test stop: TesterStop
- Big Expert Advisor example
- Mathematical calculations
- Debugging and profiling
- Limitations of functions in the tester
Getting testing financial statistics: TesterStatistics
We usually evaluate the quality of an Expert Advisor based on a trading report, which is similar to a testing report when dealing with a tester. It contains a large number of variables that characterize the trading style, stability and, of course, profitability. All these metrics, with some exceptions, are available to the MQL program through a special function TesterStatistics. Thus, the Expert Advisor developer has the ability to analyze individual variables in the code and construct their own combined optimization quality criteria from them.
double TesterStatistics(ENUM_STATISTICS statistic)
The TesterStatistics function returns the value of the specified statistical variable, calculated based on the results of a separate run of the Expert Advisor in the tester. A function can be called in the OnDeinit or OnTester handler, which is yet to be discussed.
All available statistical variables are summarized in the ENUM_STATISTICS enumeration. Some of them serve as qualitative characteristics, that is, real numbers (usually total profits, drawdowns, ratios, and so on), and the other part is quantitative, that is, integers (for example, the number of transactions). However, both groups are controlled by the same function with the double result.
The following table shows real indicators (monetary amounts and coefficients). All monetary amounts are expressed in the deposit currency.
Identifier |
Description |
---|---|
STAT_INITIAL_DEPOSIT |
Initial deposit |
STAT_WITHDRAWAL |
The amount of funds withdrawn from the account |
STAT_PROFIT |
Net profit or loss at the end of testing, the sum of STAT_GROSS_PROFIT and STAT_GROSS_LOSS |
STAT_GROSS_PROFIT |
Total profit, the sum of all profitable trades (greater than or equal to zero) |
STAT_GROSS_LOSS |
Total loss, the sum of all losing trades (less than or equal to zero) |
STAT_MAX_PROFITTRADE |
Maximum profit: the largest value among all profitable trades (greater than or equal to zero) |
STAT_MAX_LOSSTRADE |
Maximum loss: the smallest value among all losing trades (less than or equal to zero) |
STAT_CONPROFITMAX |
Total maximum profit in a series of profitable trades (greater than or equal to zero) |
STAT_MAX_CONWINS |
Total profit in the longest series of profitable trades |
STAT_CONLOSSMAX |
Total maximum loss in a series of losing trades (less than or equal to zero) |
STAT_MAX_CONLOSSES |
Total loss in the longest series of losing trades |
STAT_BALANCEMIN |
Minimum balance value |
STAT_BALANCE_DD |
Maximum balance drawdown in money |
STAT_BALANCEDD_PERCENT |
Balance drawdown in percent, which was recorded at the time of the maximum balance drawdown in money (STAT_BALANCE_DD) |
STAT_BALANCE_DDREL_PERCENT |
Maximum balance drawdown in percent |
STAT_BALANCE_DD_RELATIVE |
Balance drawdown in money equivalent, which was recorded at the moment of the maximum balance drawdown in percent (STAT_BALANCE_DDREL_PERCENT) |
STAT_EQUITYMIN |
Minimum equity value |
STAT_EQUITY_DD |
Maximum drawdown in money |
STAT_EQUITYDD_PERCENT |
Drawdown in percent, which was recorded at the time of the maximum drawdown of funds in the money (STAT_EQUITY_DD) |
STAT_EQUITY_DDREL_PERCENT |
Maximum drawdown in percent |
STAT_EQUITY_DD_RELATIVE |
Drawdown in money that was recorded at the time of the maximum drawdown in percent (STAT_EQUITY_DDREL_PERCENT) |
STAT_EXPECTED_PAYOFF |
Mathematical expectation of winnings (arithmetic mean of the total profit and the number of transactions) |
STAT_PROFIT_FACTOR |
Profitability, which is the ratio STAT_GROSS_PROFIT/STAT_GROSS_LOSS (if STAT_GROSS_LOSS = 0; profitability takes the value DBL_MAX) |
STAT_RECOVERY_FACTOR |
Recovery factor: the ratio of STAT_PROFIT/STAT_BALANCE_DD |
STAT_SHARPE_RATIO |
Sharpe ratio |
STAT_MIN_MARGINLEVEL |
Minimum margin level reached |
STAT_CUSTOM_ONTESTER |
The value of the custom optimization criterion returned by the OnTester function |
The following table shows integer indicators (amounts).
Identifier |
Description |
---|---|
STAT_DEALS |
Total number of completed transactions |
STAT_TRADES |
Number of trades (deals to exit the market) |
STAT_PROFIT_TRADES |
Profitable trades |
STAT_LOSS_TRADES |
Losing trades |
STAT_SHORT_TRADES |
Short trades |
STAT_LONG_TRADES |
Long trades |
STAT_PROFIT_SHORTTRADES |
Short profitable trades |
STAT_PROFIT_LONGTRADES |
Long profitable trades |
STAT_PROFITTRADES_AVGCON |
Average length of a profitable series of trades |
STAT_LOSSTRADES_AVGCON |
Average length of a losing series of trades |
STAT_CONPROFITMAX_TRADES |
Number of trades that formed STAT_CONPROFITMAX (maximum profit in the sequence of profitable trades) |
STAT_MAX_CONPROFIT_TRADES |
Number of trades in the longest series of profitable trades STAT_MAX_CONWINS |
STAT_CONLOSSMAX_TRADES |
Number of trades that formed STAT_CONLOSSMAX (maximum loss in the sequence of losing trades) |
STAT_MAX_CONLOSS_TRADES |
Number of trades in the longest series of losing trades STAT_MAX_CONLOSSES |
Let's try to use the presented metrics to create our own complex Expert Advisor quality criterion. To do this, we need some kind of "experimental" example of an MQL program. Let's take the Expert Advisor MultiMartingale.mq5 as a starting point, but we will simplify it: we will remove multicurrency, built-in error handling, and scheduling. Moreover, we will choose a signal trading strategy for it with a single calculation on the bar, i.e., at the opening prices. This will speed up optimization and expand the field for experiments.
The strategy will be based on the overbought and oversold conditions determined by the OsMA indicator. The Bollinger Bands indicator superimposed on OsMA will help you dynamically find the boundaries of excess volatility, which means trading signals.
When OsMA returns inside the corridor, crossing the lower border from the bottom up, we will open a buy trade. When OsMA crosses the upper boundary in the same way from top to bottom, we will sell. To exit positions, we use the moving average, also applied to OsMA. If OsMA shows a reverse movement (down for a long position or up for a short position) and touches the MA, the position will be closed. This strategy is illustrated in the following screenshot.
Trading strategy based on OsMA, BBands and MA indicators
The blue vertical line corresponds to the bar where the buy is opened, since on the two previous bars the lower Bollinger band was crossed by the OsMA histogram from the bottom up (this place is marked with a hollow blue arrow in the subwindow). The red vertical line is the location of the reverse signal, so the buy was closed and the sell was opened. In the subwindow, in this place (or rather, on the two previous bars, where the hollow red arrow is located), the OsMA histogram crosses the upper Bollinger band from top to bottom. Finally, the green line indicates the closing of the sale, due to the fact that the histogram began to rise above the red MA.
Let's name the Expert Advisor BandOsMA.mq5. The general settings will include a magic number, a fixed lot, and a stop loss distance in points. For the stop loss, we will use TrailingStop from the previous example. Take profit is not used here.
input group "C O M M O N S E T T I N G S"
|
Three groups of settings are intended for indicators.
input group "O S M A S E T T I N G S"
|
In the MultiMartingale.mq5 Expert Advisor, we had no trading signals, while the opening direction was set by the user. Here we have trading signals, and it makes sense to arrange them as a separate class. First, let's describe the abstract interface TradingSignal.
interface TradingSignal
|
It is as simple as our other interface TradingStrategy. And this is good. The simpler the interfaces and objects, the more likely they are to do one single thing, which is a good programming style because it minimizes bugs and makes large software projects more understandable. Due to abstraction in any program that uses TradingSignal, it will be possible to replace one signal with another. We can also replace the strategy. Our strategies are now responsible for preparing and sending orders, and signals initiate them based on market analysis.
In our case, let's pack the specific implementation of TradingSignal into the BandOsMaSignal class. Of course, we need variables to store the descriptors of the 3 indicators. Indicator instances are created and deleted in the constructor and destructor, respectively. All parameters will be passed from input variables. Note that iBands and iMA are built based on the hOsMA handler.
class BandOsMaSignal: public TradingSignal
|
The direction of the current trading signal is placed in the variable direction: 0 no signals (undefined situation), +1 buy, -1 sell. We will fill in this variable in the signal method. Its code repeats the above verbal description of signals in MQL5.
virtual int signal(void) override
|
As you can see, the indicator values are read for bars 1 and 2, since we will work on opening a bar, and the 0th bar has just opened by the time we we call the signal method.
The new class that implements the TradingStrategy interface will be called SimpleStrategy.
The class provides some new features while also using some previously existing parts. In particular, it retained autopointers for PositionState and TrailingStop and has a new autopointer to the TradingSignal signal. Also, since we are going to trade only on the opening of bars, we needed the lastBar variable, which will store the time of the last processed bar.
class SimpleStrategy: public TradingStrategy
|
The global parameters are passed to the SimpleStrategy constructor. We also pass a pointer to the TradingSignal object: in this case, it will be BandOsMaSignal which will have to be created by the calling code. Next, the constructor tries to find among the existing positions those that have the required magic number and symbol, and if successful, adds a trailing stop. This will be useful if the Expert Advisor has a break for one reason or another, and the position has already been opened.
public:
|
The implementation of the trade method is similar to the martingale example. However, we have removed lot multiplications and added the signal method call.
virtual bool trade() override
|
Auxiliary methods openBuy, openSell, and others have undergone minimal changes, so we will not list them (the full source code is attached).
Since we always have only one strategy in this Expert Advisor, in contrast to the multi-currency martingale in which each symbol required its own settings, let's exclude the strategy pool and manage the strategy object directly.
AutoPtr<TradingStrategy> strategy;
|
We now have a ready Expert Advisor which we can use as a tool for studying the tester. First, let's create an auxiliary structure TesterRecord for querying and storing all statistical data.
struct TesterRecord
|
In this case, the feature string field is needed only for informative log output. To save all indicators (for example, to be able to generate your own report form later), a simple array of type double of appropriate length is enough.
Using the structure in the OnDeinit handler, we make sure that the MQL5 API returns the same values as the tester's report.
void OnDeinit(const int)
|
For example, when running the Expert Advisor on EURUSD, H1 with a deposit of 10000 and without any optimizations (with default settings), we will get approximately the following values for 2021 (fragment):
[feature] [value]
|
Knowing all these values, we can invent our own formula for the combined metric of the Expert Advisor quality and, at the same time, the objective optimization function. But the value of this indicator in any case will need to be reported to the tester. And that's what the OnTester function does.