Expert Advisor Programming For MetaTrader 5-P2
Expert Advisor Programming For MetaTrader 5-P2
If you just need to copy a single data series into an array, you can use one of the following functions instead:
These work similar to the CopyRates() function. First, declare an array of the appropriate type. The type
required for each function is listed above before the function name. Next, set it as a series array using
ArraySetAsSeries(). Then use the appropriate Copy..() function to copy data into the array.
We'll demonstrate using the CopyClose() function. Here is the function definition for CopyClose():
int CopyClose(
string symbol_name, // symbol name
ENUM_TIMEFRAMES timeframe, // period
int start_pos, // start position
int count, // data count to copy
double close_array[] // target array to copy
);
The parameters are nearly identical to CopyRates(). There are several variants of the function that allow you
to specify start and end dates as well. You can view these in the MQL5 Reference under Timeseries and
Indicator Access. Here's an example of the usage of the CopyClose() function:
double close[];
ArraySetAsSeries(close,true);
CopyClose(_Symbol,_Period,0,3,close);
189
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
We declare an array of type double named close[] that will hold our close prices. Next, we set it as a series
array using ArraySetAsSeries(). Finally, the CopyClose() function copies three bars worth of data into the
close[] array. To retrieve the current bar's value, we reference the zero index of the close[] array.
The other Copy...() functions work the same, except that the array type will differ. Refer to the MQL5
Reference for details. If you only need a single price series, then these functions may work better for you. We
will be using the CBars class that we created in the previous section for the remainder of this book.
Assuming that you have your price data already copied into an array, the ArrayMaximum() and
ArrayMinimum() functions can be used to find the highest or lowest value of a price series. Here's how we can
find the lowest low of the last 10 bars:
double low[];
ArraySetAsSeries(low,true);
CopyLow(_Symbol,_Period,0,100,low);
First we declare the low[] array that will hold our price data. We set the array as series, and use the
CopyLow() functions to copy 100 bars of low price data for the current chart symbol and period into our
low[] array.
The ArrayMinimum() function takes as its first parameter the array to search. The second parameter is the
starting position, and the third is the number of bars to search. Starting at the current bar, we look back 10
bars to find the lowest low. The ArrayMinimum() function returns the index value of the bar with the lowest
low. We save this value in the minIdx variable. A call to low[minIdx] will return the lowest low price located
by the ArrayMinimum() function.
You can use ArrayMinimum() or ArrayMaximum() on any one-dimensional numeric array. Most commonly,
we will be searching for the highest high and lowest low of the last X bars. We can create some simple
functions that will return the highest high or lowest low of a specified bar range:
190
Bar and Price Data
return(highest);
}
This function returns the highest high price for the specified bar range. The pSymbol parameter is the symbol
to use, and pPeriod is the chart period to use. The pBars parameter is the number of bars to search, and
pStart is the start location. The default value of pStart is 0, which is the current bar.
We declare the high[] array and set it as series, and then use the CopyHigh() function to copy the high
prices into our array. The pStart parameter determines where we start copying the data from, while the
pBars parameter determines the amount of data in our array. Note the line after the CopyHigh() function – if
the copying fails for some reason, we return a -1 to the calling program to indicate an error.
When we call the ArrayMaximum() function, we only need to specify the array name, since it already contains
all of the data that we need to search. The index of the array that contains the highest high is saved to the
maxIdx variable. We then use high[maxIdx] to retrieve the highest high and return that to the calling
program.
There is also a LowestLow() function that operates identically to the HighestHigh() function above. Both
functions can be viewed in the \MQL5\Include\Mql5Book\Price.mqh include file.
For example, if you have a trading system where you need to check the close price against an indicator line,
you will retrieve the price of the most recently closed bar and compare it to the indicator value of the same
bar. Our simple expert advisor that we created earlier in the book compared the close price of the current bar
to the current moving average value:
191
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
But perhaps you want to wait until the bar closes before opening the order. In that case, we will need to check
the moving average and close values of the previous bar. The array index of the current bar is 0, so the index
of the previous bar would be 1:
We could define our order opening condition even more explicitly by making sure that the close price crossed
the moving average within the last bar. One way of doing this is to check if the previous bar's close price was
below the moving average value:
Because a buy position will only open if the close price is greater than the moving average for the most
current bar, and the close price was less than the moving average for the previous bar, we no longer need to
check the value of the glBuyPlaced variable. This works best if you have a trading condition that always
indicates either a bullish or bearish trading signal at all times.
Candle Patterns
Another use of price data is to detect candle patterns. Many traders use Japanese
candlestick patterns such as the doji, engulfing, harami, hammers/shooting stars, and so
forth. We use price data to identify these patterns on a chart.
Let's take the engulfing pattern, for example. An engulfing pattern is a reversal pattern
where the body of a candle is longer than the body of the previous candle, and the
direction is reversed. For example, you may see an engulfing candle at the bottom of a
downtrend. The previous candle will be bearish, while the most recent candle will be Fig 16.1 – Bullish
Engulfing candle.
192
Bar and Price Data
bullish. The open of the most recent candle will be roughly equal to the close of the previous candle, and the
close of the most recent candle will be above the previous candle's open.
When trading a candle pattern, we will look for a confirmation candle in the direction of the anticipated trend.
In the case of our engulfing bullish reversal pattern, we are looking for a bullish candle following our bullish
engulfing candle.
Let's express this as a trading condition. Assuming that we have two arrays, close[] and open[], filled with
price data, here's how we would locate a bullish engulfing pattern in an expert advisor:
if(close[3] < open[3] && close[2] > open[3] && close[1] > open[1])
{
Print("Bullish engulfing reversal pattern detected");
}
We start three candles back with the bearish candle. The condition close[3] < open[3] means that the
candle is bearish – in other words, the close was lower than the open. Next, we check the engulfing candle.
The open price of a candle is usually identical (or very close) to the close price of the previous candle, so we
won't bother checking the open of the engulfing candle. The important thing to check is whether the close is
greater than the previous bar's open, or close[2] > open[3]. If so, then we have an engulfing candle.
Finally, if close[1] > open[1], then the most recent candle is bullish. If all three candles match the condition,
then a message prints to the log indicating that a bullish engulfing pattern was detected on the chart. Of
course, you could open a buy position as well.
By comparing the open and close values of a bar, you can determine whether a candle is bullish or bearish.
You can also detect candle patterns by examining the open, high, low and close values of consecutive candles.
193
Using Indicators in Expert Advisors
// Input variables
input int MAPeriod = 10;
input int MAShift = 0;
input ENUM_MA_METHOD MAMethod = MODE_EMA;
input ENUM_APPLIED_PRICE MAPrice = PRICE_CLOSE;
195
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
In the OnTick() event handler, we initialize the ma[] array, which will hold our moving average data. The
ArraySetAsSeries() function sets the ma[] array as a series array. The iMA() function takes the input
variables and calculates the indicator for the specified symbol and chart period. Every built-in MetaTrader
indicator has a similar function. You can view them in the MQL5 Reference under Technical Indicators.
int iMA(
string symbol, // symbol name
ENUM_TIMEFRAMES period, // period
int ma_period, // averaging period
int ma_shift, // horizontal shift
ENUM_MA_METHOD ma_method, // smoothing type
ENUM_APPLIED_PRICE applied_price // type of price or handle
);
Every technical indicator function begins with the symbol and period parameters. The remaining parameters
depend on the indicator. The moving average indicator has four parameters that can be adjusted. Some
indicators have just one or two parameters, and a few have none at all. Here are the input variables for the
moving average indicator again:
// Input variables
input int MAPeriod = 10;
input int MAShift = 0;
input ENUM_MA_METHOD MAMethod = MODE_EMA;
input ENUM_APPLIED_PRICE MAPrice = PRICE_CLOSE;
Note the type of each parameter. For example, the ma_method parameter of the iMA() function uses the
ENUM_MA_METHOD type. Therefore, the corresponding input parameter must be of the same type. In this case,
the MAMethod input variable uses the ENUM_MA_METHOD type, so this is the value that we will pass to the
ma_method parameter.
By looking at the function declaration for the specific technical indicator function you need to use, you can
determine the input parameters you'll need to add to your expert advisor. Note that the four input variables
for the moving average indicator match the parameters for the iMA() function.
The iMA() function, as well as all other indicator functions, returns an indicator handle, which is a unique
identifier for the indicator. This handle is used for all actions related to the indicator, including copying data
and removing the indicator from use.
196
Using Indicators in Expert Advisors
The CopyBuffer() function is used to copy the data from the specified indicator buffer into an array. The
array can then be used to access the indicator data. CopyBuffer() works similar to the Copy...() functions
in the previous chapter. Here is the function definition for the CopyBuffer() function:
int CopyBuffer(
int indicator_handle, // indicator handle
int buffer_num, // indicator buffer number
int start_pos, // start position
int count, // amount of bars to copy
double buffer // target array to copy to
);
The indicator_handle parameter accepts the indicator handle that is returned by the iMA() or other
indicator function. The buffer_num parameter is the number of the indicator buffer that you want to copy
data from. Most indicators have only one buffer, although some have two, three or more. More on this in a
minute. The remaining parameters should be familiar to you. The start_pos parameter is the start position to
begin copying from, count is the number of bars to copy, and buffer is the name of the array to copy to.
The iMA() function returns an indicator handle, and saves it to the maHandle variable. The CopyBuffer()
function takes the maHandle value as its first parameter. The buffer number is zero, since the moving average
has only one line. We start at the most recent bar (index of 0) and copy 3 bars worth of data to the ma[] array.
The ma[] array is now filled with data and ready to use. A call to ma[0] will return the moving average value
for the current bar.
Let's take a look at a second indicator that only uses a single buffer. The RSI indicator is very popular, and is
often used to locate price extremes. Here is the function definition for the RSI indicator:
int iRSI(
string symbol, // symbol name
ENUM_TIMEFRAMES period, // period
int ma_period, // averaging period
ENUM_APPLIED_PRICE applied_price // type of price or handle
);
197
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
Just like the iMA() function, the first two parameters are symbol and period. The RSI takes two input
parameters, one for the averaging period ( ma_period) and another for the price series (applied_price). By
the way, any indicator function that has the applied_price parameter can be passed an indicator handle
instead, meaning that you can use a price series derived from another indicator.
// Input parameters
input int RSIPeriod = 10;
input ENUM_APPLIED_PRICE RSIPrice = PRICE_CLOSE;
We declare the input variables RSIPeriod and RSIPrice with the appropriate types at the beginning of our
program. Inside the OnTick() event handler, we declare the rsi[] array and set it as a series array. We pass
our input parameters to the iRSI() function and save the indicator handle to the handle variable. Finally, we
pass the handle variable to the CopyBuffer() function and three bars worth of data is copied to the rsi[]
array.
The rsi and lastRsi variables are assigned the RSI values for the current bar and the previous bar. You could
compare these variables to determine whether the RSI is rising or falling, for example.
198
Using Indicators in Expert Advisors
Multi-Buffer Indicators
When using indicators with multiple lines (and multiple buffers), we'll need to use several arrays and
CopyBuffer() functions to get all of the data into our program. Let's start with the stochastic indicator. The
stochastic is an oscillator similar to the RSI. Along with the main stochastic line, a second signal line is also
present.
Fig. 17.3 – The stochastic indicator. The red dashed line is the signal line.
// Input parameters
input int KPeriod = 10;
input int DPeriod = 3;
input int Slowing = 3;
input ENUM_MA_METHOD StochMethod = MODE_SMA;
input ENUM_STO_PRICE StochPrice = STO_LOWHIGH;
The stochastic indicator has five input parameters and two buffers. We declare the arrays main[] and
signal[] to hold the stochastic indicator data. Both arrays are set as series arrays using the
ArraySetAsSeries() function. The iStochastic() function initializes the indicator and returns the indicator
handle to the handle variable. Here is the function definition for iStochastic():
199
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
int iStochastic(
string symbol, // symbol name
ENUM_TIMEFRAMES period, // period
int Kperiod, // K-period
int Dperiod, // D-period
int slowing, // final smoothing
ENUM_MA_METHOD ma_method, // type of smoothing
ENUM_STO_PRICE price_field // stochastic calculation method
);
Compare the input variables above to the parameters for the iStochastic() function, and note the types for
the ma_method and price_field parameters. The ENUM_STO_PRICE enumeration is used to set the calculation
method for the stochastic indicator (either low/high or close/close).
If you look at the iStochastic() entry in the MQL5 Reference, you'll see this note in the description:
Note
The buffer numbers: 0 - MAIN_LINE, 1 – SIGNAL_LINE.
These are the buffer numbers for the two stochastic indicator lines. We pass the buffer number to the
buffer_num parameter of the CopyBuffer() function. The CopyBuffer() function will copy the data from
that indicator buffer into the specified array. We'll need to do this for each buffer in the indicator:
CopyBuffer(handle,0,0,3,main);
CopyBuffer(handle,1,0,3,signal);
The buffer numbers are highlighted in bold. The contents of buffer 0 ( MAIN_LINE) are copied into the main[]
array, and the contents of buffer 1 (SIGNAL_LINE) are copied into the signal[] array. The main[] array holds
the values for the main stochastic line (also called the %K line), while the signal[] array holds the values for
the signal line (also called the %D line).
The procedure for adding any multi-buffer indicator is the same. Note the buffer numbers in the MQL5
Reference entry for the relevant indicator function. Then declare the appropriate number of arrays, set them as
series, initialize the indicator and copy each of the buffers to the corresponding array.
200
Using Indicators in Expert Advisors
All of the built-in indicators that we addressed in the previous section have a few things in common. Every
indicator uses at least one array to hold the buffer data. That array needs to be set as a series array. We need a
variable to hold the indicator handle. And we need a way to copy the buffer data and access the array. Finally,
we need to be able to remove the indicator from our program if necessary.
Our indicator base class will accomplish all of these tasks. We're going to name our base class CIndicator. It
will be placed in the \MQL5\Include\Mql5Book\Indicators.mqh include file. This file will be used to hold all
of our indicator related classes and functions.
class CIndicator
{
protected:
int handle;
double main[];
public:
CIndicator(void);
double Main(int pShift=0);
void Release();
virtual int Init() { return(handle); }
};
The CIndicator class has two protected variables and four public functions. The protected keyword means
that the variable contents are only accessible inside the CIndicator class and in derived classes. The handle
variable will contain the indicator handle, and the main[] array will hold the buffer data.
There are four public functions. The CIndicator() function is the class constructor. When an object based on
this class is created, the constructor is run automatically. Let's take a look at the CIndicator class constructor:
CIndicator::CIndicator(void)
{
ArraySetAsSeries(main,true);
}
The class constructor simply sets the main[] array as a series using the ArraySetAsSeries() function. Every
time we create an object based on the CIndicator class, the main[] array is declared and set as a series
array.
201
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
Assuming that we already have a valid indicator handle, the Main() function copies the buffer data to the
main[] array, and accesses the data for the bar specified by the pShift parameter. Before returning the value,
we use the NormalizeDouble() function to round the value to the number of significant digits in the symbol
price. We'll write a function that will create the indicator handle when we start creating derived classes.
Next, we have the Release() function. This function simply releases the indicator from memory if we no
longer need it:
void CIndicator::Release(void)
{
IndicatorRelease(handle);
}
And finally, the Init() function is a virtual function whose functionality will be defined in our derived classes.
By declaring the function as virtual, we are telling the compiler that the programmer must define the
Init() function in any derived classes. We'll discuss virtual functions later in the chapter:
Derived Classes
The CIndicator class will be the basis for derived classes that will be used to initialize and retrieve data from
MetaTrader's built-in indicators. Since the implementation of each indicator will be different, we will need to
create a separate class for every indicator that we want to use.
Let's start with the moving average indicator. The moving average has just one buffer, so we can create a
derived class with a minimum of effort. Here is the class declaration for the CiMA class:
202
Using Indicators in Expert Advisors
The CiMA class has one public function, Init(), which is used to initialize the indicator. Note the : public
CIndicator after the class name. This indicates that the CiMA class is derived from CIndicator. The CiMA
class inherits all of the public and protected functions and variables from the CIndicator class.
Remember that the Init() function is declared as a virtual function in CIndicator. This means that any
derived classes of CIndicator must implement the Init() function. If the programmer attempts to compile
the program without implementing the Init() function, an error will occur.
The Init() class simply calls the iMA() function, and saves the indicator handle to the handle variable that
was declared in the CIndicator class. Now all we need to do is access the moving average data using the
Main() function that was declared in CIndicator.
Here is how we would use the CiMA class (and its parent class, CIndicator) in an expert advisor:
// Input variables
input int MAPeriod = 10;
input ENUM_MA_METHOD MAMethod = 0;
input int MAShift = 0;
input ENUM_APPLIED_PRICE MAPrice = 0;
203
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
At the top of our expert advisor source code file, we create an object based on the CiMA class. We name this
object MA. The input variables necessary for adjusting the moving average settings are also declared near the
top of the file.
The Init() function is called from the OnInit() event handler. Remember that OnInit() runs once, when
the expert advisor is first initialized. We only need to initialize the indicator once, so placing it in OnInit() will
work just fine. You can also place it in the OnTick() event handler with no ill effects.
To retrieve the current bar's moving average value, we simply need to call the Main() function with the bar
index as an optional parameter. Since we called Main() without a parameter, it will return the value for the
current bar. To retrieve the value for the previous bar, for example, we would call MA.Main(1). The
\MQL5\Experts\Mql5Book\Simple Expert Advisor with Functions.mq5 file has been updated with the
CiMA class to access the moving average data.
Input variables aside, we can initialize an indicator in just two lines of code: the object declaration at the
beginning of the file, and the Init() function to initialize the indicator. We can then access the indicator
values using easy-to-remember functions.
Let's create a second indicator class, using an indicator with multiple buffers. We discussed the stochastic
indicator earlier in the chapter, which has two buffers. We will need to declare any additional buffer arrays in
our class declaration. The class constructor will set the additional arrays as series arrays. Finally, we'll need to
add functions to access the additional arrays. Here is the class declaration for the CiStochastic class:
public:
int Init(string pSymbol, ENUM_TIMEFRAMES pTimeframe, int pKPeriod, int pDPeriod,
int pSlowing, ENUM_MA_METHOD pMAMethod, ENUM_STO_PRICE pPrice);
double Signal(int pShift=0);
CiStochastic(void);
};
Along with the Init() function that was declared as a virtual function in the CIndicator class, the
CiStochastic class has a few additional members. A private array named signal[] will hold the indicator
values for the signal or %D line. The public Signal() function will be used to access this data. We've also
added a class constructor:
204
Using Indicators in Expert Advisors
CiStochastic::CiStochastic(void)
{
ArraySetAsSeries(signal,true);
}
Just like the constructor for the CIndicator class, the CiStochastic constructor simply sets the signal[]
array as a series array. We will need to do this for every indicator that has more than one buffer.
Here are the function declarations for the Init() and Signal() functions:
The Init() function passes the input parameters to the iStochastic() function and saves the indicator
handle to the handle variable. The Signal() function copies the data from buffer 1 of the stochastic indicator
to the signal[] array and returns the value for the specified bar. We will need to add a similar function for
every additional indicator buffer in our multi-buffer indicator classes.
We add the stochastic indicator to our expert advisor the same way we added the moving average – declare
an object, initialize the indicator, and access the indicator data:
// Input variables
input int KPeriod = 10;
input int DPeriod = 3;
input int Slowing = 3;
input ENUM_MA_METHOD StochMethod = MODE_SMA;
input ENUM_STO_PRICE StochPrice = STO_LOWHIGH;
205
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
We added the stochastic indicator to our expert advisor using only a few lines of code. We declared an object
based on the CiStochastic class named Stoch. We initialized the indicator in the OnInit() event handler
using Init(). And we have two functions, Main() and Signal(), to retrieve the indicator data. The
currentStoch and currentSignal variables contain the %K and %D line values for the current bar.
Object Initialization
To sharpen your understanding of how derived classes work, let's walk through the creation of our
CiStochastic object. First, we initialize the object:
CiStochastic Stoch;
The program initializes the protected and public members of the CIndicator class. The variables and objects
listed below will be part of the CiStochastic class, and the public members can be accessed through the
Stoch object:
class CIndicator
{
protected:
int handle;
double main[];
public:
CIndicator(void);
double Main(int pShift=0);
void Release();
};
Next, the CIndicator class constructor executes. The main[] array is set as a series array:
CIndicator::CIndicator(void)
{
ArraySetAsSeries(main,true);
}
206
Using Indicators in Expert Advisors
public:
int Init(string pSymbol, ENUM_TIMEFRAMES pTimeframe, int pKPeriod, int pDPeriod,
int pSlowing, ENUM_MA_METHOD pMAMethod, ENUM_STO_PRICE pPrice);
double Signal(int pShift=0);
CiStochastic(void);
};
Then, the class constructor for the CiStochastic class executes. The signal[] array is set as a series array:
CiStochastic::CiStochastic(void)
{
ArraySetAsSeries(signal,true);
}
The Stoch object is ready to use. The handle variable, as well as the main[] and signal[] arrays, are
protected or private members that are not accessible to the programmer. We interact with these members
using our public functions, Init(), Main(), Signal() and Release().
The \MQL5\Include\Mql5Book\Indicators.mqh file contains classes for the most popular built-in indicators
for MetaTrader 5. If you need to create a class for an indicator that is not listed, you can do so using the
techniques listed in this chapter.
Custom Indicators
You can also use custom indicators in your expert advisors. To use a custom indicator, you will need to
determine the number of buffers in the indicator, as well as their usage. You will also need to determine the
names and types of the indicator parameters.
You can download custom indicators online through the MQL5 Codebase, the MQL5 Market, or from
MetaTrader-related forums and websites. It will be much easier to work with a custom indicator if you have the
MQ5 file for it. Even if you only have the EX5, it is still possible to use the indicator in your project, though it
will take a bit more detective work.
Let's use one of the custom indicators that come standard with MetaTrader 5. The Custom Indicators tree in
the Navigator window has an Examples subtree containing custom indicator examples of many built-in
207
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
MetaTrader indicators, as well as a few extras. We'll use the BB custom indicator, which plots Bollinger Bands
on the chart.
SetIndexBuffer(0,ExtMLBuffer);
SetIndexBuffer(1,ExtTLBuffer);
SetIndexBuffer(2,ExtBLBuffer);
SetIndexBuffer(3,ExtStdDevBuffer,INDICATOR_CALCULATIONS);
The first parameter of the SetIndexBuffer() function is the buffer number. The indicator has three lines, but
the code shows four buffers! Note the INDICATOR_CALCULATIONS parameter for buffer 3. This indicates that
this buffer is used for internal calculations only. We'll need to do a bit more searching to figure out what each
buffer does.
Look just below it in the file and you'll see three PlotIndexSetString() functions. These are used to set the
labels in the data window:
PlotIndexSetString(0,PLOT_LABEL,"Bands("+string(ExtBandsPeriod)+") Middle");
PlotIndexSetString(1,PLOT_LABEL,"Bands("+string(ExtBandsPeriod)+") Upper");
PlotIndexSetString(2,PLOT_LABEL,"Bands("+string(ExtBandsPeriod)+") Lower");
From these, we can infer that buffer 0 is the middle line, buffer 1 is the upper band, and buffer 2 is the lower
band. Now that we know our buffer numbers, let's figure out the input parameters for the Bollinger Bands
indicator. We can find the input variables right near the top of the file:
208
Using Indicators in Expert Advisors
We have three input parameters – two int variables for the period and shift, and a double variable for the
deviation. Feel free to copy and paste the input variables from the custom indicator file to your expert advisor
file. You can change the names of the input variables in your program if you wish.
The iCustom() function works similarly to the built-in indicator functions examined previously in this chapter.
Here is the function definition from the MQL5 Reference:
int iCustom(
string symbol, // symbol name
ENUM_TIMEFRAMES period, // period
string name // folder/custom indicator_name
... // list of indicator input parameters
);
Just like the other technical indicator functions, the iCustom() parameters begin with symbol and period.
The name parameter is for the indicator name, minus the file extension. The indicator file must be located in
the \MQL5\Indicators folder. If the file is located in a subfolder, then the subfolder name must precede the
indicator name, separated by a double slash ( \\). For example, the BB indicator is located in the
\MQL5\Indicators\Examples folder. So the value for the name parameter would be "Examples\\BB".
(Remember that the double slash (\\) is the escape character for a backslash!)
The . . . in the iCustom() function definition is where the input parameters for the custom indicator go. The
BB custom indicator has three input parameters. We need to pass those parameters to the iCustom()
function in the order that they appear in the indicator file:
We would need to pass three parameters of type int, int and double to the iCustom() function. Here's an
example of how we would use the iCustom() function to add the BB indicator to an expert advisor:
// Input variables
input int BandsPeriod = 20; // Period
input int BandsShift = 0; // Shift
input double BandsDeviation = 2; // Deviation
209
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
CopyBuffer(bbHandle,0,0,3,middle);
CopyBuffer(bbHandle,1,0,3,upper);
CopyBuffer(bbHandle,2,0,3,lower);
The input variables are presented in the order that they appear in the BB indicator file. We've renamed them
to BandsPeriod, BandsShift, and BandsDeviation. Next, we declare the three arrays that we will use to hold
the indicator data (upper[], lower[] and middle[]), and set them as series arrays. The iCustom() function
initializes the BB indicator and returns our indicator handle, saving it in the bbHandle variable. Note that we
use "Examples\\BB" for the name parameter in the iCustom() function, since the BB indicator is located in
the \Indicators\Examples subfolder.
Note the BandsPeriod, BandsShift, and BandsDeviation parameters in the iCustom() function call. You
must pass all of the input parameters of the custom indicator in the order that they appear to the iCustom()
function. The parameters must also be of the appropriate data type – the majority of indicators use either int,
double or string types. If you do not wish to add an input variable for a particular custom indicator
parameter, then simply pass a default value. For example, if you don't plan on ever changing the BandsShift
parameter for the BB indicator, then simply substitute 0 for BandsShift in the iCustom() function call.
Finally, we use the CopyBuffer() function to copy the data from the indicator buffers to our arrays. If you do
not plan on using an indicator line, then you do not need to copy data for it. For example, if you don't plan on
using the middle line of the BB indicator, then simply omit the middle[] array and the ArraySetAsSeries()
and CopyBuffer() functions that reference it.
You can add any custom indicator to your expert advisor using the procedure above. First, note the buffer
numbers for the corresponding lines/plots in the custom indicator that you wish to use. Next, note the input
parameter names and their types. Add the input parameters to your expert advisor, renaming or omitting
them as you wish. Pass the input parameters (or appropriate default values) to the iCustom() function along
with the indicator name. Finally, use the CopyBuffer() function to copy the buffer data to the series array(s)
you've prepared.
210
Using Indicators in Expert Advisors
For the built-in indicators, we have created classes that make adding them to your expert advisors very easy.
Unfortunately, the implementation of custom indicators in MQL5 make is difficult to do the same for custom
indicators. As you've seen above, the iCustom() function allows for a flexible number of input parameters. We
cannot do the same when creating classes in MQL5.
There is a second method of adding indicators to an MQL5 program, the IndicatorCreate() function, that
uses a fixed number of parameters and an object of the MqlParam structure type to pass the input parameters.
But using this method introduces a greater amount of complexity with no real gain in simplicity or
functionality. Therefore, we will not be covering this method in this book.
In summary, if you wish to add custom indicators to your expert advisor, you will need to use the iCustom()
function to return an indicator handle, and then use the CopyBuffer() function to copy the buffer data to
your series arrays as described in the previous section.
The section on Price-Based Trading Signals in the previous chapter addressed the first category. A trading
signal can be created when the price is above or below an indicator line. In this chapter, we discussed the
Bollinger Bands indicator. We can use this indicator as an example of a price/indicator relationship.
The Bollinger Bands can be used to open trades at price extremes – for example, when the price moves
outside the bands. We can express these trading conditions like so:
211
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
We use the Close() function of our CBars class created in the previous chapter to represent the current close
price. The bbUpper and bbLower variables represent the upper and lower Bollinger bands respectively. If the
current close price is above the upper band and no buy position is currently open, then we would open a buy
position. The same is true for a sell if the close is below the lower band.
You can use two or more indicator lines to create a trading signal. For example, the moving average cross is
one of the most basic trading strategies around. It uses two moving averages, one fast and one slow. When
the fast moving average is above the slow moving average a buy signal is implied, and vice versa for sell.
For example, lets use a 10 period exponential moving average for the fast MA, and a 30 period simple moving
average for the slow MA:
When the 10 EMA (the fastMA variable) is greater than the 30 SMA (the slowMA variable), a buy signal occurs.
When the opposite is true, a sell signal occurs.
You can also use indicators of different types, provided they are all drawn in the same window. For example,
you may wish to use a moving average with the Bollinger bands in the earlier example instead of the price:
In this example, we check if the moving average value of the previous bar ( ma[1]) is greater than the upper
bollinger band (bbUpper). If so, a buy signal occurs.
You can also compare two lines of the same indicator, such as the main and signal lines of the stochastic
indicator:
212
Using Indicators in Expert Advisors
The stoch[] array represents the main or %K line of the indicator, while the signal[] array is the %D line. If
the %K line is greater than the %D line, this indicates a bullish condition.
For trending indicators, you may wish to base trading decisions upon the direction in which the indicator is
trending. You can do this by checking the indicator value of a recent bar against the value of a previous bar.
For example, if we want a buy signal to occur when a moving average is sloping upward, we can compare the
value of the most recently closed bar to the value of the previous bar:
This example checks to see if the moving average value of the most recently closed bar ( ma[1]) is greater than
the value of the previous bar (ma[2]). If so, the moving average is trending upward, and a buy signal occurs.
You can do this with any indicator that trends up or down with the price, including oscillators such as RSI and
MACD.
Many indicators, namely oscillators, appear in a separate chart window. They are not plotted according to
price, but rather respond to changes in price. The RSI, for example, has a minimum value of 0 and a maximum
value of 100. When the RSI value is below 30, that indicates an oversold condition, and when it is above 70,
that indicates an overbought condition.
We can check for these overbought and oversold conditions simply by comparing the current RSI value to a
fixed value. The example below checks for an oversold condition as part of a buy signal:
If the RSI value of the most recently closed bar is less than or equal to the oversold level of 30, that indicates a
buy signal.
213
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
Histogram indicators such as the MACD oscillate around a zero line. Ascending values above zero indicate a
bullish trend, while descending values below zero indicate bearish price movement. You may wish to base a
trading signal around the indicator value relative to zero:
If the MACD value of the most recently closed bar is greater than zero, then a buy condition is indicated.
You may come up with more elaborate indicator conditions than these. But nearly all indicator-based trading
conditions are a combination of relationships between indicator readings and/or prices. To combine multiple
price and indicator conditions for a trading signal, simply use boolean AND or OR operations between the
expressions:
In the expression above, if the close price is less than the lower Bollinger band and either the RSI is less than
30, or the stochastic is less than its signal line, we open a buy position. When combining AND and OR
operations, use parentheses to establish which expressions will be evaluated first.
In an expert advisor that has multiple indicators, you may wish to turn individual indicators on and off. To do
so, we need to add a boolean input variable that will allow the user to deactivate an indicator if necessary. It
will also be necessary to modify the trading condition to allow for both on and off states.
214
Using Indicators in Expert Advisors
For example, let's create an expert advisor that uses the Bollinger Bands and an RSI. If the price is below the
lower band and the RSI is in oversold territory (below 30), then we will open a buy position. We will add an
input variable to turn the RSI condition on and off:
// Input variables
input bool UseRSI = true;
In the example above, we add an input variable named UseRSI. This turn the RSI trade condition on and off.
Inside the if operator, we check for both true and false states. If UseRSI == true, we check to see if the rsi
value is less than 30. If so, and if the other conditions are true, we open a buy position. If UseRSI == false,
and the other conditions are true, we also open a buy position.
The parentheses establish the order in which the conditions are evaluated. The inner set of parentheses
contain the operation (rsi <= 30 && UseRSI == true). This is evaluated first. If this condition is false, we
then evaluate the condition inside the outer set of parentheses, UseRSI == false. Note the OR operator (||)
separating both operations. If one of these conditions evaluates as true, then the entire expression inside the
parentheses is true.
Remember that the indicator "on" state is inside the innermost set of parentheses, and is evaluated first. The
indicator "off" state is inside the outermost set of parentheses, and is separated from the "on" condition by an
OR operator (||). Combining AND and OR operations using parentheses to establish order of evaluation
allows you to create complex trading conditions. When doing so, be sure to watch your opening and closing
parentheses to make sure that you do not leave one out, add one too many, or nest them incorrectly.
215
Working with Time and Date
When trading on the open of a new bar, we will use the closing price and indicator values of the previous bar
when making trade decisions. If you're looking at a chart of historical trades, keep in mind that the trade
criteria is based on the previous bar, and not the bar that the trade opened on!
Note that the Strategy Tester has an Open prices only execution mode that mimics this behavior. If your
strategy only opens and closes orders on the open of a new bar, then you can use Open prices only to perform
quick and accurate testing of your expert advisor.
Let's create a class that will keep track of the timestamp of the current bar and allow us to determine when a
new bar has opened. We'll create this class in a new include file named
\MQL5\Include\Mql5Book\Timer.mqh. All of the classes and functions in this chapter will go in this file.
class CNewBar
{
private:
datetime Time[], LastTime;
public:
void CNewBar();
bool CheckNewBar(string pSymbol, ENUM_TIMEFRAMES pTimeframe);
};
217
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
We have two private variables in our class. The Time[] array stores the timestamp for the current bar, and the
LastTime variable contains the timestamp for the most recently checked bar. Note that we declared both
variables on the same line, since they share the same type. We will compare these two values to determine if a
new bar has opened. Our class also contains a constructor and the CheckNewBar() function. The class
constructor simply sets the Time[] array as a series array:
void CNewBar::CNewBar(void)
{
ArraySetAsSeries(Time,true);
}
The CheckNewBar() function is used to check for the open of a new bar. Here is the function declaration:
return(newBar);
}
The function takes two parameters, the symbol and the period of the chart we are using. We initialize two
boolean variables, firstRun and newBar, and explicitly set them to false. The CopyTime() function updates
the Time[] array with the timestamp of the most recent bar.
We check the LastTime class variable to see if it has a value assigned to it. The first time this function runs,
LastTime will be equal to zero, and we will set the firstRun variable to true. This indicates that we have just
attached the expert advisor to the chart. Since we do not want the expert advisor to trade intrabar, this is a
necessary check to ensure that we don't attempt to open an order right away.
Next, we check to see if Time[0] > LastTime. If this is the first time we've run this function, then this
expression will be true. This expression will also evaluate to true every time a new bar opens. If the timestamp
of the current bar has changed, we check to see if firstRun is set to false. If so, we set newBar to true. We
218
Working with Time and Date
update the LastTime variable with the current bar's timestamp, and return the value of newBar to the
program.
The execution of CheckNewBar() works like this: When the expert advisor is first attached to a chart, the
function will return a value of false because firstRun will be set to true. On each subsequent check of the
function, firstRun will always be false. When the current bar's timestamp (Time[0]) is greater than
LastTime, we set newBar to true, update the value of LastTime, and return a value of true to the program.
This indicates that a new bar has opened, and we can check our trading conditions.
// Input variables
input bool TradeOnNewBar = true;
if(TradeOnNewBar == true)
{
newBar = NewBar.CheckNewBar(_Symbol,_Period);
barShift = 1;
}
if(newBar == true)
{
// Order placement code
}
We declare an object based on the CNewBar class named NewBar. The TradeOnNewBar input variable allows us
to select between trading on a new bar, or trading on every tick. In the OnTick() event handler, we declare a
local bool variable named newBar and initialize it to true, and an int variable named barShift.
If TradeOnNewBar is set to true, we call our CheckNewBar() function and save the return value to the newBar
variable. Most of the time, this value will be false. We will also set the value of barShift to 1. If
TradeOnNewBar is set to false, then newBar will be true, and barShift will be 0.
If newBar is true, we go ahead and check our order placement conditions. Any code that you want to run only
at the open of a new bar will go inside these brackets. This includes all order opening conditions and possibly
your closing conditions as well.
219
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
When using the CNewBar class to check for the open of a new bar, the barShift variable will be used to set
the bar index of any price or indicator values. For example, we'll use the moving average and close price
functions that we've defined in previous chapters:
The close and ma variables will be assigned the value for the current bar (barShift = 0) if TradeOnNewBar is
set to false, or the value of the previous bar (barShift = 1) if TradeOnNewBar is set to true.
For example, let's say we have two datetime variables, date1 and date2. We know that date1 is equal to July
12, 2012 at 3:00, and date2 is equal to July 14, 2012 at 22:00. If we wanted to know which date is earlier, we
can compare the two :
The correct result is "date1 is sooner". Another advantage of working with datetime values is that if you
want to add or subtract hours, days or any period of time, you can simply add or subtract the appropriate
number of seconds to or from the datetime value. For example, we'll add 24 hours to the date1 variable
above:
220
Working with Time and Date
There are 86,400 seconds in a 24 hour period. By adding 86400 to a datetime value, we have advanced the
date by exactly 24 hours. The date1 variable now has a value equal to July 13, 2012 at 3:00.
A datetime value is not human-readable, although the MetaTrader 5 terminal will automatically convert
datetime values to a readable string constant when printing them to the log. If you need to convert a
datetime value to a string for other purposes, the TimeToString() function will convert a datetime variable
to a string in the format yyyy.mm.dd hh:mm. In this example, we will convert date2 using the TimeToString()
function:
The TimeToString() function converts the date2 variable to a string, and saves the result to the convert
variable. If we print out the value of convert, it will be 2012.07.14 22:00.
We can create a datetime value by constructing a string in the format yyyy.mm.dd hh:mm:ss, and converting it
to a datetime variable using the StringToTime() function. For example if we want to convert the string
2012.07.14 22:00 to a datetime value, we pass the string to the StringToTime() function:
The dtDate variable is assigned a datetime value equivalent to 2012.07.14 22:00. We can also create a
datetime constant by enclosing the string in single quotes and prefacing it with a capital D. This datetime
constant can be assigned directly to a datetime variable:
This method allows for more flexibility when constructing the datetime constant string. For example, you
could use dd.mm.yyyy as your date format and leave off the time. The datetime constant topic in the MQL5
Reference under Language Basics > Data Types > Integer Types > Datetime Type has more information on
formatting a datetime constant.
Finally, we can use the MqlDateTime structure. This structure allows us to retrieve specific information from a
datetime variable, such as the hour or the day of the week.
221
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
struct MqlDateTime
{
int year; // Year
int mon; // Month
int day; // Day
int hour; // Hour
int min; // Minutes
int sec; // Seconds
int day_of_week; // Day of week (0-Sunday, 1-Monday, ... 6-Saturday)
int day_of_year; // Day number of the year (January 1st = zero)
};
Using the MqlDateTime structure, we can retrieve any date or time element from a datetime value. We can
retrieve the hour, minute or day and assign that value to an integer variable. We can even retrieve the day of
the week or the day of the year. We can also assign values to these variables to construct a datetime value.
The TimeToStruct() function is used to convert a datetime value to an object of the MqlDateTime type. The
first parameter of the function is the datetime value to convert. The second parameter is the MqlDateTime
object to load the values into. Here's an example:
MqlDateTime timeStruct;
TimeToStruct(dtTime,timeStruct);
First, we construct a datetime variable, dtTime, using a datetime constant. Next, we declare an object of type
MqlDateTime named timeStruct. We use the TimeToStruct() function to convert the datetime value in
dtTime to the MqlDateTime structure. Finally, we show how to retrieve the day, hour and day of week from the
timeStruct object. The day is 15, the hour is 16, and the dayOfWeek is 1 for Monday.
We can also create an MqlDateTime object, assign values to its member variables, and construct a datetime
value using the StructToTime() function. The day_of_week and day_of_year variables are not used when
222
Working with Time and Date
creating a datetime value using an MqlDateTime object. Here's an example of how to create a datetime
value using the StructToTime() function:
MqlDateTime timeStruct;
timeStruct.year = 2012;
timeStruct.mon = 10;
timeStruct.day = 20;
timeStruct.hour = 2;
timeStruct.min = 30;
timeStruct.sec = 0;
Be sure to explicitly assign values to all of the variables of the MqlDateTime object, or else you may not get
the results you expect! The StructToTime() function converts the timeStruct object to a datetime value,
and stores the result in the dtTime variable. If we print out the dtTime variable, we can see the values that we
assigned to the timeStruct object:
What happens if you don't assign a value to an MqlDateTime object variable, or if you assign a value that is
invalid?
MqlDateTime timeStruct;
timeStruct.hour = 0;
timeStruct.min = 0;
timeStruct.sec = 0;
223
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
dtTime = StructToTime(timeStruct);
The year must be specified, or else the StructToTime() function returns nothing. If the month or day are less
than 1, they default to a value of 1. If the month is larger than the maximum value of 12, it defaults to
December (12). If the day is larger than the number of days in the month, it either defaults to 31 if there are 31
days in the month, or else the excess value overflows into the following month. Needless to say, you should
not overflow the day variable, or else your date may end up several days off. Invalid hour or minute values will
default to their minimum and maximum of 0 and 59 respectively.
Let's create a function that will create a datetime value for the current date. We'll specify the hour and
minute, and the function will return a value for that time for the current date. An MqlDateTime object will be
used to create our datetime value.
All of the code in this chapter will go in the \MQL5\Include\Mql5Book\Timer.mqh include file. Here is the
code for the CreateDateTime() function:
timeStruct.hour = pHour;
timeStruct.min = pMinute;
return(useTime);
}
224
Working with Time and Date
First, we create an MqlDateTime object named timeStruct. We use the TimeToStruct() function to fill the
member variables with the current date and time using the TimeCurrent() function. Next, we assign the
pHour and pMinute parameter values to the hour and min variables of the timeStruct object. Finally, we use
StructToTime() to construct our new datetime value and assign the result to the useTime variable. The
value of useTime is returned to the program.
Once we have a datetime value for a specified time, we can easily manipulate this value if necessary. For
example, we want our trade timer to start at 20:00 each day. We will stop trading for the day at 8:00 the
following day. First, we will use our CreateDateTime() function to create datetime values for the start and
end time:
The problem here is that our end time is earlier than our start time. Assuming that today is October 22, and
we want our timer to start at 20:00, we'll need to advance the end time by 24 hours to get the correct time.
You'll recall from earlier in the chapter that 24 hours is equal to 86400 seconds. We can simply add this value
to endTime to get the correct time.
Instead of having to remember that 86400 seconds equals a day, let's define some constants that we can
easily refer to if we need to add or subtract time from a datetime variable. These will go at the top of our
Timer.mqh include file:
#define TIME_ADD_MINUTE 60
#define TIME_ADD_HOUR 3600
#define TIME_ADD_DAY 86400
#define TIME_ADD_WEEK 604800
Using these constants, we can easily add or subtract the specified time period. Let's add 24 hours to our
endTime variable:
endTime += TIME_ADD_DAY;
225
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
Now our start and end times are correct relative to each other.
Let's examine the simplest type of trade timer. This timer will take two datetime values and determine
whether the current time falls between those values. If so, our trade timer is active and trading can commence.
If the current time is outside those values, then our trade timer is inactive, and trades will not be placed.
The CTimer class will hold our timer-related functions. We will start with one function – the CheckTimer()
function, which will check two datetime variables to see if trading is allowed. We'll add other class members
later:
class CTimer
{
public:
bool CheckTimer(datetime pStartTime, datetime pEndTime, bool pLocalTime = false);
};
datetime currentTime;
if(pLocalTime == true) currentTime = TimeLocal();
else currentTime = TimeCurrent();
return(timerOn);
}
The pStartTime parameter holds the trading start time, while the pEndTime parameter holds the trading end
time. The pLocalTime parameter is a boolean value that determines whether we use local or server time.
226
Working with Time and Date
First, we check to see if pStartTime is greater than or equal to pEndTime. If so, we know that our start and
end times are invalid, so we show an error message and exit with a value of false. Otherwise, we continue
checking our timer condition.
We declare a datetime variable named currentTime that will hold the current time. If pLocalTime is true,
we will use the local computer time. If it is false, we use the server time. The TimeLocal() and
TimeCurrent() functions retrieve the current time from the local computer or the server, respectively.
Normally, we will use the server time, but it is a simple addition to allow the user to use local time, so we have
added the option.
Once the currentTime variable has been assigned the current time, we compare it to our trading start and
end times. If currentTime >= pStartTime and currentTime < pEndTime, then the trade timer is active. We
will set the timerOn variable to true and return that value to the program.
Let's demonstrate how to create a simple trade timer using CheckTimer(). We'll be using two datetime input
variables. The MetaTrader 5 interface makes it easy to input a valid datetime value. We'll set a start and end
time, and use the CheckTimer() function to check for a valid trade condition:
#include <Mql5Book/Timer.mqh>
CTimer Timer;
if(timerOn == true)
{
// Order placement code
}
First, we include the Timer.mqh file and declare the Timer object, based on our CTimer class. The input
variables StartTime and EndTime have a valid start and end time entered. In the OnTick() event handler, we
call the CheckTimer() function and pass our StartTime and EndTime input variables to it. (We've passed a
value of false for the pLocalTime parameter, meaning that we'll use server time to determine the current
time).
If the current time is between the start and end times, the boolean timerOn variable is set to true. When we
check our order opening conditions, we check if timerOn is true. If so, we proceed with checking the order
conditions.
227
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
The problems with using datetime input variables to set our start and end times is that they have to be
constantly updated. What if you just wanted to trade the same hours each day, for example?
Most savvy Forex traders know that certain hours of the day are more conducive to trading. The open of the
London session through the end of the New York session is the most active trading period of the day. We can
improve the performance of our expert advisors by limiting our trading to these hours.
We'll create a timer that will allow us to trade the same hours each day. We set a start and end hour and
minute, and unless we decide to change our trading hours we need not edit it again. Our daily timer operates
by the same principles as our simple timer in the last section. We create two datetime values using our
CreateDateTime() function. We compare our start and end times, and increment or decrement them by one
day as necessary. If the current time falls between the start and end times, trading is enabled.
Let's add our daily timer function to the CTimer class. We're also going to add two private datetime variables
– StartTime and EndTime – to the CTimer class. These will be used to save our current start and end times,
just in case we need to retrieve them elsewhere in our program:
class CTimer
{
private:
datetime StartTime, EndTime;
public:
bool CheckTimer(datetime pStartTime, datetime pEndTime, bool pLocalTime = false);
bool DailyTimer(int pStartHour, int pStartMinute, int pEndHour, int pEndMinute,
bool pLocalTime = false);
};
StartTime = CreateDateTime(pStartHour,pStartMinute);
EndTime = CreateDateTime(pEndHour,pEndMinute);
228
Working with Time and Date
return(timerOn);
}
For this function, we input the start and end hour and minute using the int variables pStartHour,
pStartMinute, pEndHour and pEndMinute. We also include the pLocalTime parameter for selecting between
server and local time. After assigning the current time to the currentTime variable, we call the
CreateDateTime() function and pass the pStartHour, pStartMinute, pEndHour and pEndMinute variables.
The resulting datetime values are stored in StartTime and EndTime respectively.
Next, we determine whether we need to increment or decrement the start and/or end time values. First, we
check to see if the EndTime value is less than the StartTime value. If so, we subtract one day from the start
time (using the TIME_ADD_DAY constant) so that the value of StartTime is sooner than the value of EndTime.
If the value of currentTime is greater than EndTime, the timer has already expired and we need to set it for
the next day. We increment both StartTime and EndTime by one day, so that the timer is set for the following
day. Finally, we pass the StartTime and EndTime values to the CheckTimer() function, along with our
pLocalTime parameter. The result is saved to the timerOn variable, which is returned to the program.
Let's demonstrate how we can use the daily timer in our expert advisor program:
#include <Mql5Book/Timer.mqh>
CTimer Timer;
229
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
if(timerOn == true)
{
// Order placement code
}
We've added an input variable named UseTimer to turn the timer on and off. We have inputs for start and end
hour and minute, as well as for local/server time.
In the OnTick() event handler, we declare a boolean variable named timerOn (not to be confused with the
local variable of the same name in our DailyTimer() function!) The value of this variable determines whether
we can trade or not. We will initialize it to true. If the UseTimer input variable is set to true, we call the
DailyTimer() function. The result of the function is saved to the timerOn variable. If timerOn is true, trading
will commence, and if it is false, we will wait until the start of the next trading day.
One issue from a user perspective is knowing whether or not trading is enabled by the timer. Let's create a
short function that writes a comment to the chart as well as to the log, to inform the user when the timer has
started and stopped. The function is named PrintTimerMessage(), and we will make it a private member of
the CTimer class. We will also declare a private variable named TimerStarted that will hold our on/off state:
class CTimer
{
private:
datetime StartTime, EndTime;
bool TimerStarted;
void PrintTimerMessage(bool pTimerOn);
public:
bool CheckTimer(datetime pStartTime, datetime pEndTime, bool pLocalTime = false);
bool DailyTimer(int pStartHour, int pStartMinute, int pEndHour, int pEndMinute,
bool pLocalTime = false);
};
230
Working with Time and Date
The function type is void, since we do not need this function to return a value. The pTimerOn parameter takes
the value of our timerOn variable from the DailyTimer() function. If pTimerOn is true, we check to see if the
timer is active and print a message to the log and to the screen.
return(timerOn);
The result is that we will have one message printed to the log when the timer activates, and another when the
timer deactivates. The message will also be printed to the comment area of the chart, and will remain there
until it is overwritten by another comment.
231
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
The trade timers we've created so far take a single start and end time. The daily timer is sufficient for most
traders, but if you need more flexibility in setting your trade timer, we've created the BlockTimer() function.
We call it the "block timer" because the user will set several blocks of time that the expert advisor will be
allowed to trade each week. This allows the user to avoid volatile market events such as the non-farm payroll
report.
We will specify the days to start and stop trading by using the ENUM_DAY_OF_WEEK enumeration. The user will
select the day of the week and specify a start and end hour and minute in the expert advisor inputs. For each
block of time, we need a start day, start hour, start minute, end day, end hour and end minute. We'll also need
a boolean variable to indicate whether to use that timer block.
A fixed number of timer blocks will need to be specified in the inputs, according to the trader's needs. Five
blocks should be enough for most traders. Due to the number of variables required for each timer block, we
will create a structure that will hold all of them. We will create an array based on this structure that will hold all
of the variables for each timer block. This array will be passed to the BlockTimer() function, which will
determine whether trading should be enabled.
Here is the structure that we will define in the Timer.mqh include file. This will be declared on the global
scope, at the top of the file:
struct TimerBlock
{
bool enabled; // Enable or disable timer block
int start_day; // Start day (1: Monday... 5: Friday)
int start_hour; // Start hour
int start_min; // Start minute
int end_day; // End day
int end_hour; // End hour
int end_min; // End minute
};
Let's demonstrate how we will use this structure to fill an array object with timer information. First, we will
declare the input variables in our expert advisor. We will add two timer blocks, each with the necessary inputs:
232
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
block[1].enabled = UseTimer2;
block[1].start_day = StartDay2;
block[1].start_hour = StartHour2;
block[1].start_min = StartMinute2;
block[1].end_day = EndDay2;
block[1].end_hour = EndHour2;
block[1].end_min = EndMinute2;
All of the input variables from the first timer block are copied into the member variables of the block[] array
at element 0. The same is done for the second timer block at element 1. Now that the input values are copied
into our block[] array, let's pass them to our timer function. This is the easy part, and it works just like the
other timer functions in this chapter:
Inside the OnTick() event handler, we check the timer state before we check our order conditions. If the
current time falls inside one of our timer blocks, the value of timerOn will be set to true. We can then check
the order opening conditions and perform any actions that occur when our timer is active.
234
Working with Time and Date
TimeToStruct(StartTime,today);
int dayShift = pBlock[i].start_day - today.day_of_week;
if(dayShift != 0) StartTime += TIME_ADD_DAY * dayShift;
TimeToStruct(EndTime,today);
dayShift = pBlock[i].end_day - today.day_of_week;
if(dayShift != 0) EndTime += TIME_ADD_DAY * dayShift;
timerOn = CheckTimer(StartTime,EndTime,pLocalTime);
if(timerOn == true) break;
}
PrintTimerMessage(timerOn);
return(timerOn);
}
The BlockTimer() function has two parameters: The pBlock[] parameter takes an array object of the
TimerBlock structure type passed by reference, and the pLocalTime parameter is a boolean variable that
determines whether to use server or local time.
First, we declare the variables that will be used in our function. An MqlDateTime object named today will be
used to retrieve the day of the week for the current date. The timerCount variable will hold the size of the
pBlock[] array, which we retrieve by using the ArraySize() function. A for loop will be used to process the
timer blocks. The number of timer blocks is determined by the timerCount variable. We will calculate the start
and end times for each timer block, and determine whether the current time falls within those times. If so, we
break out of the loop and return a value of true to the calling program.
Inside the for loop, the first thing we check is the enabled variable of the current timer block. If it is true, we
continue calculating the start and end time. We use the CreateDateTime() function to calculate the
StartTime and EndTime variables using the start_hour, start_min, end_hour and end_min variables of the
pBlock[] object.
Next, we calculate the correct date based on the day of the week indicated for the start and end time. We use
TimeToStruct() to convert the StartTime and EndTime values to an MqlDateTime structure. The result is
stored in the today object. The today.day_of_week variable will hold the current day of the week. If the
current day of the week differs from the start_day or end_day value of the pBlock[] object, we calculate the
235
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
difference and store it to the dayShift variable. The dayShift variable is multiplied by the TIME_ADD_DAY
constant, and added or subtracted from the StartTime or EndTime variables. The result is that StartTime
and EndTime are set to the correct time and date relative to the current week.
After the start and end times for the current timer block have been determined, we use the CheckTimer()
function to see if the current time falls within the start time and end time of the current timer block. If the
function returns true, we break out of the for loop. The PrintTimerMessage() function will print an
informative message to the chart and the log, and the value of timerOn is returned to the program.
Programming a flexible trade timer is the most complex thing we've had to do so far. With the timer functions
we've defined in this chapter, you should be able to implement a trade timer that is flexible enough for your
needs.
A final bit of code to add: If for some reason you need to check the current start or end time that the trade
timer is using, use the GetStartTime() and GetEndTime() functions. These functions retrieve the appropriate
datetime variable and return it to the program. Since these are very short functions, we can declare the entire
function in the class declaration:
class CTimer
{
private:
datetime StartTime, EndTime;
public:
datetime GetStartTime() {return(StartTime);};
datetime GetEndTime() {return(EndTime);};
};
For clarity, we've removed the other functions and variables from the CTimer class for this example. Note the
return statement inside the brackets after the function name. This is the function body. The sole purpose of
GetStartTime() and GetEndTime() is to return the value of the StartTime or EndTime variable to the
program. If you have a short function whose only purpose is to return a value, then you can place the body
right in the class declaration.
236
Working with Time and Date
The OnTick() event handler executes only when a change in price is received from the terminal. The
OnTimer() event, on the other hand, can execute every X number of seconds. If you want your trading
strategy to perform an action at regular intervals, place that code in the OnTimer() event handler.
To use the OnTimer() event handler, we need to set the timer interval using the EventSetTimer() function.
This function is typically declared inside the OnInit() event handler or in a class constructor. For example, if
we want the expert advisor to perform an action once every 60 seconds, here is how we set the event timer:
int OnInit()
{
EventSetTimer(60);
}
This will execute the OnTimer() event handler every 60 seconds, if one exists in your program. The MQL5
Wizard can be used to add the OnTimer() event to your source file when creating it, but you can always add it
later. The OnTimer() event handler is of type void with no parameters:
void OnTimer()
{
If for some reason you need to stop your event timer, the EventKillTimer() will remove the event timer, and
the OnTimer() event handler will no longer run. This is typically declared in the OnDeinit() event handler or
a class destructor, although it should work anywhere in the program:
EventKillTimer();
The EventKillTimer() function takes no parameters, and does not return a value.
237
Putting It All Together
Creating A Template
MetaEditor 5 doesn't allow the use of custom templates like MetaEditor 4 does. So to save time, we will need
to create our own template, which we will use when creating our expert advisors. When we create a new
expert advisor, we'll simply open this file and save it under a new name.
The template will we'll be using is named MQL Book Template.mq5. To prevent this file from being
overwritten, navigate to the \MQL5\Experts\Mql5Book folder in Windows Explorer and locate the MQL Book
Template.mq5 file. Right-click on the file and select Properties. Put a check mark in the Read-only attribute.
When attempting to save a read-only file, MetaEditor will alert you and prompt you to save it to a new file.
Here is our template file. We'll go through it a section at a time. First is our #include directives and object
declarations:
// Trade
#include <Mql5Book\Trade.mqh>
CTrade Trade;
// Price
#include <Mql5Book\Price.mqh>
CBars Price;
// Money management
#include <Mql5Book\MoneyManagement.mqh>
// Trailing stops
#include <Mql5Book\TrailingStops.mqh>
CTrailing Trail;
// Timer
#include <Mql5Book\Timer.mqh>
CTimer Timer;
CNewBar NewBar;
239
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
// Indicators
#include <Mql5Book\Indicators.mqh>
We include six files from the \MQL5\Include\Mql5Book directory. We've also declared several objects for the
classes in those files. The Trade object contains our order placement functions. The Price object is for price
data functions. Trail contains the trailing stop functions, and Timer and NewBar contain the trade timer and
new bar functions. If you need to add indicators to your expert advisor, you will create indicator objects here,
ideally under the #include directive for the Indicators.mqh file.
//+------------------------------------------------------------------+
//| Expert information |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Input variables |
//+------------------------------------------------------------------+
240
Putting It All Together
The #property directives describe our program, and will appear in the Common tab in the expert advisor
Properties dialog. The input section includes settings for our most commonly-used features, including money
management, trailing and break even stop, and trade timer. The optional features all have boolean input
variables to switch the feature on an off. Note the sinput variables – these divide our input variables into
clearly defined sections in the Inputs window.
Next, we have our global variables and the OnInit() event handler:
//+------------------------------------------------------------------+
//| Global variables |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
Trade.Deviation(Slippage);
return(0);
}
The glBuyPlaced and glSellPlaced variables keep track of whether a position was previously opened in the
specified direction. In most trading systems we only want to open one position on a new trade signal, and not
open another one until a trade signal appears in the opposite direction, or a condition occurs where we set
either of these variables to false. The OnInit() event handler contains code that will run once when the
program is first started. Above, we set the trade slippage.
241
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Check for new bar
bool newBar = true;
int barShift = 0;
if(TradeOnNewBar == true)
{
newBar = NewBar.CheckNewBar(_Symbol,_Period);
barShift = 1;
}
// Timer
bool timerOn = true;
if(UseTimer == true)
{
timerOn = Timer.DailyTimer(StartHour,StartMinute,EndHour,EndMinute,UseLocalTime);
}
// Update prices
Price.Update(_Symbol,_Period);
The first section of code checks for the opening of a new bar. We declare a boolean variable named newBar
and initialize it to true. The barShift variable is initialized to zero. If the TradeOnNewBar input variable is set
to true, we call the CheckNewBar() function and save the result to newBar, and set the barShift variable to
1. Otherwise, newBar remains set to true, barShift remains zero, and intrabar trading is enabled.
After that is the timer code. We define a boolean variable named timerOn and initialize it to true. If the
UseTimer input variable is set to true, we check the DailyTimer() function, and assign the return value to
timerOn. Otherwise, the value of timerOn remains true. If we are using bar data in our expert advisor, the
Price.Update() function updates the Bar object with price and time data.
Next is our order placement code with our money management feature:
242
Putting It All Together
// Order placement
if(newBar == true && timerOn == true)
{
// Money management
double tradeSize;
if(UseMoneyManagement == true)
tradeSize = MoneyManagement(_Symbol,FixedVolume,RiskPercent,StopLoss);
else tradeSize = VerifyVolume(_Symbol,FixedVolume);
if(glBuyPlaced == true)
{
do Sleep(100); while(PositionSelect(_Symbol) == false);
double openPrice = PositionOpenPrice(_Symbol);
We check the value of the newBar and timerOn variables to determine whether to check the trading
conditions. If both are true, we proceed. The money management code is next. If the UseMoneyManagement
input variable is true, we calculate the trade volume and save it to the tradeSize variable. Otherwise, we use
the VerifyVolume() function to verify the FixedVolume setting and save the result to the tradeSize
variable.
The buy order conditions are next. We've inserted two trade conditions, the position type and the value of
glBuyPlaced. The PositionType() function will return the current position type, or -1 if there is no position
open. Since we don't want to open an order if a position is already open, we only open an order if no buy
position is open. The glBuyPlaced variable determines whether a buy position was previously open for this
trade signal. We'll need to add more conditions to this if operator, but this is the minimum for now.
243
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
If the trade conditions are true, the Buy() function of the Trade object will place our order and set the
glBuyPlaced variable to true if the order was placed successfully. If glBuyPlaced is true, then we proceed to
modify the position.
After pausing execution with the Sleep() function and checking for an open position using the
PositionSelect() function, we retrieve the position opening price and store that value in the openPrice
variable. Next, we call the BuyStopLoss() function and save the value to the buyStop variable. The
AdjustBelowStopLevel() function verifies the buyStop price and adjusts it if necessary. The same is done for
the buyProfit variable. If buyStop or buyProfit is greater than zero, we call the ModifyPosition() function
to add the stop loss and take profit to the buy position. Finally, the glSellPlaced variable is set to false.
The sell order placement code is very similar. Here is is for completeness:
if(glSellPlaced == true)
{
do Sleep(100); while(PositionSelect(_Symbol) == false);
double openPrice = PositionOpenPrice(_Symbol);
After the sell order placement code, the final closing bracket finishes our order placement block. Remember
that anything inside these brackets is only run if a new bar has just opened, the timer is active, or both
features are disabled.
Finally, we have the break even and trailing stop code, and the end of the OnTick() event handler:
244
Putting It All Together
// Break even
if(UseBreakEven == true && PositionType(_Symbol) != -1)
{
Trail.BreakEven(_Symbol,BreakEvenProfit,LockProfit);
}
// Trailing stop
if(UseTrailingStop == true && PositionType(_Symbol) != -1)
{
Trail.TrailingStop(_Symbol,TrailingStop,MinimumProfit,Step);
}
} // End of OnTick()
We have just created an expert advisor template that includes money management, a trade timer, a new bar
check, a trailing stop, break even stop, error handling, price verification and more. Now all we need to do is
add our indicators and our order opening and closing conditions.
We're going to create two expert advisors using this template. The first is a moving average cross, which is a
trend trading system. The other will use the Bollinger Bands and RSI to create a counter-trend trading system.
First, we need to declare two objects based on the Fig. 19.1 – The moving average cross system.
CiMA class – one for the fast MA, and one for the slow MA:
#include <MqlBook\Indicators.mqh>
CiMA FastMA;
CiMA SlowMA;
245
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
The object that will be used for the fast MA is named FastMA, and of course, the object for the slow MA is
named SlowMA. Next, we need to add the settings for the moving averages. The variable names for the fast
MA settings are prefixed by "Fast", while those for the slow MA are prefixed by "Slow":
We have a 10 period SMA and a 20 period SMA set up by default. Next, we need to initialize the indicators.
We do this in the OnInit() event handler:
int OnInit()
{
FastMA.Init(_Symbol,_Period,FastMAPeriod,FastMAShift,FastMAMethod,FastMAPrice);
SlowMA.Init(_Symbol,_Period,SlowMAPeriod,SlowMAShift,SlowMAMethod,SlowMAPrice);
Trade.Deviation(Slippage);
return(0);
}
Now all we need to do is use the Main() function of the FastMA and SlowMA objects to retrieve the moving
average values. We do this in the if operators that hold the buy and sell order conditions:
if(glBuyPlaced == true)
{
do(Sleep(100)); while(PositionSelect(_Symbol) == false);
double openPrice = PositionOpenPrice(_Symbol);
246
Putting It All Together
If the fast MA is greater than the slow MA, no buy position is currently open, and glBuyPlaced is false, we
open a buy order. Note the use of the barShift variable for the Main() function parameter. If TradeOnNewBar
is set to true, barShift will always be 1. Therefore, we will be checking the value of the last bar, instead of the
current bar. If TradeOnNewBar is set to false, barShift will be zero, and we will check the value of the
current bar.
That's it! We now have a functioning moving average cross with money management, trailing stop, break even
stop and trade timer. You can view the code in the Moving Average Cross.mq5 file in the
\MQL5\Experts\Mql5Book folder.
247
Putting It All Together
Trade.Deviation(Slippage);
return(0);
}
If the close price of the current bar is less than the lower Bollinger band price, and the RSI reading is below 30,
we open a buy order. If the close is greater than the upper Bollinger band, and the RSI is above 70, we open a
sell order.
When testing this out in the Strategy Tester, we can see that there is room for improvement. For one, the
orders usually enter too soon. Instead of waiting for the price to move back inside the bands, the order opens
immediately. Secondly, this strategy could benefit by adding to an open position as new trade signals appear
in the same direction.
Let's redefine our trading conditions: First, we look for the price to move outside of the bands, accompanied
by an overbought or oversold condition on the RSI. Then we wait for the price to move back inside the bands,
at which time we open the order.
We'll need to check for the trade condition in two steps. First, we check to see if the close price is outside of
the bands and an overbought or oversold RSI condition exists. Then, we check if the price has moved inside
the bands. The first set of conditions will need to be checked outside of the normal buy/sell order conditions.
Let's create a global variable that will hold the signal value:
249
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
enum Signal
{
SIGNAL_BUY,
SIGNAL_SELL,
SIGNAL_NONE,
};
Signal glSignal;
This is declared on the global scope of the program. We've created an enumeration named Signal with three
values: SIGNAL_BUY, SIGNAL_SELL and SIGNAL_NONE. The glSignal global variable will hold one of these
values at all times.
In the OnTick() event handler, before the order placement code, we will check for the first set of conditions.
These are the same conditions we defined previously:
If the close price is less than the lower Bollinger band and the RSI is less than 30, we set the value of glSignal
to SIGNAL_BUY. This value is retained even when the condition is no longer true. The same is true for the sell
condition, in which case we set the value of glSignal to SIGNAL_SELL.
Next, we check the order opening conditions. We check the value of glSignal, and determine whether the
close price has recently crossed the upper or lower band:
First, we check if glSignal contains the value of SIGNAL_BUY. Next, we check to see if the current bar's close
price is above the lower band. Finally, we check to see if the previous bar's close price was below the lower
band. Note that we add 1 to the barShift value to reference the previous bar.
We make the order opening condition very specific to ensure that our expert advisor will not keep opening
orders once the price moves inside the bands. Notice that the PositionType() and glBuyPlaced conditions
250
Putting It All Together
are gone. As long as the specific order condition is fulfilled, we will allow our expert advisor to open another
order in the same direction as our current position.
One more thing: After we have opened the order and confirm that it has been placed, we reset the value of
glSignal to SIGNAL_NONE, to avoid any errant orders:
glSignal = SIGNAL_NONE;
Now we have a counter-trend trading system that trades exactly according to our wishes. This example shows
that it is necessary to be very specific about your trading conditions when programming expert advisors. Your
trading method may appear to be obvious at first glance, but it may take a bit more work to get your expert
advisor to trade it correctly. You can view the code for this trading system in the file Bands RSI
Countertrend.mq5, located in \MQL5\Experts\Mql5Book\.
This file is named MQL Book Pending Template.mq5, and is located in the \MQL5\Experts\Mql5Book folder.
We will note the changes over the template file detailed earlier in the chapter:
// Pending
#include <MqlBook\Pending.mqh>
CPending Pending;
We include the Pending.mqh include file that contains our pending order management functions, and create
the Pending object based on the CPending class.
251
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
glBuyPlaced = Trade.BuyStop(_Symbol,tradeSize,orderPrice,buyStop,buyProfit);
if(glBuyPlaced == true)
{
glSellPlaced = false;
}
}
glSellPlaced = Trade.SellStop(_Symbol,tradeSize,orderPrice,sellStop,sellProfit);
if(glSellPlaced == true)
{
glBuyPlaced = false;
}
}
Above is the pending buy and sell order placement code. The orderPrice variable must be filled with the
pending order opening price by the programmer. The order price is checked for correctness, the stop loss and
take profit are calculated relative to it, and the pending stop order is placed. Finally, the glBuyPlaced or
glSellPlaced variable is set to false.
Let's create a pending order trading system using this template. We will create a trading system that finds the
highest and lowest price of the last X bars. When the trade timer starts, a pending buy stop order will be
placed at the highest high and a pending sell stop order will be placed at the lowest low. The stop loss for
252
Putting It All Together
both orders will be placed at the opposite price. Only one trade in each direction will be placed, and all trades
will be closed at the timer end time.
For this strategy, we will remove the new bar check, the trailing stop and the break even features. We will also
remove the StopLoss, Slippage and UseTimer settings. Let's start with the #include directives:
// Trade
#include <MqlBook\Trade.mqh>
CTrade Trade;
// Price
#include <MqlBook\Price.mqh>
// Money management
#include <MqlBook\MoneyManagement.mqh>
// Timer
#include <MqlBook\Timer.mqh>
CTimer Timer;
// Pending
Fig. 19.3 – A pending breakout trading system.
#include <MqlBook\Pending.mqh>
CPending Pending;
These are the include files and objects that we will need for this strategy. Next, let's examine the input
variables:
253
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
We added a new input variable named HighLowBars. This is the number of bars that we are going to search
(starting from the current bar) to find the highest high and lowest low. We grouped it in with the TakeProfit
variable, added a static input variable and labeled the group as "Trade Settings".
Let's skip ahead to the order placement code. We will come back to the timer code later:
// Order placement
if(timerOn == true)
{
// Highest high, lowest low
double hHigh = HighestHigh(_Symbol,_Period,HighLowBars);
double lLow = LowestLow(_Symbol,_Period,HighLowBars);
// Money management
double tradeSize;
if(UseMoneyManagement == true)
tradeSize = MoneyManagement(_Symbol,FixedVolume,RiskPercent,(int)diff);
else tradeSize = VerifyVolume(_Symbol,FixedVolume);
If the trade timer is active, we will start by finding the highest high and lowest low. We use the
HighestHigh() and LowestLow() functions that we defined in our Price.mqh include file. The HighLowBars
parameter indicates the number of bars to search. The return values are saved in the hHigh and lLow
variables, respectively.
Since we are using our money management code, we need to figure out the stop loss in points to be able to
calculate the lot size. We do this by subtracting the lowest low (lLow) from the highest high (hHigh) and
dividing it by the current symbol's _Point value. The result is saved to the variable diff.
The money management code is the same as the previous expert advisors, except we pass the diff variable
as the final parameter to the MoneyManagement() function. The (int) in front of the diff variable simply
converts the value of diff to an integer, rounding it off and preventing the "possible loss of data due to type
conversion" warning that occurs when we implicitly change data types.
254
Putting It All Together
glBuyPlaced = Trade.BuyStop(_Symbol,tradeSize,orderPrice,buyStop,buyProfit);
}
glSellPlaced = Trade.SellStop(_Symbol,tradeSize,orderPrice,sellStop,sellProfit);
}
Let's look at the buy stop order code first. If the Pending.BuyStop() function returns zero – indicating that
no pending orders are open – and the glBuyPlaced variable is false, we proceed with placing the order. The
orderPrice variable is assigned the value of hHigh. This is our order opening price. We check it using the
AdjustAboveStopLevel() function to make sure it is not too close to the current price. The buyStop variable
is set to lLow, which is our stop loss. The take profit is calculated, if it has been specified. The
Trade.BuyStop() function places our pending order, and if it is successful, the glBuyPlaced variable is set to
true.
When the trade timer is first activated for the day, a buy and sell stop order are placed. No more orders are
placed for the day, and all orders are closed at the timer end time. Let's take a look at the timer code. This
goes before the order placement code above:
// Timer
bool timerOn = Timer.DailyTimer(StartHour,StartMinute,EndHour,EndMinute,UseLocalTime);
if(timerOn == false)
{
if(PositionSelect(_Symbol) == true) Trade.Close(_Symbol);
255
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
glBuyPlaced = false;
glSellPlaced = false;
}
First, we declare the timerOn variable, and set it using the DailyTimer() function. If the value of timerOn is
false, indicating that the timer is off, we need to close any open orders and reset our glBuyPlaced and
glSellPlaced variables. First, we check the output of the PositionSelect() function. If it returns true, we
use the Trade.Close() function to close the current position.
Next, we need to check for any open pending orders. The Pending.TotalPending() function returns the
number of open pending orders, and saves it to the total variable. If total is greater than zero, we process
the pending orders.
We declare a ulong array named tickets[] that holds the ticket numbers of any open pending orders. The
Pending.GetTickets() function fills the tickets[] array with the ticket numbers. We iterate through the
tickets[] array using a for loop, and delete each open order using the Trade.Delete() function. Finally,
we set glBuyPlaced and glSellPlaced to true.
You can view the code for this expert advisor in the \Experts\Mql5Book\Pending Order Breakout.mq5 file.
We've just demonstrated how to create several basic strategies using the techniques we've examined in this
book. Although some advanced strategies will require a bit more modification to the template file, the basics
are there so you can easily implement your strategy without having to code a new strategy from scratch.
256
Tips & Tricks
Alert() Function
If you need to alert the user to an error or other adverse condition, the built-in Alert dialog is ideal. The Alert
dialog window shows a running log of alert messages, indicating the time and symbol for each. If sound
events are enabled in the Options tab of the Tools menu, then an alert sound will play when the dialog
appears.
The Alert() function is used to show the alert dialog. The Alert() function takes any number of arguments,
of any type. We've been using the Alert() function all throughout our include files. For example, here is an
Alert() function call when an error occurs in our CTrade::OpenPosition() function in Trade.mqh:
This function has several arguments, including strings and an integer variable, all separated by commas.
257
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
MessageBox() Function
If you prefer something a little more elaborate, or if you need to accept user input, the MessageBox()
function allows you to display a standard Windows dialog box with user-defined text, caption, icons and
buttons. Here is the definition of the MessageBox() function:
int MessageBox(
string text, // Message text
string caption=NULL, // Title bar caption
int flags=0 // Button and icon constants
);
The text parameter is the text to show in the dialog window. The caption parameter is the text to show in
the title bar of the dialog window. The flags parameter defines which buttons to show, as well as icons and
default buttons. The MessageBox() flags can be viewed in the MQL5 Reference under Standard Constants... >
Input/Output Constants > MessageBox. Here are a few of the most commonly-used button flags:
• MB_OK – OK button
Let's create a simple message box with text, a caption and yes/no buttons. For example, if you want to prompt
to user to place an order when a trading signal is received:
This will create the message box dialog shown to the right.
258
Tips & Tricks
Here is how we would add the question mark icon to our message box. Flags must be separated with a pipe
character (|):
Fig. 20.3 to the right shows the Message Box dialog with the
question mark icon.
When presented with the message box, the user will click a button
and the box will close. If the message box is a simple information
or error message with a single OK button, then we do not need to
do anything else. But if the message box has several buttons, we
will need to retrieve the value of the button that the user pressed
and take appropriate action.
Fig. 20.3 – The Message Box dialog with icon.
In the example below, the place integer variable will hold the return
value of the MessageBox() function. The return value for the Yes button is IDYES, while the return value for
the No button is IDNO. If the user clicked Yes, then we need to proceed to place the order:
if(place == IDYES)
{
// Open position
}
You can view additional return value constants for MessageBox() buttons in the MQL5 Reference under
Standard Constants... > Input/Output Constants > MessageBox.
SendMail() Function
If you'd like your expert advisor to send you an email whenever a trade is placed, simply add the SendMail()
function to your expert advisor after the order placement has been confirmed. You will need to enable email
notifications in MetaTrader under the Tools menu > Options > Email tab. Under the Email tab, you will enter
your email server information and your email address.
The SendMail() function takes two parameters. The first is the subject of the message, and the second is the
message text. For example, if you want to send an email just after a buy position has been opened:
259
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
#include <Mql5Book\Trade.mqh>
if(PositionSelect(_Symbol) == true)
{
string subject = "Buy position opened on "+_Symbol;
string message = "Price: "+PositionOpenPrice()+", SL: "+PositionStopLoss()+",
TP: "+PositionTakeProfit();
SendMail(subject,message);
}
If the PositionSelect() function returns true, indicating that a position has been successfully opened, we
will prepare the subject and message body of the email and pass those string variables to the SendMail()
function. An email will be sent to the email address defined in the Email tab of the Tools menu > Options
dialog.
A mobile edition of MetaTrader is available for iPhone and Android devices. Your expert advisors can send
trade notifications to the mobile MetaTrader terminal on your smartphone by using the SendNotification()
function. You will need to configure MetaTrader to send notifications under the Tools menu > Options >
Notifications tab. In the Notifications tab, you will enter your MetaQuotes ID, which you can retrieve from your
mobile version of MetaTrader.
The SendNotification() function takes one parameter, a string indicating the text to send. You can use the
SendNotification() function anywhere you would use the SendMail() function. Here's an example:
#include <Mql5Book\Trade.mqh>
if(PositionSelect(_Symbol) == true)
{
string message = "Buy position opened on "+_Symbol+". Price: "+PositionOpenPrice()+",
SL: "+PositionStopLoss()+",TP: "+PositionTakeProfit();
SendNotification(message);
}
Playing Sound
The PlaySound() function will play a WAV sound file located in the \Sounds directory. This can be used to
play an audible alert when a trade is placed, or whenever you want the user's attention. MetaTrader comes
with several sound files, but you can find more online.
If you want the user to be able to choose the sound file, use an input string variable to enter the file name:
260
Tips & Tricks
// ...
PlaySound(SoundFile);
Comment() Function
The Comment() function will display text in the top left corner of the chart. This is useful for informing users of
actions taken by the expert advisor, such as modifying or closing orders. We've been using the Comment()
function throughout our expert advisors to write informative comments to the chart. Here's an example from
the CTrade::OpenPosition() function in Trade.mqh:
if(checkCode == CHECK_RETCODE_OK)
{
Comment(orderType," position opened at ",result.price," on ",pSymbol);
return(true);
}
The problem with using chart comments is that all programs have access to this function. So if you have an
indicator and an expert advisor that both write comments to the chart using the Comment() function, they will
overwrite each other. If you need to display information on the chart that is always present, then consider
using chart objects.
Chart Objects
Chart objects consist of lines, technical analysis tools, shapes, arrows, labels and other graphical objects. You
can insert objects on a chart using the Insert menu > Objects submenu. MQL5 has a variety of functions for
creating, manipulating and retrieving information from chart objects.
The ObjectCreate() function is used for creating new chart objects. Here is the function definition for
ObjectCreate():
bool ObjectCreate(
long chart_id, // chart identifier
string name, // object name
ENUM_OBJECT type, // object type
sub_window nwin, // window index
datetime time1, // time of the first anchor point
261
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
The chart_id parameter specifies the chart to create the object on. Since we will always be working with the
current chart, chart_id will be 0. The name parameter is the name of the object to create. We will need to
reference this name anytime we need to use the object. The type parameter is the type of object to create. It
takes a value of the ENUM_OBJECT enumeration type. The object types can be viewed in the MQL5 Reference
under Standard Constants... > Objects Constants > Object Types.
The nwin parameter is the index of the subwindow of the chart. Subwindows are opened by indicators such as
RSI and stochastics that display in a separate window. Since we will be creating objects in the main chart
window, nwin will be 0. The time1 and price1 parameters are the time and price for the first anchor point of
the object. Most objects have one or more anchor points, although some do not use them, or use only one or
the other. The first set of anchor points must always be specified, even if they are not used.
If the object uses more than one set of anchor points, they must also be specified. For example, most trend
line objects use two anchor points. So in the ObjectCreate() function, a second set of anchor points would
be passed to the function. You can view the number of anchor points for each type of object in the MQL5
Reference under Object Functions > ObjectCreate.
The ObjectCreate() function will create the object in the location specified. However, we will still need to set
the properties of the object. The ObjectSet...() functions are used to set the object's properties. There are
three ObjectSet...() functions: ObjectSetInteger(), ObjectSetDouble() and ObjectSetString(). All
three functions share the same parameters. Let's look at the function definition of ObjectSetInteger():
bool ObjectSetInteger(
long chart_id, // chart identifier
string name, // object name
int prop_id, // property
long prop_value // value
);
As discussed previously, chart_id will be 0. The name parameter is the name of the object to modify. The
prop_id parameter takes a value of the ENUM_OBJECT_PROPERTY_INTEGER enumeration, which indicates the
property to modify. Finally, the prop_value parameter is the value to set. The
ENUM_OBJECT_PROPERTY_INTEGER constants can be viewed in the MQL5 Reference under Standard Constants...
> Objects Constants > Object Properties.
262
Tips & Tricks
As an example, let's create a trend line object and set a few of its properties. The object type for a trend line is
OBJ_TREND. Since a trend line has two anchor points, we will need to pass two sets of time and price values.
We'll assume that the time1, time2, price1 and price2 variables are filled with the appropriate values:
ObjectCreate(0,"Trend",OBJ_TREND,0,time1,price1,time2,price2);
This creates a trend line object named "Trend" on the current chart. Next, let's modify a few of the properties
of our trend line. We're going to modify the color, the style and the ray right properties:
ObjectSetInteger(0,"Trend",OBJPROP_COLOR,clrGreen);
ObjectSetInteger(0,"Trend",OBJPROP_STYLE,STYLE_DASH);
ObjectSetInteger(0,"Trend",OBJPROP_RAY_RIGHT,true);
The first ObjectSetInteger() function call adjusts the color property, using the OBJPROP_COLOR constant.
The value used is the color constant for green, clrGreen. The second call of the function adjusts the line style,
using the OBJPROP_STYLE constant. The value is STYLE_DASH, a constant of the ENUM_LINE_STYLE type.
Finally, we use the OBJPROP_RAY_RIGHT constant to set the ray right property to true. The ray right property
extends the trend line to the right beyond the second anchor point.
Now we have a green dashed trend line that extends to the right. All of the examples above use integer types
to adjust the properties. The type used for each object property constant is listed in the MQL5 Reference under
Standard Constants... > Objects Constants > Object Properties.
Fig. 20.4 – A trend line object with the color, style and ray right properties set.
If you need to move an object, the ObjectMove() function allows you to adjust one of the anchor points of an
object. Here is the function definition for ObjectMove():
263
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
bool ObjectMove(
long chart_id, // chart identifier
string name, // object name
int point_index, // anchor point number
datetime time, // time
double price // price
);
The name parameter is the name of the object to modify. The point_index parameter is the anchor point to
modify. Anchor point numbers start at 0, so the first anchor point would be 0, the second would be 1, and so
on. The time and price parameters modify the time and price for the specified anchor point.
When using line objects such as trend lines or channels, you may need to retrieve the price value of a line at a
particular bar. Assuming that we know the timestamp of the bar, we can easily retrieve the price. The
ObjectGetValueByTime() function will retrieve the price for a specified time:
double ObjectGetValueByTime(
long chart_id, // chart identifier
string name, // object name
datetime time, // time
int line_id // line number
);
As always, the chart_id is 0 for the current chart. The name parameter is the name of a line object on our
chart. The time parameter is the timestamp of a bar that intersects the line. The line_id parameter is for
channel objects that have several lines. For a trend line, line_id would be 0.
Using the "Trend" line object we created above, here is how we would retrieve the price for the current bar.
We will use the CPrice::Time() function we created earlier in the book:
The ObjectGetValueByTime() function will return the value of the trend line for the current bar and save the
result to the trendPrice variable.
Now, what if you have a price, and you want to know what bar comes closest to that price? The
ObjectGetTimeByValue() function will return the timestamp of the bar where a line object intersects a given
price:
264
Tips & Tricks
datetime ObjectGetTimeByValue(
long chart_id, // chart identifier
string name, // object name
double value, // price
int line_id // line number
);
The value parameter is the price to search for. The function will return the timestamp of the closest bar where
the line intersects the price:
Earlier in the chapter, we talked about using the Comment() function to write information to the current chart.
The label object can also be used for this purpose. Unlike chart comments, label objects can be placed
anywhere on the chart, and they can use any color or font that you like.
The object type constant for the label object is OBJ_LABEL. Label objects do not use anchor points, but rather
use the corner, x-distance and y-distance properties for positioning. Here's an example of the creation and
positioning of a label object:
ObjectCreate(0,"Label",OBJ_LABEL,0,0,0);
ObjectSetInteger(0,"Label",OBJPROP_CORNER,1);
ObjectSetInteger(0,"Label",OBJPROP_XDISTANCE,20);
ObjectSetInteger(0,"Label",OBJPROP_YDISTANCE,40);
We've created a label object named "Label" and set the position to the bottom left corner using the
ObjectSetInteger() function with the OBJPROP_CORNER property. The label is 20 pixels from the left border
(OBJPROP_XDISTANCE) and 40 pixels above the bottom border (OBJPROP_YDISTANCE).
Note that the time and price parameters for the ObjectCreate() function are set to 0, since they are not
used when creating label objects. The corners are labeled from 0 to 3, starting counter-clockwise from the
top-left corner. A value of 1 is the lower-left corner, while 3 would be the upper-right corner. The x-distance
and y-distance are set in pixels from the specified corner.
Next, we'll need to set the color, font and text of the label object:
265
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
ObjectSetInteger(0,"Label",OBJPROP_COLOR,clrWhite);
ObjectSetString(0,"Label",OBJPROP_FONT,"Arial");
ObjectSetInteger(0,"Label",OBJPROP_FONTSIZE,10);
ObjectSetString(0,"Label",OBJPROP_TEXT,"Bid: "+(string)price);
We've set the color of the label object to white, using 10 point Arial font.
The text of the label contains the current Bid price. Every time this code is
run, the label object will be updated with the current Bid price.
In summary, the label object is ideal for printing useful information to the
chart. The object is anchored to the chart window itself, and will not move if
the chart is scrolled. The font, color, position and text are fully adjustable.
Fig. 20.5 – The label object.
The last object type we'll examine is the arrow object. You may wish to draw
an arrow on the chart when an order is placed. MQL5 defines two arrow object types that are useful for
marking buy and sell signals – OBJ_ARROW_BUY and OBJ_ARROW_SELL. Arrow objects use one anchor point –
the price and time where the arrow should be placed:
ObjectCreate(0,"BuyArrow"+time,OBJ_ARROW_BUY,0,time,price);
The example above will set a buy arrow object at the current Ask price on the
current bar. We append the time of the current bar to the object name so that
each arrow we draw will have a unique name. This code would be called after a Fig. 20.6 – The Buy Arrow object.
Deleting Objects
To delete an object, simply call the ObjectDelete() function and pass the chart_id (usually 0) and the
object name:
ObjectDelete(0,"Label");
To delete all objects from a chart, use the ObjectsDeleteAll() function. You can choose to delete only
objects of a certain type or from certain subwindows. To delete all objects from the current chart, use this call:
ObjectsDeleteAll(0);
266
Tips & Tricks
It is a good idea to call this function from your program's OnDeinit() function to ensure that all objects are
deleted when a program is removed from the chart.
File Functions
MQL5 has a set of functions for reading and writing to files. You can, for example, create a log of your expert
advisor trades, or import/export trading signals to a file. All files must be in the \MQL5\Experts\Files folder
of your MetaTrader 5 installation.
We will examine how to read and write to a CSV file. A CSV (comma separated value) file contains data much
like a spreadsheet. You can create and view CSV files in programs such as Microsoft Excel or OpenOffice Calc,
as well as any text editor. In this example, our CSV file will record information about trades. We will write the
symbol, open price, stop loss, take profit and open time of a trade to each line of the file. We will then read
that information back into our program.
The FileOpen() function opens files for reading and writing. If the file does not exist, it will be created. Here
is the definition of the FileOpen() function:
int FileOpen(
string file_name, // File name
int open_flags, // Combination of flags
short delimiter='\t' // Delimiter
uint codepage=CP_ACP // Code page
);
The file_name parameter is the name of the file to open in the \MQL5\Experts\Files directory. The
open_flags parameter contains a combination of flags describing the file operations. The file opening flags
can be viewed in the MQL5 Reference under Standard Constants... > Input/Output Constants > File Opening
Flags. The delimiter parameter is the field delimiter for a CSV file. The default is the tab character, but we will
use a comma. The codepage parameter will be left at its default.
The FileOpen() function returns an integer that will serve as the file handle. Every time we perform an
operation on the file, we will need to reference the file handle. For the file opening flags, we will be using a
combination of FILE_READ, FILE_WRITE and FILE_CSV.
The FileWrite() function is used to write a line of data to a CSV file. The example below shows how to open
a file, write a line of data to it, and then close the file:
267
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
string symbol;
double openPrice, sl, tp;
datetime openTime;
// ...
The symbol, openPrice, sl, tp and openTime variables will contain the information to write to the file. We'll
assume that these variables are filled with the appropriate values. The FileOpen() function creates a file
named "Trades.csv". The flags specify read and write privileges to a CSV file. The delimiter will be a comma.
The file handle is saved to the fileHandle variable.
You will need to add the FILE_READ flag when using the FILE_WRITE flag, even if you are not reading
information from the file, because the FileSeek() function will not work without it. The FileSeek() function
moves the file pointer to a specified point in the file. In this example, the pointer is moved to the end of the
file. The first parameter of the FileSeek() function is the file handle, the second is the shift in bytes, and the
third parameter is the start location. The SEEK_END constant indicates that we will move the file pointer to the
end of the file. When opening a file that already has data in it, failing to move the file pointer to the end of the
file will result in data being overwritten. Therefore, we always use FileSeek() to locate the end of the file
before writing data.
The FileWrite() function writes a line of data to the CSV file. The first parameter is the file handle. The
remaining parameters are the data to write to the file, in the order that they appear. Up to 63 additional
parameters can be specified, and they can be of any type. The delimiter specified in the FileOpen() function
will be placed between each data field in the CSV file, and a new line character ( \r\n) will be written at the
end of the line.
Finally, the FileClose() function will close the file. Be sure to close a file when you are done using it, or else
you may not be able to open it in another program. If you plan on keeping a file open for an extended period
of time, or are doing subsequent read/write operations, use the FileFlush() function to write the data to the
file without closing it.
Here is what our Trades.csv file will look with several lines of data written to it:
EURUSD,1.2345,1.2325,1.2375,2012.11.15 04:17:41
EURUSD,1.2357,1.2337,1.2397,2012.11.15 04:20:04
EURUSD,1.2412,1.2398,1.2432,2012.11.15 04:21:35
268
Tips & Tricks
From left to right, each line contains the trade symbol, opening price, stop loss, take profit and open time,
each separated by a comma. After each line is written to the file, a new line is started.
If you need to write several lines of data to a file at once, place the FileWrite() function inside a loop, and
loop it as many times as you need to. The example below assumes that we have several arrays, each with the
same number of elements, that are properly sized and filled with data. (You could also use a structure array for
this.) We use a for loop to write each line of data to the file:
string symbol[];
double openPrice[], sl[], tp[];
datetime openTime[];
// ...
FileClose(fileHandle);
We use the ArraySize() function on the symbol[] array to determine the number of times to run the loop.
The i increment variable is the array index. If each array has five elements, for example, we write five lines of
data to the file.
Next, we'll examine how to read data from a CSV file. The FileRead...() functions are used to read data
from a field and convert it to an appropriate type. There are four functions used to read data from a CSV file:
• FileReadBool() - Reads a string from a CSV file and converts it to bool type.
• FileReadDatetime() - Reads a string from a CSV file in the format yyyy.mm.dd hh:mm:ss and
converts it to datetime type.
• FileReadNumber() - Reads a string from a CSV file and converts it to double type.
If you are reading an integer from a CSV file using the FileReadNumber() function, you will need to convert it
to the appropriate type if you need to use it as an integer type in your program.
269
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
If we examine the fields in our Trades.csv file, we have a string, three double values and a datetime value
on each line. We will need to read each field of data from the file using the appropriate function so that it is
converted to the correct type. We're going to read the entire contents of the file, line by line, and save the
result to a structure array:
struct Trades
{
string symbol;
double openPrice;
double sl;
double tp;
datetime openTime;
};
Trades trade[];
int i;
while(FileIsEnding(fileHandle) == false)
{
ArrayResize(trade,ArraySize(trade) + 1);
trade[i].symbol = FileReadString(fileHandle);
trade[i].openPrice = FileReadNumber(fileHandle);
trade[i].sl = FileReadNumber(fileHandle);
trade[i].tp = FileReadNumber(fileHandle);
trade[i].openTime = FileReadDatetime(fileHandle);
i++;
}
FileClose(fileHandle);
First, we create a structure named Trades to hold the data read from the CSV file. We create an array object
named trade[], and initialize the incrementor variable i. We open the Trades.csv file using the FILE_READ
and FILE_CSV flags. The while loop will read each line of data from the file, one field at a time.
The FileIsEnding() function returns a value of true if the end of the file has been reached, and false
otherwise. As long as the end of the file has not been reached, we continue reading the next line. The
ArrayResize() function resizes our trade[] array, one element at a time. We call the ArraySize() function
to get the current size of the trade[] array and add 1 to it to increase the size.
270
Tips & Tricks
The FileRead...() functions reads each field of data from the file and converts it to the appropriate type.
The result is saved to the appropriate member variable of our trades[] array. After the current line has been
read, we increment the i variable and check the FileIsEnding() condition again. After the loop exits, we
close the file. We can now access the data read from our CSV file using the trade[] array object.
Global Variables
MetaTrader has the ability to save variables to the terminal, which remain even if the terminal is shut down.
These are referred to as Global Variables. Global Variables that are saved to the terminal are deleted after one
month. You can view the Global Variables saved to your terminal by clicking the Tools menu > Global
Variables, or by pressing the F3 key.
Do not confuse the Global Variables of the terminal with variables that we define on the global scope of a
program! Global variables in a program are available only to that program, while the Global Variables of the
terminal are available to all programs. You can use MetaTrader's Global Variables to save information about
your program's state in the event that execution is interrupted.
The GlobalVariableSet() function is used to save a Global Variable to the terminal. It has two parameters –
the name of the variable, and the value to assign to it. Make sure that you use unique names for your Global
Variables. For example, you could use the name of your trading system, followed by the name of the variable
and the symbol that it is placed on:
GlobalVariableSet(varName,1.5);
In this example, our Global Variable name is ForexRobot_TradeSize_EURUSD. The current symbol is EURUSD,
so we will be able to identify our Global Variable based on the symbol we are currently trading. We would save
this Global Variable to the terminal any time its value is set or changed.
271
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
If our terminal shut down unexpectedly (a computer crash or power failure), we can read the contents of the
Global Variable using GlobalVariableGet(), and continue where we left off. We would usually do this in our
OnInit() event handler:
// OnInit()
string varName = "ForexRobot_TradeSize_"+_Symbol;
double tradeSize = GlobalVariableGet(varName);
To prevent our program from using outdated Global Variables, we will need to delete them if necessary. If we
manually remove our expert advisor from the chart, then we need to delete the Global Variable(s) that are
currently saved. We do this in the OnDeinit() event handler using the GlobalVariableDel() function:
The OnDeinit() event handler is called for many reasons. Obviously, it is called if the program is removed
from the chart, the chart is closed, or the terminal is shut down. But it is also called if the input parameters are
changed, the period of the chart is changed, or a template is applied. We don't want to delete any Global
Variables when this occurs. So it is necessary to check the reason for deinitialization before deleting any
Global Variables.
The reason parameter of the OnDeinit() function contains the reason for deinitialization. You can view the
Deinitialization Codes in the MQL5 Reference under Standard Constants... > Named Constants > Uninitalization
Reason Codes. The codes we are concerned with are REASON_CHARTCHANGE, REASON_PARAMETERS, and
REASON_TEMPLATE. If the reason parameter contains any of these codes, we will not delete the Global
Variable:
272
Tips & Tricks
Stopping Execution
If you wish to stop the execution of an expert advisor programmatically, use the ExpertRemove() function.
Once the current event is finished executing, the expert advisor will stop its operation and remove itself from
the chart.
If you wish to close the terminal, the TerminalClose() function will close MetaTrader. The TerminalClose()
function takes one parameter, a deinitialization code that will be passed to the OnDeinit() function. When
calling the TerminalClose() function, it must be followed by the return operator:
TerminalClose(REASON_CLOSE);
return;
273
Indicators, Scripts & Libraries
Indicators
In Chapter 17, we examined how to add indicators to our expert advisor programs. MQL5 allows you to create
your own custom indicators as well. In this section, we will create a custom indicator that will plot a price
channel using the highest high and lowest low of the last x bars. This is also referred to as a Donchian channel.
Drawing Styles
MQL5 has over a dozen different drawing styles for indicator lines. The drawing style determines how the line
will appear in the chart window. Here are a few of the most common drawing styles:
• DRAW_LINE – This is the most common drawing style, and consists of a single line of the specified
color. The Moving Average, RSI and many other indicators use this drawing style.
• DRAW_HISTOGRAM – The histogram drawing style is typically used by oscillators such as the MACD, the
OsMA and the Bill Williams' oscillators. It consists of vertical lines oscillating around a zero axis.
• DRAW_SECTION – The section drawing style connects prices on non-consecutive bars with an unbroken
line. The ZigZag custom indicator that comes with MetaTrader is the typical example of this drawing
style.
• DRAW_ARROW – The arrow drawing style will draw arrow objects on the chart. The Fractals indicator
uses arrow objects to indicate swing highs and lows.
• DRAW_NONE – Used for indicator buffers that will not be drawn on the chart.
Each drawing style also has a color variant. The DRAW_COLOR_ drawing styles allows the programmer to vary
the color of the indicator line on a bar-by-bar basis. You can view all of the drawing styles with examples in
the MQL5 Reference under Custom Indicators > Indicator Styles in Examples.
All indicators require the OnCalculate() event handler. It is the equivalent of the OnStart() event handler
for expert advisors, and runs on every incoming tick. There are two variants of the OnCalculate() event
handler. The first is for indicators that use a single price series. For example, a moving average indicator allows
the user to select a price series to calculate the data on (close, high, low, etc.) The selected price series is
passed to the OnCalculate() event handler. Here is the first variant of OnCalculate():
275
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
The rates_total parameter contains the number of bars in the history for the current chart symbol. The
prev_calculated parameter is the number of bars in the history that have already been calculated previously
by the indicator. The first time that OnCalculate() runs, prev_calculated will be zero. On subsequent runs,
prev_calculated will be equal to rates_total. When a new bar opens, and the value of rates_total
increases by one, the difference between rates_total and prev_calculated will be 1, indicating that we
need to calculate the most recent bar.
Fig. 21.1 – The Apply to drop-down box under the Parameters tab of the indicator Properties dialog.
The second variant of the OnCalculate() event handler adds additional price and time series arrays that can
be used in indicator calculations. If your indicator requires multiple price series (such as the high AND low of
each bar), then use the second variant of OnCalculate():
276
Indicators, Scripts & Libraries
Our channel indicator will use this variant of the OnCalculate() event handler, since we need to use both the
high and low price series.
MQL5 Wizard
The MQL5 Wizard can be used to create a starting template for your custom indicator. It is much more
convenient to use the wizard to add our event handlers, buffers and lines than it is to add them manually.
Open the MQL5 Wizard by clicking the New button on the MetaEditor toolbar. Select Custom Indicator and
click Next to continue.
Fig. 21.2 – The custom indicator properties dialog of the MQL5 Wizard.
The custom indicators are saved to the \MQL5\Indicators folder by default. You can add input variables on
this screen if you wish. In Fig. 21.2, we have added an int input variable named HighLowBars, with a default
value of 8.
The next screen allows you to select the event handlers to add to your indicator. The first variant of the
OnCalculate() event handler is selected by default. You can add additional event handlers if necessary. For
our indicator, we will need to select the second variant of the OnCalculate() event handler. Fig. 21.3 below
shows the Event handlers dialog:
277
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
Fig. 21.3 – The event handlers dialog of the MQL5 Wizard for custom indicators.
The final screen is where you set the drawing properties of the indicator. If this indicator will be displayed in a
separate window (such as an oscillator), check Indicator in separate window to add the relevant #property
directive to the indicator file. The Plots window allows you to add and set the properties for the indicator lines:
Fig. 21.4 – The drawing properties dialog of the MQL5 Wizard for custom indicators.
278
Indicators, Scripts & Libraries
The Label column is the name of the indicator line as it will appear in the Data Window. It is also used to
create the array buffer name. We have added two indicator lines named Upper and Lower. Double-click on the
Type column to reveal a drop-down box to select the line's drawing type. We have left our lines set to the Line
drawing type. Finally, the Color column is where you set the color of the indicator line. We have set both lines
to Red.
Click Finish to close the Wizard and open the indicator template in MetaEditor. Here is what our indicator
template look like. We've left out the OnCalculate() event handler for now, and formatted the file for clarity:
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
SetIndexBuffer(0,UpperBuffer,INDICATOR_DATA);
SetIndexBuffer(1,LowerBuffer,INDICATOR_DATA);
return(0);
}
279
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
The relevant #property directives have been inserted for our indicator lines. You can view all of the indicator
#property directives in the MQL5 Reference under Language Basics > Preprocessor > Program Properties. Our
HighLowBars input variable has been inserted, as well as two arrays for our buffers, UpperBuffer[] and
LowerBuffer[]. The SetIndexBuffer() functions in the OnInit() event handler assigns the arrays to the
relevant indicator buffers.
Our indicator calculations are carried out in the OnCalculate() event handler. The OnCalculate() event
handler for our indicator is shown below:
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &Time[],
const double &Open[],
const double &High[],
const double &Low[],
const double &Close[],
const long &TickVolume[],
const long &Volume[],
const int &Spread[])
{
//---
ArraySetAsSeries(UpperBuffer,true);
ArraySetAsSeries(LowerBuffer,true);
ArraySetAsSeries(High,true);
ArraySetAsSeries(Low,true);
280
Indicators, Scripts & Libraries
None of the arrays in this program are set as series arrays by default. We will need to set them as series arrays
using the ArraySetAsSeries() function. This is usually optional, but our indicator requires us to access the
price data as a series. Both of our indicator buffers, as well as the High[] and Low[] arrays passed by the
OnCalculate() event handler will be set as series:
ArraySetAsSeries(UpperBuffer,true);
ArraySetAsSeries(LowerBuffer,true);
ArraySetAsSeries(High,true);
ArraySetAsSeries(Low,true);
We use a for loop to calculate the indicator values for each of the bars on the current chart. We determine
the number of bars to process by using the prev_calculated and rates_total parameters of the
OnCalculate() event handler. As mentioned earlier, the rates_total variable contains the total number of
bars on the chart, while prev_calculated contains the number of bars calculated by the previous run of the
OnCalculate() event handler.
For series arrays, the maximum array index is rates_total – 1. This refers to the oldest bar on the chart. The
most recent bar has an index of zero. When OnCalculate() is first run, the value of prev_calculated will be
zero. If prev_calculated is zero, we set the maximum array index to rates_total – 1. On subsequent runs,
we calculate the maximum array index by subtracting prev_calculated from rates_total. This ensures that
only the most recent bar(s) will be calculated:
The variable bars will hold the starting array index. In our for loop below, we assign the value of bars to our
incrementor variable i. We will decrement the value of i until i = 0:
As long as your price and buffer arrays are set as series, the for loop above will work for calculating most
indicators. The code to calculate the indicator and fill the buffers goes inside the loop.
To calculate the array buffers for our channel indicator, we simply use the ArrayMaximum() and
ArrayMinimum() functions to find the highest and lowest values for the number of bars indicated by
HighLowBars, relative to the array index indicated by the i incrementor variable. The resulting array index is
281
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
used in the High[] and Low[] arrays to return the highest high and lowest low. The result is saved to the
UpperBuffer[] and LowerBuffer[] arrays respectively:
The last step is to return the value of rates_total and exit the OnCalculate() event handler. This is inserted
automatically by the MQL5 Wizard:
We've just created a simple indicator that takes price data from the OnCalculate() event handler and
calculates two indicator lines. This indicator can be used to create trading signals, or as a stop loss for trending
positions. We've only touched upon you can do with custom indicators in MQL5. To learn more about custom
indicator function, consult the MQL5 Reference under Custom Indicators.
You can view the source code for this file in \MQL5\Indicators\High Low Channel.mq5.
282
Indicators, Scripts & Libraries
Scripts
A script is a simple MQL5 program that executes once when it is attached to a chart. It consists of a single
event handler, the OnStart() event handler. When a script is attached to a chart, the OnStart() event
handler executes. Unlike an expert advisor or indicator, a script does not repeat its execution after the
OnStart() event handler has finished. The script is automatically detached from a chart after execution.
To create a script, use the MQL5 Wizard. All scripts are saved to the \MQL5\Scripts directory. Your new script
file will contain an empty OnStart() event handler. There are a couple of #property directives that control
the behavior of your script. If your script has input variables, you'll want to use the script_show_inputs
property to allow the user to adjust the inputs and verify execution. If your script does not have input
variables, the script_show_confirm property displays a confirmation box asking to user whether to execute
the script. You'll want to add one of these #property directives to your script.
Here is an empty script file containing several #property directives and the OnStart() event handler:
void OnStart()
{
We're going to create a useful script that can be used to close all open orders and positions on a chart. When
attached to a chart, the script will first prompt the user as to whether to execute the script. If so, the script will
first close any open position on the current chart symbol. Then it will close any open pending orders.
#property script_show_confirm
#include <Mql5Book\Trade.mqh>
CTrade Trade;
#include <Mql5Book\Pending.mqh>
CPending Pending;
283
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
void OnStart()
{
// Close current position
if(PositionType(_Symbol) != WRONG_VALUE)
{
bool closed = Trade.Close(_Symbol);
if(closed == true)
{
Comment("Position closed on "+_Symbol);
}
}
// Close orders
for(int i = 0; i < numTickets; i++)
{
Trade.Delete(tickets[i]);
}
if(Pending.TotalPending(_Symbol) == 0)
{
Comment("All pending orders closed on "+_Symbol);
}
}
}
The #property directives include the copyright, link and description, as well as the
script_show_confirm property. This prompts the user with a confirmation dialog before executing the script.
We've also included our Trade.mqh and Pending.mqh files from the \MQL5\Include\Mql5Book folder and
created objects based on the CTrade and CPending classes.
When the script is executed, the OnStart() event handler runs. We first check the output of the
PositionType() function. If it indicates an open position, we call the Trade.Close() function to close the
current position. A chart comment is shown if the position is closed properly.
Next, we check the Pending.TotalPending() function to see if there are pending orders open. If so, we use
the Pending.GetTickets() function to retrieve the current order tickets, and close them using
Trade.Delete(). If no pending orders are open, then a comment will print to the chart.
284
Indicators, Scripts & Libraries
This script will close all orders and positions on the current chart symbol. We can modify the program to
specify the symbol to close positions and orders for. We'll add an input variable named CloseSymbol. If a
value is specified for CloseSymbol, the script will close all orders on the specified symbol. Otherwise, the
script will use the current chart symbol:
#property script_show_inputs
#include <Mql5Book\Trade.mqh>
CTrade Trade;
#include <Mql5Book\Pending.mqh>
CPending Pending;
void OnStart()
{
// Check symbol name
string useSymbol = CloseSymbol;
if(CloseSymbol == "") useSymbol = _Symbol;
// Close orders
for(int i = 0; i < numTickets; i++)
{
Trade.Delete(tickets[i]);
}
285
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
if(Pending.TotalPending(useSymbol) == 0)
{
Comment("All pending orders closed on "+useSymbol);
}
}
}
We use the script_show_inputs property directive to show the input window to the user before script
execution. The user will input a symbol name for CloseSymbol and execute the script. If no symbol name has
been specified, the program will assign the current chart symbol to the useSymbol variable. The useSymbol
variable is used throughout the script to indicate the symbol to use.
You can view the source code of the Close All Orders.mq5 script in the \MQL5\Scripts folder.
Libraries
A library is an executable file that contains reusable functions for use by other programs. It is similar to an
include file, but with several important differences. Unlike an include file, a library does not have classes or
variables that can be used by other programs. You can define classes, structures, enumerations and the like in
your library, but they will not be usable outside of the library.
You can use native Windows DLLs or other DLLs created in C++ in your MQL5 programs. The process of
importing DLLs functions is similar to importing functions from an MQL5 library. Functions contained within
libraries have limitations as to the types of parameters that can be passed to them. Pointers and objects that
contain dynamic arrays cannot be passed to a library function. If you are importing functions from a DLL, you
cannot pass string or dynamic arrays to those functions.
The advantage of a library is that you can distribute it without making the source code available. If you use a
library in numerous expert advisors, you can make minor changes to the library without having to recompile
every program that depends on it (as long as you don't change the function parameters, that is).
You can create a blank library file using the MQL5 Wizard. Libraries are saved in the \MQL5\Libraries folder.
A library must have the library property directive at the top of the file. If it is not present, the file will not
compile. Let's create a sample library with two exportable functions. The functions that we wish to export will
have the export modifier after the function parameters:
#property library
#include <Mql5Book\Indicators.mqh>
CiRSI RSI;
286
Indicators, Scripts & Libraries
This file is named SignalLibrary.mq5, and is located in the \MQL5\Libraries folder. The #property
library directive indicates that this is a library file. This library contains two functions that will be used to
return trade signals to the calling program. These functions use simple RSI overbought and oversold trade
signals, but you could create a library with more elaborate trade signals that you can keep hidden from the
expert advisors and programmers that use them.
We include the \MQL5\Include\Mql5Book\Indicators.mqh file and declare an object based on the CiRSI
class. Note that this object is not visible outside of the library. The BuySignal() and SellSignal() functions
take parameters that set the parameters for the RSI indicator. Note the export modifier after the closing
parenthesis. Any function that will be imported into another program must have the export modifier!
This file will be compiled just like any other MQL5 program. To use our library functions in another program,
we need to import them into that program. We do this using the #import directive. Here is how we would
import these functions into an expert advisor program:
#import "SignalLibrary.ex5"
bool BuySignal(string pSymbol, ENUM_TIMEFRAMES pTimeframe, int pPeriod,
ENUM_APPLIED_PRICE pPrice);
bool SellSignal(string pSymbol, ENUM_TIMEFRAMES pTimeframe, int pPeriod,
ENUM_APPLIED_PRICE pPrice);
#import
The library name is contained in double quotes after the opening #import directive. Note the .ex5 in the
library name indicating that this is a compiled executable file. You cannot import a source code file. All
libraries must be located in the \MQL5\Libraries folder. Following the opening #import directive are the
287
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
functions that we are importing from our library. A closing #import directive must be present after the last
imported function.
Our imported functions are used just like any other functions. Here's an example of how we could use the
BuySignal() function in an expert advisor:
The example above calls the BuySignal() function, and calculates the RSI value for a 14 period RSI using the
close price for the current chart symbol and period. If the RSI is currently oversold, the function returns true.
If an imported function from a library has the same name as a function in your program or a predefined MQL5
function, the scope resolution operator (::) must be used to identify the correct function. For example, let's
assume that our program already has a function named BuySignal(). If we import the BuySignal() function
from our library file, we'll need to preface it with the library name when we call it:
SignalLibrary::BuySignal(_Symbol,_Period,10,PRICE_CLOSE)
288
Debugging and Testing
Errors
There are three types of errors related to MQL5 programs. Compilation errors occur in MetaEditor when invalid
source code is compiled. Runtime errors are logic or software errors that occur when a program is executed in
MetaTrader. Trade server errors occur when a trade request is unsuccessful.
Compilation Errors
It is common to mistype a function call, omit a semicolon or closing bracket, or make a syntax error when
coding. When you compile your program, a list of errors will appear in the Errors tab in MetaEditor. The first
time this happens, it may appear daunting. But don't worry – we're going to address some of the most
common syntax errors that occur.
The first thing to remember when confronted with a list of compilation errors is to always start with the first
error in the list. More often than not, it is a single syntax error that results in a whole list of errors. Correct the
first error, and the remaining errors will disappear.
Fig. 22.1 – The Errors tab under the Toolbox window in MetaEditor. Note that all
of these errors are due to a single missing right parenthesis.
Double-clicking the error in the Errors tab will take you to the spot in your program where the error was
triggered. More than likely, the error will be right under your cursor, or on the previous line. A missing
289
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
semicolon, parentheses or bracket may result in misleading compilation errors, so be sure to check the
previous lines(s) for these when faced with an error on a line that otherwise looks correct.
Here's a list of the most common syntax errors in MetaEditor, and the most common reasons for these errors:
• Unexpected token – A right parenthesis is missing in the previous line, or an extra left parenthesis is
present in the current line.
• Unbalanced right parenthesis – An extra right parenthesis is present or a left parenthesis is missing
in the current line.
• Expressions are not allowed on a global scope – A left bracket in a compound operator may be
missing.
• Wrong parameters count – Too many or not enough parameters in a function call.
• Undeclared identifier – A variable or object name is used without being declared first. Check the
spelling and case of the variable name, and declare it if necessary.
• Unexpected end of program – A closing bracket is missing in your program. This is a tricky one,
since the error refers to the end of the program. Examine the code that you edited recently and look
for a missing closing bracket.
Runtime Errors
A runtime error is an error that occurs during the execution of a program. Runtime errors are logic errors – in
other words, the program will compile, but is not operating as expected. An error message will print to the log
when a runtime error occurs. The error message will indicate the cause of the error, as well as the line on
which it occurred in your source code.
You can use the GetLastError() function to retrieve the error code of the last runtime error, in case you want
to add error handling for runtime errors to your program. After accessing the last error code, use the
ResetLastError() function to reset the error code to zero.
Programs will continue to run after a runtime error occurs, but there are a few critical errors that will end
execution of a program immediately. A divide by zero error is where a division operation uses a divisor of zero.
A array out of range error occurs when the program attempts to access an array element that doesn't exist.
This usually occurs by trying to access an array element larger than the size of the array. Attempting to access
an invalid pointer will also cause a critical error.
290
Debugging and Testing
A complete list of runtime errors can be found in the MQL5 Reference under Standard Constants... > Codes of
Errors and Warnings > Runtime Errors.
Trade server errors are returned by the ObjectSend() function when attempting to perform a trade action.
The retcode variable of a MqlTradeResult object passed to the ObjectSend() function contains the return
code from the server. We've already discussed return codes on page 86, and our order placement and
modification functions contain code to handle trade server errors. If you need to write a class or function that
uses the OrderSend() function, be sure to add code that will handle trade server errors.
Debugging
New in MetaTrader 5 is a debugger that can be used to execute your programs interactively. By clicking the
Start debugging button on the MetaEditor toolbar, your program will be opened on a chart in MetaTrader and
tested in real time using live data. You can stop or pause the debugging process by clicking the Stop or Pause
buttons:
Fig. 22.2 – Debugging buttons. From left to right are the Start, Pause and Stop debugging buttons.
The debugging process is entered when a breakpoint is reached. A breakpoint can be defined in MetaEditor by
pressing the F9 key to toggle a breakpoint on the current line. You can also use the DebugBreak() function to
define a breakpoint. When a breakpoint is reached during program execution, the program pauses and
control is turned over to the programmer. Using the Step Into, Step Over and Step Out buttons on the toolbar,
the programmer can observe the program execution line by line.
The Step Into button moves execution to the next line of the program. Step Over will skip over any functions
that are encountered during the execution, and Step Out exits the current function and returns control to the
function that called it (or exits the current event).
291
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
Fig. 22.4 – Step buttons. From left to right are the Step Into, Step Over and Step Out buttons.
You can monitor the value of a variable during debugging by using the Watch window inside the Debug tab in
the MetaEditor Toolbox window. The Watch window displays the current value of watched variables and
expressions. To add a variable or expression to the watch window, place your mouse cursor over the variable
name, or click-and-drag to select the expression you wish to add to the watch window. Right-click and select
Add Watch from the popup menu, or press Shift+F9 on your keyboard.
Debugging in MetaEditor is done in real time on live data, so you may need to modify your program to
produce the result you are looking for immediately, especially if you are debugging a trading signal or trade
operation. If you need to debug a program quickly, try logging and testing your program in the Strategy
Tester.
Logging
Sometimes errors may occur during live or demo trading when a debugger is not available. Or you may need
to test multiple trading scenarios at once in the Strategy Tester. You can use the Print() function to log
information about your program's state to MetaTrader's log files. The Alert() function automatically prints
the alert string to the log, so any alerts will be displayed in the log as well.
We have already added Print() functions throughout our trading classes and functions. These will print the
results of trade operations to the log, as well as the values of relevant variables that are in use when an error
condition occurs. This example is from the CTrade::OpenPosition() function in Trade.mqh:
292
Debugging and Testing
Open Buy order #40: 10009 - Request is completed, Volume: 0.2, Price: 1.29693, Bid: 1.29678,
Ask: 1.29693
You should always build this kind of logging functionality into your programs, especially when performing
trade operations or testing new code.
Debugging with Print() functions, while less interactive than using the debugger, allows the programmer to
examine the output of many trades at once. You can view the Strategy Tester log under the Journal tab in the
Strategy Tester window. Right-click in the Journal window and select Open from the popup menu to open the
logs folder and view the files in a text editor. Or you can use the Journal Viewer (select Viewer from the right-
click menu) to filter and view the logs by date.
When trading with an expert advisor on a live chart, MetaTrader uses two different log folders for output. The
terminal logs, located in the \Logs folder, displays basic trade and terminal information. You can view this log
under the Journal tab in the Toolbox window. The experts log, located in \Experts\Logs, contains the output
of Print() and Alert() functions, as well as detailed trade information. You can view the experts log under
the Experts tab in the Toolbox window. You can open the log folders by right-clicking inside the Journal or
Experts tab and selecting Open from the popup menu, or view them in the Journal Viewer by selecting Viewer
from the popup menu.
Fig. 22.7 – The Experts tab in the toolbox window. All Print() and Alert() output is viewable here.
293
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
forward testing. To access the Strategy Tester, open the Strategy Tester window in MetaTrader from the
Standard toolbar, or press Ctrl+R on your keyboard.
The Settings tab in the Strategy Tester window is where you'll enter the settings for testing. Most of the
settings are self-explanatory, and should be familiar to you if you've used the MetaTrader 4 strategy tester.
There are a few new settings worth mentioning. The Execution drop-down box allows you to incorporate a
random delay when testing order operations, which mimic the natural delays in live trading.
To the right of the Execution drop-down box is the tick generation mode. Every tick attempts to model every
incoming tick from the server. It is the slowest mode, but the most accurate. 1 minute OHLC uses the open,
high low and close of each M1 bar in the testing period, providing a rough approximation of intrabar price
changes. It is quicker than Every tick, but less accurate. Open prices only uses only the OHLC of each bar of the
selected chart period. This is the quickest mode, but the least accurate. This is useful if you wish to quickly test
an expert advisor that only opens orders at the open of a new bar.
If you check the Visualization checkbox, a separate visualization window will appear when you start the
testing. The Visualization window shows the testing tick-by-tick. All indicators are drawn, and you can see
orders opening and closing on the chart. Fig. 22.9 shows the Visualization window. The speed of the
visualization is also adjustable.
The remaining settings in the Settings tab are for optimization. We'll discuss optimization shortly.
294
Debugging and Testing
The Inputs tab is used to adjust the input parameters of the expert advisor. The Value column is used to set
the values for the current test. The Start, Step and Stop columns are for optimization, and will be addressed
shortly. You can save the current settings to a file, load settings from a file, or set all parameters to their
defaults using the right-click menu.
Once your testing settings and input settings are configured, press the Start button in the Settings tab. After
the testing has completed, a chart will open and the results will appear under the Results tab. The Results tab
displays a testing report showing the profit, drawdown, profit factor and other statistical measures of
performance. If you scroll down in the Results tab, there are additional trade statistics and graphs.
295
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
To view the trade details, right-click in the Results tab. You can select Deals, Orders or Orders & Deals from the
popup menu. The Deals report is the most useful, as these reflect the actual trade results. Note the Direction
column in the Deals report. A direction of in means that a deal is adding to the current position. A direction of
out means that the deal is closing out part or all of the position. An in/out direction refers to a reversal in
position.
Fig 22.12 – The Deals report inside the Results tab of the Strategy Tester.
Note that the reports do not list the stop loss or take profit placed on a position! If you need this information,
check the log under the Journal tab and search for the order/deal number. If a position closes out at a stop
loss or take profit, that will be reflected in the Deals report with an entry in the Comment column, a red or
green background in the Price column, and a Direction of out. Note that when a position is closed out, it is
listed as a separate deal. You can save the contents of the Results tab to a report file by right-clicking inside
the Results tab and selecting Open XML or HTML from the Report submenu.
The Graph tab shows a graph of profitability over time. The Agents tab allows you to manage multi-core
processors for testing, as well as utilize remote computers and the MQL5 Cloud Network for running resource-
296
Debugging and Testing
intensive optimizations. Finally, the Journal tab shows the testing log, which is located in the \Tester\Logs
folder. The results of your testing, including all log entries and errors, will appear in this log.
Optimization
Running an optimization on an expert advisor involves selecting the input parameters to optimize and testing
every possible combination of parameter sets to determine which parameters are most profitable. This is
usually followed by a forward test that tests the optimization results on out-of-sample data. Optimization and
forward testing is the process by which you will evaluate the profitability of your expert advisors.
Let's start under the Inputs tab. The Start, Step and Stop columns are used to set the optimization parameters.
To optimize a parameter, select the checkbox to the left of the parameter name in the Variable column. The
Start value is the starting value for the parameter. The Step value increments the parameter by the specified
amount, and the Stop value is the ending value for the parameter. For example, if we have a Start value of 10, a
Step value of 5, and a Stop value of 50, then the parameter will be optimized starting at 10, 15, 20... all the way
up to 50 for a total of 10 steps.
Select each parameter that you wish to optimize, and set the Start, Step and Stop values for each of them. You
may want to limit the number of parameters to test, as well as the step value for each parameter. The more
parameters/steps there are to test, the longer the optimization will take.
Fig 22.13 – The Inputs tab in the Strategy Tester, using the Start, Step and Stop columns for optimization.
The optimization settings are at the bottom of the Settings tab. If an optimization is not being performed, the
Optimization drop-down will be set to Disabled. If you wish to perform an optimization, select one of the
other options. Slow complete algorithm tests every possible combination of parameters, and can take a very
long time if there are a lot of parameters to optimize for. Fast genetic based algorithm uses a genetic algorithm
to narrow down the range of parameters to test. The results are comparable to using the complete algorithm,
and should be your default choice. All symbols selected in MarketWatch will test all symbols in the Market
297
EXPERT ADVISOR PROGRAMMING FOR METATRADER 5
Watch window. You can add and remove symbols from the market watch window using the right-click menu
inside the Market Watch window.
Fig. 22.14 – The optimization settings in the Settings tab of the Strategy Tester.
The drop-down box to the left of Optimization is the optimization criteria for the genetic algorithm. The
default is to sort by max balance, although you can sort by max balance combined with other criteria, such as
profit factor, drawdown or expected payoff.
The Forward drop-down selects the forward testing period. If Forward is set to something other than No, then
the selected fraction of the optimization period will be set aside for forward testing. You can select your own
start date for the forward testing period by selecting Custom and then setting the start date in the date entry
box to the right. If forward testing is enabled, then each of the optimization results will be tested individually
in the forward testing period.
You can view the results of the optimization in the Optimization Results tab. Compare the optimization results
to the forward testing results to see how well the optimization results hold up. If the forward testing results
are comparable to the optimization results, then your system may perform well in trading. If the two sets of
results are not comparable, then you may need to adjust your optimization parameters, your trading system
or both.
Fig. 22.15 – The Optimization Results tab in the Strategy Tester, showing both forward and back testing results.
Right-click in the Optimization Results tab to select between Back Test Results and Forward Test Results if
applicable. You can also enable additional columns in the report such as the profit factor, drawdown, expected
payoff and optimization parameters. The optimization results can be saved to an XML file that can be opened
by any spreadsheet program by selecting Export to XML from the right-click menu. The Optimization Graph
tab shows a scatterplot of optimization results. You can select a 2D or 3D graph from the right-click menu, and
set the parameters to use for the X-axis and Y-axis.
298
Debugging and Testing
The Results and Optimization Results tabs present a variety of statistical measures to evaluate the profitability
and stability of your trading system. MetaTrader 5 has added a lot of new statistics to the Strategy Trader
report. In this section, we will examine the most important statistics that appear in the trading and
optimization reports:
• Net Profit – The net profit is calculated as the gross profit minus the gross loss. This is probably the
most important statistic, and should always be considered in relation to other statistics. Obviously, a
higher net profit is better.
• Drawdown – The drawdown is the maximum peak to valley loss during the testing period. The
absolute drawdown is the maximum drawdown of balance or equity below the original starting
balance. The maximal and relative drawdown is the maximum drawdown of equity of balance from the
maximum profit to the maximum loss. Relative drawdown is the most important value. Lower values
are better.
• Profit Factor – The profit factor is a simple ratio of gross profit to gross loss. A system that makes zero
profit has a profit factor of 1. If profit factor is less than 1, then the system has lost money. A higher
profit factor is better.
• Expected Payoff – The expected payoff is the average/profit or loss of a single trade. Higher values
are better.
• Recovery Factor – The recovery factor determines the risk of a trading strategy, and how well it
recovers from a loss. It is a ratio of profit to maximal drawdown. Higher values are better.
• Sharpe Ratio – The Sharpe ratio also determines the risk and stability of a trading system. It uses a
sophisticated algorithm to compare the returns of a system versus a risk-free method of return (such
as a Treasury Bond). Higher values are better.
299
Index
A color type ....................................................................................................... 15
Comment() .................................................................................................. 261
AccountInfoDouble() ............................................................................... 180
Concatenating strings ............................................................................... 14
Addition operation ..................................................................................... 33
const specifier .............................................................................................. 17
AdjustAboveStopLevel() include function ...................................... 122
Constants ........................................................................................................ 17
AdjustBelowStopLevel() include function ....................................... 123
continue operator ....................................................................................... 48
Alert() ................................................................................................... 103, 257
CopyBuffer() ................................................................................................ 197
AND operation ............................................................................................. 36
CopyClose() ................................................................................................. 189
ArrayCopy() ................................................................................................. 147
CopyHigh() .................................................................................................. 189
ArrayFree() ................................................................................................... 146
CopyLow() .................................................................................................... 189
ArrayMaximum() ....................................................................................... 190
CopyRates() ................................................................................................. 185
ArrayMinimum() ........................................................................................ 190
copyright property ..................................................................................... 65
ArrayResize() .................................................................................................. 19
CopyTime() .................................................................................................. 189
Arrays ............................................................................................................... 18
CPending class ........................................................................................... 144
Dynamic ................................................................................................ 19
CreateDateTime() include function ................................................... 224
Multi-Dimensional ........................................................................... 19
CTimer class ................................................................................................ 226
ArraySetAsSeries() .......................................................................... 186, 201
CTrade class ................................................................................................... 97
ArraySize() ...................................................................................................... 21
Ask() include function ............................................................................. 183 D
Assignment operations ............................................................................ 34
DailyTimer() class function ................................................................... 228
B datetime constants ..................................................................................... 16
datetime type ..................................................................................... 16, 220
Bid() include function .............................................................................. 183
DebugBreak() ............................................................................................. 291
BlockTimer() class function ................................................................... 234
default operator .......................................................................................... 43
bool type ........................................................................................................ 14
Delete() class function ............................................................................ 156
Break even stop ......................................................................................... 174
description property .................................................................................. 65
break operator ............................................................................................. 48
Deviation() class function ...................................................................... 109
BreakEven() class function .................................................................... 176
Division operation ...................................................................................... 34
BuyStopLoss() include function .......................................................... 119
do-while operator ....................................................................................... 45
BuyTakeProfit() include function ........................................................ 120
double type ................................................................................................... 13
C E
case operator ................................................................................................ 42
else if operator ............................................................................................. 40
CBars class ................................................................................................... 186
else operator ................................................................................................. 40
char type ......................................................................................................... 12
enum keyword .............................................................................................. 22
CheckAboveStopLevel() include function ...................................... 124
ENUM_CHECK_RETCODE ............................................................. 102, 106
CheckBelowStopLevel() include function ....................................... 124
ENUM_DAY_OF_WEEK ............................................................................ 232
CheckNewBar() class function ............................................................. 218
ENUM_ORDER_TYPE .................................................................................. 81
CheckOrderType() include function .................................................. 104
ENUM_ORDER_TYPE_FILLING ................................................................ 81
CheckReturnCode() include function ..................................... 102, 106
ENUM_ORDER_TYPE_TIME ...................................................................... 82
CheckTimer() class function ................................................................. 226
ENUM_TRADE_REQUEST_ACTIONS .................................................... 80
CiMA class ................................................................................................... 202
Enumerations ................................................................................................ 21
CIndicator class ......................................................................................... 201
Equal to operator ........................................................................................ 35
Close() CBars class function ................................................................. 187
Escape characters ........................................................................................ 13
Close() CTrade class function ............................................................... 134
Event handlers .............................................................................................. 73
CNewBar class ............................................................................................ 217
EventKillTimer() .......................................................................................... 237
EventSetTimer() ......................................................................................... 237 iRSI() ............................................................................................................... 197
Execution types ............................................................................................ 72 iStochastic() ................................................................................................. 199
F L
FileClose() ..................................................................................................... 268 Less than operator ...................................................................................... 35
FileFlush() ..................................................................................................... 268 Less than or equal to operator .............................................................. 35
FileIsEnding() .............................................................................................. 270 Library ........................................................................................................... 286
FileOpen() .................................................................................................... 267 library property ......................................................................................... 286
FileReadBool() ............................................................................................ 269 Limit orders ................................................................................................. 139
FileReadDatetime() ................................................................................. 269 link property .................................................................................................. 65
FileReadNumber() .................................................................................... 269 Local variables .............................................................................................. 27
FileReadString() ......................................................................................... 269 long type ........................................................................................................ 12
FileSeek() ...................................................................................................... 268 LowestLow() include function ............................................................. 191
FileWrite() ..................................................................................................... 267
Fill policy ......................................................................................................... 73 M
FillType() class function .......................................................................... 110 Main() class function ............................................................................... 202
float type ......................................................................................................... 13 MathAbs() .................................................................................................... 181
for operator ................................................................................................... 46 MessageBox() ............................................................................................. 258
Functions ........................................................................................................ 49 ModifyPending() class function .......................................................... 152
Default values .................................................................................... 50 ModifyPosition() class function .......................................................... 131
Overloading ........................................................................................ 54 Modulus operation ..................................................................................... 34
Parameters by reference ................................................................ 53 MoneyManagement() include function .......................................... 179
MQL5 Wizard ...................................................................................... 75, 277
G
MqlDateTime structure .......................................................................... 222
GetEndTime() class function ................................................................ 236 MqlRates structure ................................................................................... 185
GetLastError() ............................................................................................. 290 MqlTick structure ...................................................................................... 184
GetStartTime() class function .............................................................. 236 MqlTradeRequest structure .................................................................... 79
GetTickets() class function .................................................................... 147 MqlTradeResult structure ......................................................................... 85
Global variables (program) ..................................................................... 30 Multiplication operation .......................................................................... 33
Global Variables (terminal) ................................................................... 271
GlobalVariableDel() .................................................................................. 272 N
GlobalVariableGet() .................................................................................. 272 Negation operation ................................................................................... 33
GlobalVariableSet() .................................................................................. 271 NormalizeDouble() ..................................................................................... 35
Greater than operator ............................................................................... 35 Not equal to operator ............................................................................... 35
Greater than or equal to operator ....................................................... 35 NOT operation ............................................................................................. 37
NULL ................................................................................................................. 11
H
HighestHigh() include function .......................................................... 191 O
Object-oriented programming ............................................................. 57
I
Classes ................................................................................................... 58
iCustom() ...................................................................................................... 209 Constructors ....................................................................................... 60
if operator ...................................................................................................... 39 Derived classes ........................................................................ 61, 202
iMA() .............................................................................................................. 196 Objects .................................................................................................. 63
Include file ........................................................................................................ 3 Virtual functions ................................................................................ 62
INDICATOR_CALCULATIONS ................................................................ 208 ObjectCreate() ............................................................................................ 261
IndicatorRelease() ..................................................................................... 202 ObjectDelete() ............................................................................................ 266
Init() class function ................................................................................... 203 ObjectGetValueByTime() ........................................................................ 264
input keyword ............................................................................................... 26 ObjectMove() ............................................................................................. 263
Input variables .............................................................................................. 26 ObjectsDeleteAll() .................................................................................... 266
int type ............................................................................................................ 12 ObjectSetInteger() .................................................................................... 262
OnCalculate() .............................................................................................. 275 SetIndexBuffer() ......................................................................................... 208
OnDeinit() ............................................................................................. 74, 272 short type ....................................................................................................... 12
OnInit() ............................................................................................................ 73 Signal() class function ............................................................................. 205
OnStart() ....................................................................................................... 283 sinput keyword ................................................................................... 26, 233
OnTick() ........................................................................................................... 74 static keyword ............................................................................................... 31
OnTimer() ....................................................................................................... 74 Static variables .............................................................................................. 31
OnTimer() event handler ....................................................................... 237 Stop level ...................................................................................................... 121
OnTrade() ........................................................................................................ 74 Stop limit orders ....................................................................................... 139
OpenPending() class function ................................................... 140, 142 Stop loss ....................................................................................................... 115
OpenPosition() class function ...................................................... 98, 110 Stop orders .................................................................................................. 139
OR operation ...................................................................................... 37, 215 StopPriceToPoints() include function ............................................... 181
OrderCount() class function ................................................................. 145 string type ...................................................................................................... 13
OrderGetDouble() .................................................................................... 149 StringToTime() ............................................................................................ 221
OrderGetInteger() ..................................................................................... 149 struct keyword .............................................................................................. 24
OrderGetString() ....................................................................................... 149 StructToTime() ............................................................................................ 222
OrderGetTicket() ....................................................................................... 144 Structures ....................................................................................................... 23
OrderSend() ................................................................................................... 79 Subtraction operation ............................................................................... 33
OrdersTotal() ............................................................................................... 144 switch operator ............................................................................................ 42
SymbolInfoDouble() ........................................................................................
P SYMBOL_ASK ................................................................................... 101
PlaySound() ................................................................................................. 260 SYMBOL_BID .................................................................................... 101
PlotIndexSetString() ................................................................................. 208 SYMBOL_POINT .............................................................................. 119
PositionGetDouble() ................................................................................ 129 SYMBOL_TRADE_TICK_VALUE ................................................... 180
PositionGetInteger() ................................................................................ 129 SYMBOL_VOLUME_MAX ............................................................. 177
PositionGetString() ................................................................................... 129 SYMBOL_VOLUME_MIN .............................................................. 177
PositionOpenPrice() include function .................................... 130, 131 SYMBOL_VOLUME_STEP ............................................................. 177
PositionSelect() .......................................................................................... 100 SymbolInfoInteger() ........................................................................................
PositionType() include function .......................................................... 129 SYMBOL_DIGITS ............................................................................. 120
Preprocessor directives ............................................................................. 65 SYMBOL_TRADE_STOPS_LEVEL ................................................ 121
Preset file (.set) ............................................................................................... 4 SymbolInfoTick() ....................................................................................... 184
prev_calculated .......................................................................................... 281
T
PrintTimerMessage() class function .................................................. 230
private access keyword ............................................................................. 58 Take profit .................................................................................................... 118
protected access keyword ....................................................................... 59 TerminalClose() .......................................................................................... 273
public access keyword .............................................................................. 58 Ternary operator .......................................................................................... 41
TimeCurrent() ............................................................................................. 227
R TimeLocal() .................................................................................................. 227
rates_total .................................................................................................... 281 TimerBlock structure ............................................................................... 232
Release() class function .......................................................................... 202 TimeToString() ............................................................................................ 221
ResetLastError() ......................................................................................... 290 TimeToStruct() ............................................................................................ 222
Return codes ................................................................................................. 86 TRADE_ACTION_DEAL ............................................................................... 82
return operator ............................................................................................ 52 TRADE_ACTION_MODIFY ........................................................................ 84
TRADE_ACTION_PENDING ...................................................................... 84
S TRADE_ACTION_REMOVE ....................................................................... 85
TRADE_ACTION_SLTP ................................................................................ 83
script_show_confirm property ............................................................ 283
TradeServerReturnCodeDescription() .............................................. 103
script_show_inputs property ...................................................... 283, 286
Trailing stop ................................................................................................ 165
SellStopLoss() include function .......................................................... 121
Dynamic ............................................................................................. 171
SellTakeProfit() include function ......................................................... 120
Minimum profit ............................................................................... 167
SendMail() .................................................................................................... 259
Step ...................................................................................................... 168
SendNotification() .................................................................................... 260
TrailingStop() class function ................................................................. 170 while operator .............................................................................................. 44
Typecasting .................................................................................................... 25 WRONG_VALUE ......................................................................................... 100
Explicit typecasting .......................................................................... 25
Z
U
ZeroMemory() .............................................................................................. 92
uchar type ...................................................................................................... 12
uint type .......................................................................................................... 12 _
ulong type ...................................................................................................... 12 _Digits .............................................................................................................. 32
Update() class function .......................................................................... 187 _Period ............................................................................................................. 32
ushort type .................................................................................................... 12 _Point ................................................................................................................ 32
_Symbol ........................................................................................................... 32
V
Variable scope .............................................................................................. 28 #
VerifyVolume() include function ......................................................... 178 #define directive .................................................................................. 17, 66
version property .......................................................................................... 65 #import directive ...................................................................................... 287
virtual keyword ................................................................................... 62, 202 #include directive ................................................................................ 67, 97
void type ......................................................................................................... 52 #property directive ........................................................................... 65, 283