Custom Indicators in MQL5 For Newbies
Custom Indicators in MQL5 For Newbies
Custom Indicators in MQL5 For Newbies
METATRADER 5 — EXAMPLES
9 20 851
NIKOLAY KOSITSIN
Introduction
The basis of deep understanding of any intellectual subject (no matter, mathematics, music or programming) is the study of its fundamentals. It's great when a similar study is started in a fairly
young age, so the understanding of fundamentals is much easier, and knowledge is specific and comprehensive.
Unfortunately, the most of people begin to study financial and stock markets in a middle age, so the study isn't easy. In this article I'll try to help to overcome this initial barrier in understanding
of MQL5 and in writing custom indicators for the MetaTrader 5 client terminal.
As example, let's consider the most famous indicator of technical analysis - Simple Moving Average (SMA). Its calculation is simple:
SMA = SUM (CLOSE (i), MAPeriod) / MAPeriod
where:
//+------------------------------------------------------------------+
//| SMA.mq5 |
//| Copyright 2009, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 Red
double ExtLineBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
void OnInit()
{
SetIndexBuffer(0, ExtLineBuffer, INDICATOR_DATA);
PlotIndexSetInteger(0, PLOT_SHIFT, MAShift);
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, MAPeriod - 1);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
if (rates_total < MAPeriod - 1)
return(0);
if (prev_calculated == 0)
first = MAPeriod - 1 + begin;
else first = prev_calculated - 1;
ExtLineBuffer[bar] = SMA;
}
return(rates_total);
}
//+------------------------------------------------------------------+
First we need to consider two things - the purpose of each string of the code on the one hand, and interaction between this program code and the client terminal on the other hand.
Using Comments
At first glance at the indicator code, the eye catches objects like these:
//+------------------------------------------------------------------+
//| SMA.mq5 |
//| Copyright 2009, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
This website uses cookies. Learn more about our Cookies Policy.
https://www.mql5.com/en/articles/37 1/4
5/1/2020 Custom Indicators in MQL5 for Newbies - MQL5 Articles
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
It's necessary to note that they aren't related directly with the code, they are just comments, they are designed for code readability and shows a certain semantic content of some parts of this
code. Of course, they could be removed from the code without any damage for its further simplification, but the code then would lose its laconic brevity in understanding. In our case, we deal
with single-line comments, which always start from a pair of characters "//" and end with a newline character.
It's clear that in comments the author can write everything necessary that will help to understand this code after some time. In our case, in the first part of the commented strings, there is a
name of the indicator and information about its author, the second and third parts of comments are splitting the functions OnInit() and OnCalculate(). The last line in the end simply closes the
program code.
1. The code, that is written without brackets on the global level, it is located between the first two comments.
2. Description of the OnInit() function.
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
After attaching this indicator to a chart from the Navigator window, the MetaTrader will execute the code of the first part of the indicator. After that it will call the function OnInit() for a
single execution of this function and further, at each new tick (after the new quote arrival) it will call the OnCalculate() function and consequently execute the code of this function. IF
OnDeInit() were present in the indicator, MetaTrader would call this function once after detaching the indicator from the chart or after the timeframe changing.
The meaning and purpose of all parts of the indicator are clear after this explanation. In the first part of the code on the global level there are some simple operators that are executed once
after the start of the indicator. In addition there is a declaration of variables, that are "visible" in all blocks of the indicator and that remember their values while the indicator is on the chart.
The constants and functions that are executed once should be located inside the OnInit() function, because it would be inexpedient to place them in block of the function OnCalculate(). The
code for the calculation of the indicator, that allows to calculate its values for each bar, should be placed in function OnCalculate().
The procedures that delete the useless garbage (if there is any) from the chart after the indicator is removed from it, should be placed inside of OnDeInit(). For example, it's necessary for the
deletion of the graphic objects, created by the indicator.
After these explanations, we are ready to examine in details the code of the indicator, that has been considered above.
Note that there are no semicolons (";") at the ends of the lines. The reason is the following: in fact, in our case it's definition of constants, but presented in another way.
Our simple moving average has only 2 parameters, that can be changed by a user - it's an averaging period and horizontal shift (in bars) of the indicator along the time axes. These two
parameters should be declared as input variables of the indicator, as it has been declared in two further code lines:
Note that after the declaration of these input parameters there are comments, and these comments will be visible as names of input parameters in the "Properties" window of the indicator:
In our case, these names are much clearer, than the variable names of the indicator. So, these comments should be simple.
And the last code line that hasn't any brackets is the declaration of the dynamic array ExtLineBuffer[].
First of all, this array should be converted into the indicator buffer, it's implemented in the block of the OnInit() function. Second, the indicator buffer itself will be used inside the
OnCalculate() function. Third, this array will store the values of the indicator, that will be plotted as a curve on the chart. Because of the fact, that it has been declared as a global variable, it's
available for all blocks of the indicator, and it stores its values all the time until the indicator is detached from the chart.
The content of the OnInit() function is presented by just 3 operators, they are built-in functions of the MetaTrader client terminal.
The call of the first function assigns the zeroth indicator buffer with one dimensional dynamic array ExtLineBuffer[]. Two calls of another function with different values of input parameters
allow to shift the indicator along the price axis and allows to specify its plotting from the bar with number MAPeriod.
void OnInit()
{
//----+
//---- assign the dynamic array ExtLineBuffer with 0th indicator's buffer
SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
//---- set plot shift along the horizontal axis by MAShift bars
PlotIndexSetInteger(0,PLOT_SHIFT,MAShift);
//---- set plot begin from the bar with number MAPeriod
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,MAPeriod);
//----+
} This website uses cookies. Learn more about our Cookies Policy.
https://www.mql5.com/en/articles/37 2/4
5/1/2020 Custom Indicators in MQL5 for Newbies - MQL5 Articles
The last call of the PlotIndexSetInteger() function passes the value equal to MAPeriod (via the parameter begin of the function OnCalculate()) to another indicator, if it is applied to the values
of our indicator. The logic is simple, there is nothing to average in the first MaPeriod-1 bars, that's why the plotting of this indicator is useless. However, this value should be passed to shift the
origin of the calculations of another indicator.
It isn't a full list of built-in functions, that are used in custom indicators and can be located in this block of the indicator. See MQL5 documentation for the details.
Finally, let's consider the code of the OnCalculate() function. There isn't any custom calls in this function like the function OnInit() because these functions are called by the MetaTrader client
terminal. Because of this reason, the input parameters of the function are declared as constants.
int OnCalculate(
const int rates_total, // number of available bars in history at the current tick
const int prev_calculated,// number of bars, calculated at previous tick
const int begin, // index of the first bar
const double &price[] // price array for the calculation
)
These input parameters cannot be changed, their values are passed by the client terminal for the further use in the code of this function. The input variables of OnCalculate are described in
documentation of MQL5. The function OnCalculate() returns its values for the client terminal using the return(rates_total) function. The client terminal receives this value of the current tick
after the execution of OnCalculate() and passes the returned value to another parameter prev_calculated. So, it's always possible to determine the range of bar indexes and perform the
calculations at once only for new values of the indicator, that have appeared after the previous tick.
It's necessary to note that the bars ordering in MetaTrader client terminal is performed from left to right, so the very old bar (the left), presented at chart has index 0, the next has index 1, etc.
The elements of the buffer ExtLineBuffer[] have the same ordering.
The simple code structure inside the function OnCalculate of our indicator is universal and typical for many technical analysis indicators. So, let's consider it in details. The liogic of
OnCalcualte() function is:
1. Check for the presence of bars, necessary for the calculations.
2. Declaration of local variables.
3. Get the index of starting bar for the calculation.
4. The main loop of the calculation of the indicator
5. Return the value of rates_total to the client terminal by using the operator return().
I think that the first term is clear. For example, if the averaging period of Moving Average is equal to 200, but the client terminal has only 100 bars, it isn't necessary to perform calculation
because there are no bars, sufficient for the calculation. So we have to return 0 to the client terminal using the operator return.
//---- check for the presence of bars, sufficient for the calculation
if(rates_total<MAPeriod-1+begin)
return(0);
Our indicator can be applied to the data of some other indicator, that also can have some minimal number of bars for the calculation. The use of the constant begin is necessary to take into
account this fact. See the article Applying One Indicator to Another for the details.
The local variables, declared in this block are necessary only for the intermediate calculations inside the OnCalculate() function. These variables are released from the computer RAM after the
call of the function.
It's necessary to be careful with the start index of the main loop (variable first). At the first call of the function (we can determine it by value of parameter prev_calculated) we have to
perform the calculation of indicator values for all the bars. For all further ticks of the client terminal we have to perform the calculation only for the new bars appeared. It is done by 3 code
lines:
The range of the variable changes in the main loop operator of indicator re-calculation has been already discussed.
The bar processing in the main loop is performed in increasing order (bar++), in other words, from left to right, as a natural and right way. In our indicator it could be implemented in another
way (in reverse order). It's better to use the increasing order in indicators. The variable of the main loop is named as "bar", but many programmers prefer to use the name "i". I prefer to use the
"bar", because it makes code clearer and readable.
The averaging algorithm, that has been implemented in the main loop, is simple.
{
Sum=0.0;
//---- summation loop for the current bar averaging
for(iii=0;iii<MAPeriod;iii++)
Sum+=price[bar-iii]; // Sum = Sum + price[bar - iii]; // eqaual to
//---- set the element of the indicator buffer with the value of SMA we have calculated
ExtLineBuffer[bar]=SMA;
}
In the second loop, we are performing the cumulative summation of the prices from the previous bars of the period and dividing it by this averaging period. As a result we have the final value of
SMA.
After the main loop is finished, the OnCalculate function returns the number of available bars from the variable rates_total. In the next call of the OnCalculate() function, this value will be
passed by the client terminal to the variable prev_calculated. This value, decreased by 1, will be used as a start index for the main loop.
Here is the full source code of the indicator with detailed comments for each code line:
//+------------------------------------------------------------------+
//| SMA.mq5 |
//| Copyright 2009, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
//---- the indicator will be plotted in the main window
#property indicator_chart_window
//---- one buffer will be used for the calculations and plot of the indicator
#property indicator_buffers 1
//---- only one graphic plot is used
#property indicator_plots 1
//---- the indicator should be plotted as a line
#property indicator_type1 DRAW_LINE
//---- the color of the indicator's line is red
#property indicator_color1 Red
https://www.mql5.com/en/articles/37 3/4
5/1/2020 Custom Indicators in MQL5 for Newbies - MQL5 Articles
//----+
//---- check for the presence of bars, sufficient for the calculation
if (rates_total < MAPeriod - 1 + begin)
return(0);
//---- set the element of the indicator buffer with the value of SMA we have calculated
ExtLineBuffer[bar]=SMA;
}
//----+
return(rates_total);
}
//+------------------------------------------------------------------+
I would like to outline another feature that can be used to simplify the understanding of code. You can use spaces and empty lines to make it clear.
Conclusion
That's all about the interaction between the code of the custom indicator and the MetaTrader client terminal. Of course, the subject is much wider than what we have considered, the aim of
this article is to help newbies to understand the fundamentals, so see documentation for the details.
Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/37
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This website uses cookies. Learn more about our Cookies Policy.
https://www.mql5.com/en/articles/37 4/4