### Introduction

What is an indicator? It is a set of calculated values that we want to be displayed on the screen in a convenient way. Sets of values are represented in programs as arrays. Thus, creation of an indicator means writing an algorithm that handles some arrays (price arrays) and records results of handling to other arrays (indicator values).

Despite the fact that there are a lot of ready indicators, which have become classics already, the necessity to create one's own indicators will always exist. Such indicators that we create using our own algorithms are called custom indicators. In this article we will discuss how to create a simple custom indicator.

### Indicators Are Different

An indicator can be presented as colored lines or areas, or it can be displayed as special labels pointing at favorable moments for position entering. Also these types can be combined, which gives even more indicator types. We'll consider creation of an indicator on the example of the well-known True Strength Index developed by William Blau.

### True Strength Index

The TSI indicator is based on the double-smoothed momentum to identify trends, as well as oversold/overbought areas. Mathematical explanation of it can be found in Momentum, Direction, and Divergence by William Blau. Here we include only its calculation formula.

TSI(CLOSE,r,s) =100*EMA(EMA(mtm,r),s) / EMA(EMA(|mtm|,r),s)

where:

- mtm = CLOSEcurrent – CLOSprev, array of values denoting the difference between close prices of the current bar and that of the previous one;

- EMA(mtm,r) = exponential smoothing of mtm values with the period length equal to r;
- EMA(EMA(mtm,r),s) = exponential smoothing of EMA(mtm,r) values with s period;
- |mtm| = absolute values mtm;

- r = 25,

- s = 13.

From this formula, we can extract three parameters that influence the indicator calculation. These are periods r and s, as well as the type of prices used for calculations. In our case we use CLOSE price.

### MQL5 Wizard

Let's display TSI as a blue line - here we need to start MQL5 Wizard. At the first stage we should indicated the type of a program we want to create - custom indicator. At the second stage, let's set the program name, *r* and *s* parameters and their values.

After that let's define that the indicator shall be displayed in a separate window as a blue line and set the TSI label for this line.

All initial data have been entered, so we press Done and get a draft of our indicator.

#property copyright "2009, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_label1 "TSI"
#property indicator_type1 DRAW_LINE
#property indicator_color1 Blue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
input int r=25;
input int s=13;
double TSIBuffer[];
int OnInit()
{
SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA);
return(0);
}
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& tick_volume[],
const long& volume[],
const int& spread[])
{
return(rates_total);
}

MQL5 Wizard creates the indicator header, in which it writes indicator properties, namely:

- indicator is displayed in a separate window;
- number of indicator buffers,
* indicator_buffers*=1;
- number of plottings,
*indicator_plots*= 1;
- name of plotting No 1,
*indicator_label1*="TSI";
- style of the first plotting - line,
*indicator_type1*=DRAW_LINE;
- color of plotting No 1,
*indicator_color1*=Blue;
- style of a line,
*indicator_style1*=STYLE_SOLID;
- line width for plotting 1,
*indicator_width1*=1.

All preparations are ready, now we can refine and improve our code.

### OnCalculate()

The OnCalculate() function is the handler of the Calculate event, that appears when it's necessary to recalculate indicator values and redraw it on the chart. This is the event of a new tick receiving, symbol history update, etc. That's why the main code for all calculations of indicator values must be located exactly in this function.

Of course, auxiliary calculations can be implemented in other separate functions, but those functions must be used in the OnCalculate handler.

By default, MQL5 Wizard creates the second form of OnCalculate(), which provides access to all types of timeseries:

- Open, High, Low, Close prices;
- volumes (real and/or tick);
- spread;
- period opening time.

But in our case we need only one data array, that's why let's change OnCalculate() the first form of calling.

int OnCalculate (const int rates_total,
const int prev_calculated,
const int begin,
const double& price[])
{
return(rates_total);
}

This will enable us to further apply the indicator not only to price data, but also create the indicator based on values of other indicators.

If we select **Close **in the Parameters tab (it is offered by default), then *price[]* passed to OnCalculate() will contain close prices. If we select, for example, **Typical Price**, *price[]* will contain prices of (High+Low+Close)/3 for each period.

The *rates_total* parameter denotes the size of the *price[]* array; it will be useful for organizing calculations in a cycle. Indexing of elements in price[] starts from zero and is directed from past to future. I.e. the price[0] element contains the oldest value, while price[rates_total-1] contains the latest array element.

### Organizing Auxiliary Indicator Buffers

Only one line will be shown in a chart, i.e. data of one indicator array. But before that we need to organize intermediate calculations. Intermediate data are stored in indicator arrays that are marked by the INDICATOR_CALCULATIONS attribute. From the formula we see that we need additional arrays:

- for values mtm - array MTMBuffer[];
- for values |mtm| - array AbsMTMBuffer[];
- for EMA(mtm,r) - array EMA_MTMBuffer[];
- for EMA(EMA(mtm,r),s) - array EMA2_MTMBuffer[];
- for EMA(|mtm|,r) - array EMA_AbsMTMBuffer[];
- for EMA(EMA(|mtm|,r),s) - array EMA2_AbsMTMBuffer[].

Totally we need to add 6 more arrays of double type at global level and bind these arrays with the indicator buffers on the OnInit() function. Don't forget to indicate the new number of indicator buffers; the *indicator_buffers* property must be equal to 7 (there was 1, and 6 buffers more were added).

#property indicator_buffers 7

Now the indicator code looks like this:

#property indicator_separate_window
#property indicator_buffers 7
#property indicator_plots 1
#property indicator_label1 "TSI"
#property indicator_type1 DRAW_LINE
#property indicator_color1 Blue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
input int r=25;
input int s=13;
double TSIBuffer[];
double MTMBuffer[];
double AbsMTMBuffer[];
double EMA_MTMBuffer[];
double EMA2_MTMBuffer[];
double EMA_AbsMTMBuffer[];
double EMA2_AbsMTMBuffer[];
int OnInit()
{
SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA);
SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS);
return(0);
}
int OnCalculate (const int rates_total,
const int prev_calculated,
const int begin,
const double& price[])
{
return(rates_total);
}

### Intermediate Calculations

It is very easy to organize calculation of values for buffers MTMBuffer[] and AbsMTMBuffer[]. In the loop, one by one go through values from price[1] to price[rates_total-1] and write difference into one array, and the absolute value of difference into the second one.

for(int i=1;i<rates_total;i++)
{
MTMBuffer[i]=price[i]-price[i-1];
AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
}

The next stage is the calculation of the exponential average of these arrays. There are two ways to do it. In the first one we write the whole algorithm trying to make no mistakes. In the second case we use ready functions that are already debugged and intended exactly for these purposes.

In MQL5 there are no built-in functions for calculating moving averages by array values, but there is a ready library of functions MovingAverages.mqh, the complete path to which is *terminal_directory*/MQL5/Include/MovingAverages.mqh, where the *terminal_directory *is a catalog where the MetaTrader 5 terminal is installed. The library is an include file; it contains functions for calculating moving averages on arrays using one of the four classical methods:

- simple averaging;
- exponential averaging;
- smoothed averaging;
- linear weighted averaging.

In order to use these functions, in any MQL5 program add the following in the code heading:

#include <MovingAverages.mqh>

We need the function ExponentialMAOnBuffer(), which calculates exponential moving average on the array of values and records values of the average into another array.

### The Function of Smoothing of an Array

Totally, the include file MovingAverages.mqh contains eight functions that can be divided into two groups of functions of the same type, each containing 4 of them. The first group contains functions that receive an array and simply return a value of a moving average at a specified position:

- SimpleMA() - for calculating the value of a simple average;

- ExponentialMA() - for calculating the value of an exponential average;
- SmoothedMA() - for calculating the value of a smoothed average;
- LinearWeightedMA() - for calculating the value of a linear-weighted average.

These functions are intended for obtaining the value of an average once for an array, and are not optimized for multiple calls. If you need to use a function from this group in a loop (to calculate values of an average and further write each calculated value into an array), you'll have to organize an optimal algorithm.

The second group of functions is intended for filling out the recipient array by values of a moving average based on the array of initial values:

- SimpleMAOnBuffer() - fills out the output array buffer[] by values of a simple average from the price[] array;
- ExponentialMAOnBuffer() - fills out the output array buffer[] by values of an exponential average from the price[] array;
- SmoothedMAOnBuffer() - fills out the output array buffer[] by values of a smoothed average from the price[] array;

- LinearWeightedMAOnBuffer() - fills out the output array buffer[] by values of a linear weighted average from the price[] array.

All the specified functions, except for arrays buffer[], price[] and the *period* averaging period, get 3 more parameters, the purpose of which is analogous to parameters of the OnCalculate() function - rates_total, prev_calculated and begin. Functions of this group correctly process passed arrays of price[] and buffer[], taking into account the direction of indexing (AS_SERIES flag).

The *begin* parameter indicates the index of a source array, from which meaningful data start, i.e. data that need to be handled. For the MTMBuffer[] array real data start with the index 1, because MTMBuffer[1]=price[1]-price[0]. The value of MTMBuffer[0] is undefined, that's why begin=1.

//--- calculate the first moving
ExponentialMAOnBuffer(rates_total,prev_calculated,
1, // index, starting from which data for smoothing are available
r, // period of the exponential average
MTMBuffer, // buffer to calculate average
EMA_MTMBuffer); // into this buffer locate value of the average
ExponentialMAOnBuffer(rates_total,prev_calculated,
1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);

When averaging, the period value should be taken into account, because in the output array the calculated values are filled out with a small delay, which is larger at larger averaging periods. For example, if period=10, values in the resulting array will start with begin+period-1=begin+10-1. At further calls of buffer[] it should be taken into account, and handling should be started with the index begin+period-1.

Thus we can easily obtain the second exponential average from the arrays of MTMBuffer[] and AbsMTMBuffer:

//--- calculate the second moving average on arrays
ExponentialMAOnBuffer(rates_total,prev_calculated,
r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
ExponentialMAOnBuffer(rates_total,prev_calculated,
r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);

The value of *begin* is now equal to r, because begin=1+r-1 (r is the period of the primary exponential averaging, handling starts with the index 1). In output arrays of EMA2_MTMBuffer[] and EMA2_AbsMTMBuffer[], calculated values start with the index r+s-1, because we started to handle input arrays with the index r, and the period for the second exponential averaging is equal to s.

All the pre-calculations are ready, now we can calculate values of the indicator buffer TSIBuffer[], which will be plotted in the chart.

//--- now calculate values of the indicator
for(int i=r+s-1;i<rates_total;i++)
{
TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
}

Compile the code by pressing the

**F5** key and start it in the MetaTrader 5 terminal. It works!

Still there are some questions left.

### Optimizing Calculations

Actually, it's not enough just to write a working indicator. If we carefully look at the current implementation of OnCalculate(), we'll see that it's not optimal.

int OnCalculate (const int rates_total,
const int prev_calculated,
const int begin,
const double &price[])
{
MTMBuffer[0]=0.0;
AbsMTMBuffer[0]=0.0;
for(int i=1;i<rates_total;i++)
{
MTMBuffer[i]=price[i]-price[i-1];
AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
}
ExponentialMAOnBuffer(rates_total,prev_calculated,
1,
r,
MTMBuffer,
EMA_MTMBuffer);
ExponentialMAOnBuffer(rates_total,prev_calculated,
1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);
ExponentialMAOnBuffer(rates_total,prev_calculated,
r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
ExponentialMAOnBuffer(rates_total,prev_calculated,
r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);
for(int i=r+s-1;i<rates_total;i++)
{
TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
}
return(rates_total);
}

At each function start we calculate values in arrays of MTMBuffer[] and AbsMTMBuffer[]. In this case, if the size of price[] equals to hundreds of thousands or even millions, unnecessary repeated calculations can take all CPU resources, no matter how powerful it is.

For organizing optimal calculations, we use the *prev_calculated* input parameter, which is equal to the value returned by OnCalculate() at the previous call. In the first call of the function, the value of prev_calculated is always equal to 0. In this case we calculate all values in the indicator buffer. During the next call, we won't have to calculate the whole buffer - only the last value will be calculated. Let's write it down like this:

if(prev_calculated==0)
{
MTMBuffer[0]=0.0;
AbsMTMBuffer[0]=0.0;
}
int start;
if(prev_calculated==0) start=1;
else start=prev_calculated-1;
for(int i=start;i<rates_total;i++)
{
MTMBuffer[i]=price[i]-price[i-1];
AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
}

Calculation blocks of EMA_MTMBuffer[], EMA_AbsMTMBuffer[], EMA2_MTMBuffer[] and EMA2_AbsMTMBuffer[] do not require optimization of calculations, because ExponentialMAOnBuffer() is already written in the optimal way. We need to optimize only calculation of values for the TSIBuffer[] array. We use the same method as the one, used for MTMBuffer[].

if(prev_calculated==0) start=r+s-1;
for(int i=start;i<rates_total;i++)
{
TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
}
return(rates_total);

The last remark for the optimization procedure: OnCalculate() returns the value of *rates_total*. This means the number of elements in the price[] input array, which is used for indicator calculations.

**Value returned by OnCalculate()** is saved in the terminal memory, and at the next call of OnCalculate() **it is passed to the function as the value of the input parameter prev_calculated**.

This allows to always know the size of the input array at the previous call of OnCalculate() and start the calculation of indicator buffers from a correct index without unnecessary recalculations.

### Checking Input Data

There is one more thing we need to do for OnCalculate() to operate perfectly. Let's add checking of the price[] array, on which indicator values are calculated. If the size of the array (*rates_total*) is too small, no calculations are required - we need to wait till the next call of OnCalculate(), when data are enough.

if(rates_total<r+s) return(0);
if(prev_calculated==0)
{
MTMBuffer[0]=0.0;
AbsMTMBuffer[0]=0.0;
}

Since the exponential smoothing is used twice sequentially to calculate True Strength Index, the size of price[] must be at least equal to or larger than the sum of r and s periods; otherwise the execution is terminated, and OnCalculate() returns 0. The returned zero value means that the indicator will not be plotted in the chart, because its values aren't calculated.

### Setting up Representation

As for the correctness of calculations, the indicator is ready to use. But if we call it from another mql5-program, it will be built by Close prices on default. We can specify another default price type - specify a value from the ENUM_APPLIED_PRICE enumeration in the *indicator_applied_price* property of the indicator.

For example, in order to set a typical price ( (high+low+close)/3) for a price, let's write the following:

#property indicator_applied_price PRICE_TYPICAL

If we are planning to use only its values using iCustom() or IndicatorCreate() functions, no further refinements are required. But if used directly, i.e. plotted in the chart, additional settings are recommended:

- bar number, starting from which an indicator is plotted;
- Label for values in TSIBuffer[], which will be reflected in DataWindow;
- short name of the indicator, shown in a separate window and in the pop-up help when pointing the mouse cursor over the indicator line;
- number of digits after the decimal point that is shown in the indicator values (this doesn't affect the accuracy).

These settings can be tuned in the OnInit() handler, using functions from the Custom Indicators group. Add new lines and save the indicator as True_Strength_Index_ver2.mq5.

int OnInit()
{
SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA);
SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS);
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,r+s-1);
string shortname;
StringConcatenate(shortname,"TSI(",r,",",s,")");
PlotIndexSetString(0,PLOT_LABEL,shortname);
IndicatorSetString(INDICATOR_SHORTNAME,shortname);
IndicatorSetInteger(INDICATOR_DIGITS,2);
return(0);
}

If we start both versions of the indicator and scroll the chart to the beginning, we'll see all the differences.

### Conclusion

Based on the example of creating the True Strength Index indicator, we can outline the basic moments in the process of writing of any indicator in MQL5:

- To create your own custom indicator use MQL5 Wizard that will help you perform preliminary routine operations on the indicator setup. Select the necessary variant of the OnCalculate() function.
- If necessary, add more arrays for intermediate calculations and bind them with corresponding indicator buffers using the SetIndexBuffer() function. Indicate the INDICATOR_CALCULATIONS type for these buffers.
- Optimize calculations in OnCalculate(), because this function will be called each time price data change. Use ready debugged functions to make code writing easier, and for better readability.

- Perform additional visual tuning of the indicator, to make the program easy to use both for other mql5 programs and by users.