Writing a custom indicator in mql4 to normalise market prices into a simple line graph

Ben Diagi
4 min readMay 27, 2020

Data normalisation means transforming a set of variables to have values within a specified range such as between 0 and 1.

Swetha Lakshmanan in her article explains when and why people would want to normalise data.

The goal of normalisation for me today, is to change the values of market prices to a common scale, without distorting differences in the ranges of market prices.

I am essentially creating a line graph of market price in order to view the real movement of price without the micro distortions caused by volatility.

Every market has a unique price range; EurUsd has been ranging between 0.8200 and 1.6000, NzdJpy has been ranging between 41.5 and 97.5. This massive discrepancy in the range of these two pairs would make it practically impossible to compare them algorithmically. Hence the need to normalise the prices.

Specifically, I have built an indicator for this so that I can further use the normalised data in an expert advisor to trade multiple instruments.

Firstly, you should create an indicator using metaeditor:

Select ‘Custom Indicator’
Name the indicator
Select ‘OnCalculate’
Select ‘Indicator in Seperate Window’. Select Minimum as 0 and Maximum as 1. This is the range to which we would be normalising the price values.

How to normalise:

where 𝑥=(𝑥1,…,𝑥𝑛) and 𝑧𝑖 is now the 𝑖𝑡ℎ normalized data

x is the most recent (close/open)price. min(x) is the minimum price value for a period and max(x) is the maximum price value for the same period, p.

In order to achieve this, we would need to calculate the minimum and maximum values on a rolling basis considering that we would be working with a constant feed of market prices.

Assuming a period of 120 (t) where the timeframe, t could be 1hour, 4hours, 1day, our min(x), would be the low of the lowest candle in that 120(t) period, and our max(x), would be the high of the highest candle in the period.

However, I will use the open and close prices rather than the low and highs. This is personal preference, as I would like to consider the read bodies of the candles.

To determine the open/close price of the lowest/highest candle in a period, we use the iLowest and iHighest functions. Note that iLowest and iHighest do not return a price, they return the index of the lowest or highest candle, and as such, we use iLow or iHigh to determine the low or high price for that particular candle.

Code:

int period = 120;
double Normalise(int pos, string pair)
{
double x = iClose(pair,0,pos);
double min = iOpen(pair,0,iLowest(pair,0,MODE_OPEN,period,pos));
double max = iClose(pair,0,iHighest(pair,0,MODE_CLOSE,period,pos));
double i = (x-min)/(max-min);
return(i);
}

This is the function that calculates the normalised value and you would want to place it in the global scope.

Next, we need to write the OnCalculate function that constantly runs the normalise function for every new candle on the timeframe. We would write a for loop that runs through all the bars on the selected chart calculates the i value for every candle. Assuming the pair is EurUsd.

Code:

int uncalculatedBar = rates_total - prev_calculated;
for (int i=Bars-1; i>=0; i--)
{
int shift = iBarShift("EURUSD",0,Time[i]);

EurUsdBuffer[i] = Normalise(shift ,"EURUSD");
}

You should then place this loop into the OnCalculate function, after which your indicator code should look like this:

#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property indicator_separate_window
#property indicator_minimum 0
#property indicator_maximum 1
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_label1 "EurUsd"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrRed
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
double EurUsdBuffer[];

int OnInit()
{
SetIndexBuffer(0,EurUsdBuffer);
return(INIT_SUCCEEDED);
}
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[])
{
int uncalculatedBar = rates_total - prev_calculated;
for (int i=Bars-1; i>=0; i--)
{
int shift = iBarShift("EURUSD",0,Time[i]);
EurUsdBuffer[i] = Normalise(shift ,"EURUSD");
}
return(rates_total);
}

int period = 120;
double Normalise(int pos, string pair)
{
double x = iClose(pair,0,pos);
double min = iOpen(pair,0,iLowest(pair,0,MODE_OPEN,period,pos));
double max = iClose(pair,0,iHighest(pair,0,MODE_CLOSE,period,pos));
double i = (x-min)/(max-min);
return(i);
}

I hope you found this article useful. Happy trading!

--

--

Ben Diagi

I’m a Product Manager & Designer. I write about Product, Design and Finance. In my spare time, I build trading algorithms and create UX prototypes.