The Mathematics of Volatility: Why the GRI Indicator Deserves to Return to Your Trading Terminal
Contents
- Introduction
- Implementing the Indicator for MetaTrader 5
- Interpretation Notes
- Testing the Indicator in an Expert Advisor
- Conclusion
Introduction
In modern trading, it is important not only to determine the direction of price movement, but also to understand how "active" or "chaotic" the market is at any given moment. Many traders find themselves in a situation where standard indicators do not provide a clear answer: whether to expect a strong move or whether the market is in a quiet phase. When looking for a simple and intuitive tool to assess market volatility and the market's "degree of chaos," one option is the Gopalakrishnan Range Index (GRI), also known as the Range of Chaos Index (ROCI).
GRI was first published in January 2001 in the respected journal Technical Analysis of Stocks & Commodities (TASC). Its creator was Jayanthi Gopalakrishnan, a well-known analyst and editor at TASC. At that time, the indicator was proposed as a simple way to quantify the "degree of chaos" (range) of price movement over a given period. Despite its simplicity and clarity, GRI has not gained widespread popularity and is rarely found in modern trading platforms and strategies.
Throughout the history of technical analysis, there have been many tools developed during an era when powerful computers and complex algorithms did not exist. They are characterized by their simplicity, clear logic, and versatility. Indicators such as GRI can be easily implemented in modern trading platforms, and their concepts can be adapted to new market conditions. Sometimes it is precisely the forgotten tools that hold the key to success when standard methods no longer yield the desired results.
Implementing the Indicator for MetaTrader 5
GRI measures how much the closing price changes over a selected period (ChaoticPeriod), normalizing this range on a logarithmic scale.
Indicator calculation formula:
GRI = log10(High(Close, N) - Low(Close, N)) / log10(N)
where:
- High(Close, N) — the maximum closing price over N bars,
- Low(Close, N) — the minimum closing price over N bars,
- N — calculation period (ChaoticPeriod).
Calculation logic:
- For each bar, we find the maximum and minimum closing prices over the last N bars.
- We calculate the range (the difference between the maximum and the minimum).
- We take the common (base-10) logarithm of the range and divide it by the logarithm of the period.
- If the range is zero, the indicator returns 0.
Based on the above formulas and logic, let's create an indicator for MetaTrader 5:
//+------------------------------------------------------------------+ //| GRI.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- plot GRI #property indicator_label1 "GRI" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- input parameters input(name="ChaoticPeriod") int InpPeriod = 5; // Calculation period //--- indicator buffers double BufferGRI[]; //--- global variables int ExtPeriod; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,BufferGRI,INDICATOR_DATA); ArraySetAsSeries(BufferGRI,true); // Buffer as a time series //--- Setting the indicator parameters ExtPeriod=(InpPeriod<2 ? 2 : InpPeriod); // Adjust the calculation period string short_name=StringFormat("GRI(%d)",ExtPeriod); // Define a short name IndicatorSetString(INDICATOR_SHORTNAME,short_name); // Set the short name IndicatorSetInteger(INDICATOR_DIGITS,_Digits); // Display accuracy //--- Completed successfully return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { //--- Close array as a time series ArraySetAsSeries(close,true); //--- Check the number of available bars if(rates_total<fmax(ExtPeriod,5)) return 0; //--- Check and calculate the number of bars to process int limit=rates_total-prev_calculated; //--- If this is the first launch or the historical data has changed if(limit>1) { limit=rates_total-ExtPeriod-1; // Start the calculation from the beginning of the historical data ArrayInitialize(BufferGRI,EMPTY_VALUE); // Initialize the indicator buffer with an empty value } //--- Main loop for(int i=limit;i>=0;i--) { //--- Calculate the closing price range over ExtPeriod bars int mx=ArrayMaximum(close,i,ExtPeriod); int mn=ArrayMinimum(close,i,ExtPeriod); if(mx==WRONG_VALUE || mn==WRONG_VALUE) return 0; //--- Maximum and minimum Close values over the ExtPeriod period double max=close[mx]; double min=close[mn]; double range = max-min; //--- Calculate and write the ExtPeriod-bar “degree of chaos” value to the buffer BufferGRI[i]=(range>0 ? MathLog(range)/MathLog((double)ExtPeriod) : 0); } //--- Return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
The higher the GRI value, the more “chaotic” (volatile) the price movement was during the period. If the range is small, GRI tends toward zero.
Interpretation Notes
In the GRI calculation performed in the code shown above, the indicator values can be either positive or negative. This is because, for small price ranges (range < 1), the logarithm becomes negative. As a result, the indicator scale has no lower bound and depends on the instrument's volatility and the selected period, which creates some challenges when automatically interpreting the indicator values.

However, it is more convenient to have a specific value to start from when interpreting the indicator values. To do this, the minimum value of the indicator can be set to zero (that is, make the scale start at zero).
To do this, you can use the logarithm of a shifted range:
GRI = log10(1 + High(Close, N) - Low(Close, N)) / log10(N)
With this formula, the indicator will be calculated as follows:
//--- Main loop for(int i=limit;i>=0;i--) { //--- Calculate the range of closing prices over ExtPeriod bars int mx=ArrayMaximum(close,i,ExtPeriod); int mn=ArrayMinimum(close,i,ExtPeriod); if(mx==WRONG_VALUE || mn==WRONG_VALUE) return 0; //--- Maximum and minimum Close values over the ExtPeriod period double max=close[mx]; double min=close[mn]; double range = max-min; //--- Calculate and store in a buffer the "degree of chaos" value over ExtPeriod bars //BufferGRI[i]=(range>0 ? MathLog(range)/MathLog((double)ExtPeriod) : 0); // Arbitrary range BufferGRI[i] = MathLog(1.0 + range) / MathLog((double)ExtPeriod); // Value range from zero upward }
Now the indicator will always have minimum values approaching zero, which will make it easier to interpret without looking at the chart, since there is always a value to use as a starting point when calculating threshold values:

High GRI values (sharp spikes on the indicator chart) indicate that the market was highly volatile during the selected period and that there were significant price movements. This may indicate the emergence or continuation of a trend, the release of important news, or simply a phase of high market activity.
Low GRI values (flat sections close to zero) indicate that the market is in a state of consolidation, or a "lull," when the price moves within a narrow range. At times like these, there is often no clear trend.
There is one more point I would like to mention and correct. This calculation yields resulting indicator values that are too small:
BufferGRI[i] = MathLog(1.0 + range) / MathLog((double)ExtPeriod); // A range of values from zero upward
To make threshold values easier to use in the EA settings, it is better to scale them to the symbol’s points. To do this, simply divide the final value by the Point value:
BufferGRI[i] = (MathLog(1.0 + range)/MathLog((double)ExtPeriod))/_Point; // A range of values from zero upward
With this method of calculating the indicator and using it in an Expert Advisor, it will be possible to enter values such as 10, 50, 100, 150, and so on in the EA settings. This is more convenient than entering small fractional values.
Let’s look at practical scenarios for using the indicator.
- Filtering signals from other indicators.
GRI can be used as a filter in trend-following strategies; for example, trades can be opened only when GRI exceeds a certain level, which indicates volatility and potential for price movement. - Identifying entry and exit points.
A GRI spike following a prolonged lull may signal the start of a new move — this could be an entry point. Conversely, a sharp drop in GRI following a strong move may indicate the end of the momentum — a reason to take profit. - Determining the market phase.
If the GRI remains low for an extended period, the market is most likely range-bound. During such periods, it is best to avoid trend-following strategies and use methods designed for range-bound trading. - Adjusting trading system parameters.
You can dynamically change the parameters of other indicators or Expert Advisors based on the current GRI value; for example, increase stop-loss and take-profit levels during periods of high volatility and decrease them during periods of low volatility.
The indicator has only one configurable parameter — the calculation period (ChaoticPeriod).
A short period (3–5) makes the indicator more sensitive to local spikes, while a long period (10–20) smooths the values and shows more global changes in volatility.
To determine the indicator’s states, you need to select the appropriate GRI threshold values. The optimal indicator levels may vary for each instrument and timeframe. It is recommended to conduct a visual analysis of historical data and select levels that clearly distinguish between periods of high and low volatility.
To improve signal accuracy, use GRI in combination with trend indicators and oscillators.
You should not use GRI as your sole source of signals — it only shows the degree of chaos, not the direction of movement.
Backtest the indicator on historical data and adjust the parameters to suit your trading style and instrument.
Let's try to develop a simple trading system using this indicator and test it in an Expert Advisor (EA).
Testing the Indicator in an EA
To test the GRI indicator, we will create an EA that trades based on signals from two moving averages — TEMA and AMA. The idea behind the trading system is as follows: TEMA indicates the direction of movement (up or down), while AMA filters the signals by confirming them based on the price’s position relative to its line.
The GRI indicator will be used as a market condition filter: if the indicator shows that the market is active (volatility is rising or a new surge in volatility is beginning), then trading is permitted. If the market transitions to declining volatility, activity fades, or volatility falls for several consecutive periods, trading is prohibited.
The EA will operate according to the following logic:
- If TEMA is rising and the price is above the rising AMA, this is a buy signal.
- If TEMA is falling and the price is below the falling AMA, this is a sell signal.
- Before a trade is opened, the market state is checked using GRI. If the indicator shows that volatility is increasing or beginning to increase, a position is opened in the direction of the moving averages.
If volatility is falling or the market is flat/range-bound, the Expert Advisor does not trade. - Stop-loss and take-profit levels are calculated automatically based on the values of the Bollinger Bands and ATR indicators, respectively, or based on values specified in points.
Thus, the EA will open positions only when there is a confirmed signal from the moving averages and the market is active according to the GRI indicator. This will help you avoid trading during "dormant" or fading market phases and trade at times when price movement is most likely. GRI signal filtering will be optional, allowing you to test a section of historical data with and without GRI signal filtering.
At the same time, it will be possible to open positions in both directions — both long and short. The Expert Advisor will not attempt to detect whether the market is flat/range-bound or trending, whether the current trend has changed, or whether a trend is present at all. The direction of the position will be determined based on two moving averages, and the decision to enter the market in the direction of the moving averages will be based on the state of the GRI indicator (if the filter is enabled).
Let's take a look at the entire EA code:
//+------------------------------------------------------------------+ //| ExpGRI_AMA_TEMA.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Included files | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <Arrays\ArrayLong.mqh> //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ //--- GRI states enum ENUM_GRI_STATE { GRI_STATE_UNKNOWN, // No data available GRI_STATE_FLAT, // Low volatility GRI_STATE_VOLATILITY_HIGH, // High volatility GRI_STATE_GROWING, // Volatility is rising GRI_STATE_FALLING, // Volatility is falling GRI_STATE_TURN_TO_GROW, // Transition to increasing volatility GRI_STATE_TURN_TO_FALL // Transition to declining volatility }; //--- Types of moving average signals enum ENUM_SIGNAL_TYPE { SIGNAL_TYPE_NONE, // No signal SIGNAL_TYPE_LONG, // Buy signal SIGNAL_TYPE_SHORT, // Sell signal SIGNAL_TYPE_CLOSE, // Signal to close positions }; //--- Position structure struct SData { CArrayLong list_tickets; // List of open position tickets double total_volume; // Total volume of open positions }; //--- Position data structure by type struct SDataPositions { SData Buy; // Buy position data SData Sell; // Sell position data } Data; //+------------------------------------------------------------------+ //| Macro definitions | //+------------------------------------------------------------------+ #define DATA_COUNT 3 // Amount of data obtained from the indicators (3 and more) #define ENV_ATTEMPTS 3 // Number of environment retrieval attempts #define ENV_WAIT_ATTEMPT 1000 // Number of milliseconds spent on waiting for environment update #define SPREAD_MLTP 3 // Spread multiplier for stop order distance //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ //--- GRI input int InpPeriodGRI = 9; /* GRI calculation period */ input bool InpUseGRI = true; /* Use GRI filtering */ input double InpThresholdGRI = 50.0; /* GRI volatility threshold */ //--- TEMA - indicates the trading direction input int InpPeriodTEMA = 14; /* TEMA calculation period */ input ENUM_APPLIED_PRICE InpPriceTEMA = PRICE_CLOSE; /* TEMA applied price */ input int InpShiftTEMA = 0; /* TEMA shift */ //--- AMA - filters signals based on whether the price is above or below it input int InpPeriodAMA = 9; /* AMA calculation period */ input int InpFastEmaAMA = 2; /* AMA fast EMA period */ input int InpSlowEmaAMA = 30; /* AMA slow EMA period */ input ENUM_APPLIED_PRICE InpPriceAMA = PRICE_CLOSE; /* AMA applied price */ input int InpShiftAMA = 0; /* AMA shift */ //--- BB - sets stops based on values input int InpPeriodBB = 58; /* BB calculation period */ input double InpDeviationBB = 2.0; /* BB deviations */ input int InpShiftBB = 0; /* BB shift */ input ENUM_APPLIED_PRICE InpPriceBB = PRICE_CLOSE; /* BB applied price */ //--- ATR - calculates the take profit size based on the value input int InpPeriodATR = 64; /* ATR calculation period */ //--- Trading input double InpVolume = 0.1; /* Position volume */ sinput ulong InpDeviation = 10; /* Slippage (in points) */ sinput ulong InpMagic = 123456; /* Magic number */ input int InpStopLoss = -1; /* Stop loss (in points), 0 - none, -1 - half of BB */ input int InpTakeProfit = -1; /* Take profit (in points), 0 - none, -1 - ATR value */ input double InpSLMltp = 2.6; /* Stop-loss size multiplier, if SL == -1 */ input double InpTPMltp = 1.3; /* Take-profit size multiplier, if TP == -1 */ //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ CTrade trade; // Trading class object int handle_gri; // GRI indicator handle int handle_tema; // TEMA indicator handle int handle_ama; // AMA indicator handle int handle_bb; // BB indicator handle int handle_atr; // ATR indicator handle double gri[DATA_COUNT]={}; // Array of GRI values double tema[DATA_COUNT]={}; // Array of TEMA values double ama[DATA_COUNT]={}; // Array of AMA values double bb0[DATA_COUNT]={}; // Array of BB values, buffer 0 (Upper) double bb1[DATA_COUNT]={}; // Array of BB values, buffer 1 (Lower) double bb2[DATA_COUNT]={}; // Array of BB values, buffer 2 (Middle) double atr[DATA_COUNT]={}; // Array of ATR values MqlRates prc[DATA_COUNT]={}; // Array of prices and times //--- GRI int period_gri; // GRI calculation period //--- TEMA int period_tema; // TEMA calculation period //--- AMA int period_ama; // AMA calculation period int fast_ema_ama; // AMA fast EMA period int slow_ema_ama; // AMA slow EMA period //--- BB int period_bb; // BB calculation period double deviation_bb; // BB deviations int shift_bb; // BB shift //--- ATR int period_atr; // ATR calculation period //--- double lot; // Position volume int prev_total; // Number of positions at the previous check string program_name; // Program name bool netto; // Netting account flag //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- If the account is not a hedging account, set a flag and report that the EA will not operate correctly netto=false; if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { Print("The EA is designed for use on a hedging account. Correct operation on a netting account is not guaranteed."); netto=true; } //--- Set and adjust the indicator input variables //--- GRI period_gri=(InpPeriodGRI<1 ? 5 : InpPeriodGRI); //--- TEMA period_tema=(InpPeriodTEMA<1 ? 14 : InpPeriodTEMA); //--- AMA period_ama=(InpPeriodAMA<1 ? 9 : InpPeriodAMA); fast_ema_ama=(InpFastEmaAMA<1 ? 2 : InpFastEmaAMA); slow_ema_ama=(InpSlowEmaAMA<1 ? 30 : InpSlowEmaAMA); //--- BB period_bb=(InpPeriodBB<2 ? 20 : InpPeriodBB); deviation_bb=InpDeviationBB; shift_bb=InpShiftBB; //--- ATR period_atr=(InpPeriodATR<1 ? 14 : InpPeriodATR); //--- Initialize the arrays of indicator values ArrayInitialize(gri,EMPTY_VALUE); ArrayInitialize(tema,EMPTY_VALUE); ArrayInitialize(bb0,EMPTY_VALUE); ArrayInitialize(bb1,EMPTY_VALUE); ArrayInitialize(bb2,EMPTY_VALUE); ArrayInitialize(atr,EMPTY_VALUE); ZeroMemory(prc); //--- Create indicator handles //--- GRI handle_gri=iCustom(Symbol(),PERIOD_CURRENT,"GRI",period_gri); if(handle_gri==INVALID_HANDLE) { PrintFormat("%s: Failed to create handle for custom GRI(%d) indicator",__FUNCTION__,period_gri); return INIT_FAILED; } //--- TEMA handle_tema=iTEMA(Symbol(),PERIOD_CURRENT,period_tema,InpShiftTEMA,InpPriceTEMA); if(handle_tema==INVALID_HANDLE) { PrintFormat("%s: Failed to create iTEMA(%d) handle",__FUNCTION__,period_tema); return INIT_FAILED; } //--- AMA handle_ama=iAMA(Symbol(),PERIOD_CURRENT,period_ama,fast_ema_ama,slow_ema_ama,InpShiftAMA,InpPriceAMA); if(handle_ama==INVALID_HANDLE) { PrintFormat("%s: Failed to create iAMA(%d,%d,%d) handle",__FUNCTION__,period_ama,fast_ema_ama,slow_ema_ama); return INIT_FAILED; } //--- BB handle_bb=iBands(Symbol(),PERIOD_CURRENT,period_bb,InpShiftBB,InpDeviationBB,InpPriceBB); if(handle_bb==INVALID_HANDLE) { PrintFormat("%s: Failed to create iBands(%d,%d,%.3f,%s) handle",__FUNCTION__,period_bb,shift_bb,deviation_bb,EnumToString(InpPriceBB)); return INIT_FAILED; } //--- ATR handle_atr=iATR(Symbol(),PERIOD_CURRENT,period_atr); if(handle_atr==INVALID_HANDLE) { PrintFormat("%s: Failed to create iATR(%d) handle",__FUNCTION__,period_atr); return INIT_FAILED; } //--- Program name and number of positions at the previous check program_name=MQLInfoString(MQL_PROGRAM_NAME); prev_total=0; //--- Automatic setting of the order filling type trade.SetTypeFilling(GetTypeFilling()); //--- Set the magic number trade.SetExpertMagicNumber(InpMagic); //--- Set slippage trade.SetDeviationInPoints(InpDeviation); //--- Set the lot size with adjustment of the entered value lot=CorrectLots(InpVolume); //--- Completed successfully PrintFormat("%s::%s: Initialization was successful",program_name,__FUNCTION__); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get data for three bars of indicators and prices into arrays if(!CopyIndicatorsData() || !CopyPricesData()) return; //--- Fill the position ticket lists int positions_total=PositionsTotal(); if(prev_total!=positions_total) { if(!FillingListTickets(Symbol(),InpMagic)) return; prev_total=positions_total; } //--- Get signals from indicators ENUM_SIGNAL_TYPE signal_tema=SignalTEMA(); // Upward/downward direction (SIGNAL_TYPE_LONG/SIGNAL_TYPE_SHORT) ENUM_SIGNAL_TYPE signal_ama =SignalAMA(); // Price above/below (SIGNAL_TYPE_LONG/SIGNAL_TYPE_SHORT) //--- The signals from TEMA and AMA must match (TEMA: direction; AMA: price position (above/below)) ENUM_SIGNAL_TYPE signal=(signal_tema==signal_ama ? signal_tema : SIGNAL_TYPE_NONE); //--- If GRI filtering is enabled if(InpUseGRI) { //--- Adjust the signal based on the GRI state signal=SignalCorrectionByGRI(signal); //--- Adjust the signal based on the GRI volatility threshold if(!IsHighVolatility(InpThresholdGRI) && signal!=SIGNAL_TYPE_CLOSE) signal=SIGNAL_TYPE_NONE; } //--- Trade based on signals TradeProcess(signal); } //+------------------------------------------------------------------+ //| Retrieve OHLCTV values for three bars | //+------------------------------------------------------------------+ bool CopyPricesData(void) { ResetLastError(); if(CopyRates(Symbol(),PERIOD_CURRENT,0,DATA_COUNT,prc)!=DATA_COUNT) { PrintFormat("%s: Failed to get price Open data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Retrieve GRI values for three bars | //+------------------------------------------------------------------+ bool CopyGRIData(void) { ResetLastError(); if(CopyBuffer(handle_gri,0,0,DATA_COUNT,gri)!=DATA_COUNT) { PrintFormat("%s: Failed to get GRI data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Retrieve TEMA values for three bars | //+------------------------------------------------------------------+ bool CopyTEMAData(void) { ResetLastError(); if(CopyBuffer(handle_tema,0,0,DATA_COUNT,tema)!=DATA_COUNT) { PrintFormat("%s: Failed to get TEMA data. Error %d", __FUNCTION__, GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Retrieve TEMA values for three bars | //+------------------------------------------------------------------+ bool CopyAMAData(void) { ResetLastError(); if(CopyBuffer(handle_ama,0,0,DATA_COUNT,ama)!=DATA_COUNT) { PrintFormat("%s: Failed to get AMA data. Error %d", __FUNCTION__, GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Retrieve BB values for three bars | //+------------------------------------------------------------------+ bool CopyBBData(void) { ResetLastError(); if(CopyBuffer(handle_bb,UPPER_BAND,0,DATA_COUNT,bb0)!=DATA_COUNT) { PrintFormat("%s: Failed to get BB Upper Line data. Error %d",__FUNCTION__,GetLastError()); return false; } if(CopyBuffer(handle_bb,LOWER_BAND,0,DATA_COUNT,bb1)!=DATA_COUNT) { PrintFormat("%s: Failed to get BB Lower Line data. Error %d",__FUNCTION__,GetLastError()); return false; } if(CopyBuffer(handle_bb,BASE_LINE,0,DATA_COUNT,bb2)!=DATA_COUNT) { PrintFormat("%s: Failed to get BB Base Line data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Retrieve ATR values for three bars | //+------------------------------------------------------------------+ bool CopyATRData(void) { ResetLastError(); if(CopyBuffer(handle_atr,0,0,DATA_COUNT,atr)!=DATA_COUNT) { PrintFormat("%s: Failed to get ATR data. Error %d",__FUNCTION__,GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Retrieve indicator values for three bars | //+------------------------------------------------------------------+ bool CopyIndicatorsData(void) { bool res=CopyTEMAData(); // Result of retrieving TEMA data res &=CopyAMAData(); // Result of retrieving AMA data res &=CopyGRIData(); // Result of retrieving GRI data res &=CopyBBData(); // Result of retrieving BB data res &=CopyATRData(); // Result of retrieving ATR data return res; } //+------------------------------------------------------------------+ //| Return the Open price from the array by time series index (0–2) | //+------------------------------------------------------------------+ double PriceOpen(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].open); } //+------------------------------------------------------------------+ //|Return the High price from the array by time series index (0–2) | //+------------------------------------------------------------------+ double PriceHigh(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].high); } //+------------------------------------------------------------------+ //| Return the Low price from the array by time series index (0–2) | //+------------------------------------------------------------------+ double PriceLow(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].low); } //+------------------------------------------------------------------+ //| Return the Close price from the array by time series index (0–2) | //+------------------------------------------------------------------+ double PriceClose(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].close); } //+------------------------------------------------------------------+ //| Return the bar time from the array by time series index (0–2) | //+------------------------------------------------------------------+ datetime Time(const int index) { return(index<0 || index>DATA_COUNT-1 ? 0 : prc[DATA_COUNT-index-1].time); } //+------------------------------------------------------------------+ //| Return GRI data from the array by time series index (0–2) | //+------------------------------------------------------------------+ double GRI(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : gri[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Return TEMA data from the array by time series index (0–2) | //+------------------------------------------------------------------+ double TEMA(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : tema[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Return AMA data from the array by time series index (0–2) | //+------------------------------------------------------------------+ double AMA(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : ama[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Return BB Upper data from the array by time series index (0–2) | //+------------------------------------------------------------------+ double BBUpper(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : bb0[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Return BB Lower data from the array by timeseries index (0–2) | //+------------------------------------------------------------------+ double BBLower(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : bb1[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Return BB Middle data from the array by timeseries index (0–2) | //+------------------------------------------------------------------+ double BBMiddle(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : bb2[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| Return half the BB width in points | //+------------------------------------------------------------------+ int HalfSizeBB(const int index) { double up=BBUpper(index); double dn=BBLower(index); if(up==EMPTY_VALUE || dn==EMPTY_VALUE) return 0; return (int)round(((up-dn)*0.5)/Point()); } //+------------------------------------------------------------------+ //| Return ATR data from the array by timeseries index (0–2) | //+------------------------------------------------------------------+ double ATR(const int index) { return(index<0 || index>DATA_COUNT-1 ? EMPTY_VALUE : atr[DATA_COUNT-index-1]); } //+------------------------------------------------------------------+ //| GRI state | //+------------------------------------------------------------------+ ENUM_GRI_STATE GRIState(void) { //--- Get GRI data double gri0=GRI(0); double gri1=GRI(1); double gri2=GRI(2); //--- Error retrieving data: no signal if(gri0==EMPTY_VALUE || gri1==EMPTY_VALUE || gri2==EMPTY_VALUE) return GRI_STATE_UNKNOWN; //--- Volatility is rising if(gri0>gri1 && gri1>gri2) return GRI_STATE_GROWING; //--- Volatility is declining if(gri0<gri1 && gri1<gri2) return GRI_STATE_FALLING; //--- Transition to increasing volatility (previously declining/flat, now rising) if(gri0>gri1 && gri1<=gri2) return GRI_STATE_TURN_TO_GROW; //--- Transition to declining volatility (previously rising/flat, now declining) if(gri0<gri1 && gri1>=gri2) return GRI_STATE_TURN_TO_FALL; //--- Flat/range-bound market return GRI_STATE_FLAT; } //+------------------------------------------------------------------+ //| Adjust the signal based on the GRI state | //+------------------------------------------------------------------+ ENUM_SIGNAL_TYPE SignalCorrectionByGRI(const ENUM_SIGNAL_TYPE signal_type) { //--- Current signal ENUM_SIGNAL_TYPE signal=signal_type; //--- Adjust the current signal depending on the GRI state ENUM_GRI_STATE state =GRIState(); switch(state) { //--- Transition to increasing volatility case GRI_STATE_TURN_TO_GROW : // Here, the signal remains unchanged, // but the start of the increase could be handled in some other way break; //--- Volatility is rising case GRI_STATE_GROWING : // Here, the signal remains unchanged, // but this constant growth can also be handled in some other way break; //--- High volatility case GRI_STATE_VOLATILITY_HIGH : // Here, the signal remains unchanged, // but high volatility can also be handled in some other way break; //--- Transition to declining volatility case GRI_STATE_TURN_TO_FALL : signal=SIGNAL_TYPE_NONE; // No signal // There is simply no signal here, // but this state can be handled differently, // for example, here you can close part of the position or move the stop to breakeven, // or do both break; //--- Volatility is falling case GRI_STATE_FALLING : signal=SIGNAL_TYPE_NONE; // No signal // There is simply no signal here, // but this state can be handled differently, // for example, enable a trailing stop or close positions: //signal=SIGNAL_TYPE_CLOSE; // Signal to close all positions (volatility has been declining for three consecutive bars) break; //--- Low volatility case GRI_STATE_FLAT : signal=SIGNAL_TYPE_NONE; // No signal // There is simply no signal here, // but this state can be handled differently, // for example, you can place pending orders here break; //--- GRI_STATE_UNKNOWN Error retrieving data default: signal=SIGNAL_TYPE_NONE; // No signal break; } return signal; } //+------------------------------------------------------------------+ //| GRI volatility threshold exceeded | //+------------------------------------------------------------------+ bool IsHighVolatility(double threshold) { //--- Retrieving GRI data double gri0=GRI(0); //--- Data retrieval error - false if(gri0==EMPTY_VALUE) return false; return(gri0>=threshold); } //+------------------------------------------------------------------+ //| TEMA signal | //+------------------------------------------------------------------+ ENUM_SIGNAL_TYPE SignalTEMA(void) { double tema0=TEMA(0); double tema1=TEMA(1); //--- Error - no signal if(tema0==EMPTY_VALUE || tema1==EMPTY_VALUE) return SIGNAL_TYPE_NONE; //--- Buy signal: TEMA is rising if(tema0>tema1) return SIGNAL_TYPE_LONG; //--- Sell signal: TEMA is falling if(tema0<tema1) return SIGNAL_TYPE_SHORT; //--- No signal return SIGNAL_TYPE_NONE; } //+------------------------------------------------------------------+ //| AMA signal | //+------------------------------------------------------------------+ ENUM_SIGNAL_TYPE SignalAMA(void) { double ama0=AMA(0); double ama1=AMA(1); double price=PriceClose(0); //--- Error - no signal if(ama0==EMPTY_VALUE || ama1==EMPTY_VALUE || price==0) return SIGNAL_TYPE_NONE; //--- Buy signal: price is above AMA, and AMA is rising if(price>ama0 && ama0>ama1) return SIGNAL_TYPE_LONG; //--- Sell signal: price is below AMA, and AMA is falling if(price<ama0 && ama0<ama1) return SIGNAL_TYPE_SHORT; //--- No signal return SIGNAL_TYPE_NONE; } //+------------------------------------------------------------------+ //| Return the order execution type matching type, | //| if it is available for the symbol; otherwise, a valid option | //| https://www.mql5.com/ru/forum/170952/page4#comment_4128864 | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE_FILLING GetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_RETURN) { const ENUM_SYMBOL_TRADE_EXECUTION exe_mode=(ENUM_SYMBOL_TRADE_EXECUTION)::SymbolInfoInteger(Symbol(),SYMBOL_TRADE_EXEMODE); const int filling_mode=(int)::SymbolInfoInteger(Symbol(),SYMBOL_FILLING_MODE); return((filling_mode==0 || (type>=ORDER_FILLING_RETURN) || ((filling_mode &(type+1))!=type+1)) ? (((exe_mode==SYMBOL_TRADE_EXECUTION_EXCHANGE) || (exe_mode==SYMBOL_TRADE_EXECUTION_INSTANT)) ? ORDER_FILLING_RETURN :((filling_mode==SYMBOL_FILLING_IOC) ? ORDER_FILLING_IOC : ORDER_FILLING_FOK)) : type); } //+------------------------------------------------------------------+ //| Return a valid lot size | //+------------------------------------------------------------------+ double CorrectLots(const double lots,const bool to_min_correct=true) { double min=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); double max=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); double step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP); return(to_min_correct ? VolumeRoundToSmaller(lots,min,max,step) : VolumeRoundToCorrect(lots,min,max,step)); } //+------------------------------------------------------------------+ //| Return the nearest valid lot size | //+------------------------------------------------------------------+ double VolumeRoundToCorrect(const double volume,const double min,const double max,const double step) { return(step==0 ? min : fmin(fmax(round(volume/step)*step,min),max)); } //+------------------------------------------------------------------+ //| Return the nearest lower valid lot size | //+------------------------------------------------------------------+ double VolumeRoundToSmaller(const double volume,const double min,const double max,const double step) { return(step==0 ? min : fmin(fmax(floor(volume/step)*step,min),max)); } //+---------------------------------------------------------------------------------------+ //| Return a flag indicating that the total volume on the account has not been exceeded | //+---------------------------------------------------------------------------------------+ bool CheckLotForLimitAccount(const ENUM_POSITION_TYPE position_type,const double volume) { double lots_limit=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT); if(lots_limit==0) return true; double total_volume=(position_type==POSITION_TYPE_BUY ? Data.Buy.total_volume : Data.Sell.total_volume); return(total_volume+volume<=lots_limit); } //+------------------------------------------------------------------+ //| Return the valid StopLoss relative to StopLevel | //+------------------------------------------------------------------+ double CorrectStopLoss(const ENUM_POSITION_TYPE position_type,const int stop_loss) { if(stop_loss==0) return 0; double pt=Point(); double price=(position_type==POSITION_TYPE_BUY ? SymbolInfoDouble(Symbol(),SYMBOL_ASK) : SymbolInfoDouble(Symbol(),SYMBOL_BID)); int lv=StopLevel(), dg=Digits(); return(position_type==POSITION_TYPE_BUY ? NormalizeDouble(fmin(price-lv*pt,price-stop_loss*pt),dg) : NormalizeDouble(fmax(price+lv*pt,price+stop_loss*pt),dg)); } //+------------------------------------------------------------------+ //| Return the valid TakeProfit relative to StopLevel | //+------------------------------------------------------------------+ double CorrectTakeProfit(const ENUM_POSITION_TYPE position_type,const int take_profit) { if(take_profit==0) return 0; double pt=Point(); double price=(position_type==POSITION_TYPE_BUY ? SymbolInfoDouble(Symbol(),SYMBOL_ASK) : SymbolInfoDouble(Symbol(),SYMBOL_BID)); int lv=StopLevel(), dg=Digits(); return(position_type==POSITION_TYPE_BUY ? NormalizeDouble(fmax(price+lv*pt,price+take_profit*pt),dg) : NormalizeDouble(fmin(price-lv*pt,price-take_profit*pt),dg)); } //+------------------------------------------------------------------+ //| Return the calculated StopLevel | //+------------------------------------------------------------------+ int StopLevel(void) { int sp=(int)SymbolInfoInteger(Symbol(),SYMBOL_SPREAD); int lv=(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL); return(lv==0 ? sp*SPREAD_MLTP : lv); } //+------------------------------------------------------------------+ //| Return an "undefined" trading environment state | //+------------------------------------------------------------------+ bool IsUncertainStateEnv(const string symbol_name,const ulong magic_number) { //--- In the tester, the trading environment state is always valid if(MQLInfoInteger(MQL_TESTER)) return false; //--- In a loop over the number of orders int total=OrdersTotal(); for(int i=total-1; i>=0; i--) { //--- Select an order to retrieve its properties if(OrderGetTicket(i)==0) continue; //--- If the order's magic number does not match the target one, skip it if(OrderGetInteger(ORDER_MAGIC)!=magic_number) continue; //--- If the order type is neither Buy nor Sell, skip it ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(type!=ORDER_TYPE_BUY && type!=ORDER_TYPE_SELL) continue; //--- If the order symbol matches the target symbol, but the order has no position ID entry, //--- This means that the data for the opening position has not yet been added to the order history. //--- This is an undefined trading environment state—return true if(!OrderGetInteger(ORDER_POSITION_ID) && OrderGetString(ORDER_SYMBOL)==symbol_name) return true; } //--- The trading environment is valid return false; } //+------------------------------------------------------------------+ //| Check the trading environment state | //+------------------------------------------------------------------+ bool CheckUncertainStateEnv(const string symbol_name,const ulong magic_number,const int attempts,const int wait) { //--- If the trading environment is valid, return true if(IsUncertainStateEnv(symbol_name,magic_number)) return true; //--- Make ENV_ATTEMPTS attempts to obtain a valid trading environment, waiting ENV_WAIT_ATTEMPT between attempts int n=0; while(!IsStopped() && n<attempts && IsUncertainStateEnv(symbol_name,magic_number)) { n++; Sleep(wait); } //--- If the trading environment is still undefined after the wait is complete, report this and return `false` if(n>=attempts && IsUncertainStateEnv(symbol_name,magic_number)) { PrintFormat("%s: Uncertain state of the environment. Please try again.",__FUNCTION__); return false; } //--- The environment is valid return true; } //+------------------------------------------------------------------+ //| Fill arrays of position tickets | //+------------------------------------------------------------------+ bool FillingListTickets(const string symbol_name,const ulong magic_number) { //--- If the trading environment is not valid, return false if(!CheckUncertainStateEnv(symbol_name,magic_number,ENV_ATTEMPTS,ENV_WAIT_ATTEMPT)) return false; //--- Clear the lists and initialize the variables Data.Buy.list_tickets.Clear(); Data.Sell.list_tickets.Clear(); Data.Buy.total_volume=0; Data.Sell.total_volume=0; //--- In the loop over open positions int total=PositionsTotal(); for(int i=total-1; i>WRONG_VALUE; i--) { //--- Select the position to retrieve its properties ulong ticket=PositionGetTicket(i); if(ticket==0) continue; //--- If the magic number or symbol does not match those passed to the function, continue if(PositionGetInteger(POSITION_MAGIC)!=InpMagic || PositionGetString(POSITION_SYMBOL)!=symbol_name) continue; //--- Get the position type and volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double volume=PositionGetDouble(POSITION_VOLUME); //--- Depending on the position type, add the ticket and volume to the corresponding lists if(type==POSITION_TYPE_BUY) { Data.Buy.list_tickets.Add(ticket); Data.Buy.total_volume+=volume; } //--- POSITION_TYPE_SELL else { Data.Sell.list_tickets.Add(ticket); Data.Sell.total_volume+=volume; } } //--- Completed successfully return true; } //+------------------------------------------------------------------+ //| Return the number of Buy positions | //+------------------------------------------------------------------+ int TotalBuy(void) { return Data.Buy.list_tickets.Total(); } //+------------------------------------------------------------------+ //| Return the number of Sell positions | //+------------------------------------------------------------------+ int TotalSell(void) { return Data.Sell.list_tickets.Total(); } //+------------------------------------------------------------------+ //| Return the most recently added position ticket by type | //+------------------------------------------------------------------+ ulong LastAddedTicket(const ENUM_POSITION_TYPE type) { return(type==POSITION_TYPE_BUY ? (TotalBuy()>0 ? Data.Buy.list_tickets.At(0) : 0) : (TotalSell()>0 ? Data.Sell.list_tickets.At(0) : 0)); } //+------------------------------------------------------------------+ //| Return the bar number on which the position was opened | //+------------------------------------------------------------------+ int PositionBar(const ulong ticket) { //--- Select a position by ticket ResetLastError(); if(!PositionSelectByTicket(ticket)) { PrintFormat("%s: Failed to select position by ticket #%I64u. Error %d",__FUNCTION__,ticket,GetLastError()); return -1; } //--- Get the position opening time and symbol datetime time=(datetime)PositionGetInteger(POSITION_TIME); string symbol=PositionGetString(POSITION_SYMBOL); //--- Return the bar number based on the time the position was opened return iBarShift(symbol,PERIOD_CURRENT,time); } //+------------------------------------------------------------------------+ //| Return whether the specified position was opened on the current bar | //+------------------------------------------------------------------------+ bool IsPresentPosOnCurrentBar(const ENUM_POSITION_TYPE type) { ulong ticket=LastAddedTicket(type); return(ticket>0 ? PositionBar(ticket)==0 : false); } //+------------------------------------------------------------------+ //| Return the opening price of a position by ticket | //+------------------------------------------------------------------+ double PositionPriceOpen(const ulong ticket) { //--- Check the ticket if(ticket==0) return 0; //--- Select a position by ticket ResetLastError(); if(!PositionSelectByTicket(ticket)) { PrintFormat("%s: Failed to select position by ticket #%I64u. Error %d",__FUNCTION__,ticket,GetLastError()); return 0; } //--- Return the position opening price return PositionGetDouble(POSITION_PRICE_OPEN); } //+------------------------------------------------------------------+ //| Close Buy positions | //+------------------------------------------------------------------+ bool CloseBuy(void) { int total=TotalBuy(); bool res=true; for(int i=total-1; i>=0; i--) { ulong ticket=Data.Buy.list_tickets.At(i); if(ticket==NULL) continue; if(!trade.PositionClose(ticket,InpDeviation)) res=false; } return res; } //+------------------------------------------------------------------+ //| Close Sell positions | //+------------------------------------------------------------------+ bool CloseSell(void) { int total=TotalSell(); bool res=true; for(int i=total-1; i>=0; i--) { ulong ticket=Data.Sell.list_tickets.At(i); if(ticket==NULL) continue; if(!trade.PositionClose(ticket,InpDeviation)) res=false; } return res; } //+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ bool OpenPosition(const string symbol_name,const ENUM_POSITION_TYPE type,const double volume,const string comment) { //--- Calculate values for stop orders int bb=int(HalfSizeBB(0)*InpSLMltp); double atrd=ATR(0); int atrp=(atrd!=EMPTY_VALUE ? int(round(atrd*InpTPMltp/Point())) : 0); double sl=(InpStopLoss==0 ? 0 : (InpStopLoss<0 ? (bb!=0 ? CorrectStopLoss(type,bb) : 0) : CorrectStopLoss(type,InpStopLoss))); double tp=(InpTakeProfit==0 ? 0 : (InpStopLoss<0 ? (atrp!=0 ? CorrectTakeProfit(type,atrp) : 0) : CorrectTakeProfit(type,InpTakeProfit))); //--- On a netting account, removing stop orders if(netto) sl=tp=0; //--- Get prices MqlTick tick={}; if(!SymbolInfoTick(symbol_name,tick)) { PrintFormat("%s: Unable to get prices"); return false; } //--- Check and retrieve the normalized lot size for the position being opened double ll=trade.CheckVolume(symbol_name,volume,(type==POSITION_TYPE_BUY ? tick.ask : tick.bid),(ENUM_ORDER_TYPE)type); if(ll==0) { PrintFormat("%s: Error. CheckVolume() returned a zero lot",__FUNCTION__); return false; } //--- Check the limit on the maximum total volume of open positions on the account if(!CheckLotForLimitAccount(type,ll)) { PrintFormat("%s: CheckLotForLimitAccount() returned an error",__FUNCTION__); return false; } //--- There may be a situation where a trade order has already been submitted but has not yet been fully processed, //--- which may result in opening a duplicate position. //--- If the trading environment is invalid, return false if(!CheckUncertainStateEnv(symbol_name,InpMagic,ENV_ATTEMPTS,ENV_WAIT_ATTEMPT)) return false; //--- Wait to obtain a valid trading environment may take some time //--- Get the prices again if(!SymbolInfoTick(symbol_name,tick)) { PrintFormat("%s: Unable to get prices"); return false; } //--- Return the result of sending the trade request to the server return(type==POSITION_TYPE_BUY ? trade.Buy(ll,symbol_name,tick.ask,sl,tp,comment) : trade.Sell(ll,symbol_name,tick.bid,sl,tp,comment)); } //+------------------------------------------------------------------+ //| Trading process | //+------------------------------------------------------------------+ void TradeProcess(const ENUM_SIGNAL_TYPE signal) { //--- No signal — exit if(signal==SIGNAL_TYPE_NONE) return; //--- Signal to close if(signal==SIGNAL_TYPE_CLOSE) { CloseBuy(); CloseSell(); } //--- Buy signal if(signal==SIGNAL_TYPE_LONG) { //--- If there is no open Buy position on this bar if(!IsPresentPosOnCurrentBar(POSITION_TYPE_BUY)) { //--- Get the price of the last open Buy position double price_last=PositionPriceOpen(LastAddedTicket(POSITION_TYPE_BUY)); //--- If this is the very first Buy position, or the opening price is better than the previous position's opening price— //--- Send a request to open a Buy position if(price_last==0 || price_last>SymbolInfoDouble(Symbol(),SYMBOL_ASK)) { //--- If the position has been opened, update the lists of open position tickets if(OpenPosition(Symbol(),POSITION_TYPE_BUY,lot,"")) FillingListTickets(Symbol(),InpMagic); } } } //--- Sell signal if(signal==SIGNAL_TYPE_SHORT) { //--- If there is no open Sell position on this bar if(!IsPresentPosOnCurrentBar(POSITION_TYPE_SELL)) { //--- Get the price of the last open Sell position double price_last=PositionPriceOpen(LastAddedTicket(POSITION_TYPE_SELL)); //--- If this is the very first Sell position, or the opening price is better than the previous position's opening price— //--- Send a request to open a Sell position if(price_last<SymbolInfoDouble(Symbol(),SYMBOL_ASK)) { //--- If the position has been opened, update the lists of open position tickets if(OpenPosition(Symbol(),POSITION_TYPE_SELL,lot,"")) FillingListTickets(Symbol(),InpMagic); } } } } //+------------------------------------------------------------------+
This Expert Advisor is based on "A Simple Expert Advisor Based on the WPR, Bollinger Bands, and ATR Indicators" in the CodeBase. The main logic and implementation decisions in the code are explained in comments and, I think, are easy to understand. In any case, you can ask any questions you may have in the comments section of this article.
Save the Expert Advisor in the \MQL5\Experts\STOCKS_COMMODITIES\Gopalakrishnan Range Index\ folder as ExpGRI_AMA_TEMA.mq5.
Compile the Expert Advisor and disable GRI-based filtering in the settings

and run a backtest on the H4 timeframe, using every tick, for EURUSD from January 1, 2024, to January 5, 2026.
The result is the following backtest graph:

Now let's enable signal filtering based on the GRI indicator

and run the test again with the same parameters. This gives the following backtest graph:
When using entries only in the active market (with signals filtered by GRI), the difference compared to the test without signal filtering is clearly noticeable. Yes, just like when testing without a volatility filter, there are both profitable and unprofitable trading days here. Still, it is clear that unprofitable periods are shorter than profitable ones. And if we consider that the trading system is clearly trend-following, yet lacks any logic for identifying trends and flat, range-bound market conditions, we can see that using the GRI indicator yielded a positive result over the selected historical interval.
For the EA to work correctly, the GRI indicator must be located where the EA is located — in the same folder, for example \MQL5\Experts\STOCKS_COMMODITIES\Gopalakrishnan Range Index\.
Conclusion
In the world of financial analysis, the pursuit of innovation often leads us to seek out complex algorithms, delve into neural networks, and look for unconventional solutions, while time-tested ideas remain in the shadows. The implementation of the Gopalakrishnan Range Index for MetaTrader 5 shows that what worked decades ago has not lost its relevance; it simply requires a modern form of implementation.
This is yet another confirmation that everything new is simply something old that has been well forgotten. By bringing this tool back from the archives of the journal Technical Analysis of Stocks & Commodities into the trading platforms of today’s traders, we are not only paying homage to history, but also gaining a reliable, time-tested method for assessing volatility that is free from unnecessary market noise.
In summary, it is worth noting that GRI is a striking example of how fundamental mathematical concepts from the 1990s are finding a new lease on life in the era of algorithmic trading. Often, the most effective tools are not the ones that use the most complex formulas, but those that are based on the fundamental logic of price behavior. Bringing this indicator to the MetaTrader 5 platform reminds us that before we start searching for the “Holy Grail” among the latest innovations, it’s worth taking a look at the classics. After all, a good solution has often already been found; we simply need to adapt it to today’s computing capabilities.
All the indicator and EA files are attached to the article for independent study.
The complete source code for the project, including all the files described in the article, is available in the repository.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/20795
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 article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Ebola Optimization Search Algorithm (EOSA)
Creating a Cairo-Inspired Graphics Library for MetaTrader 5 (Part 2): Points, Contours and the Path
Master MQL5 — From Beginner to Pro (Part VII): Principles of Debugging MQL Applications
Market Simulation: Position View (XIV)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
I’d ban someone for that :-)
Because there’s no answer to the question posed in the thread, no requests from anyone, just a mad, gloomy wall of code. That’s what a moderator’s pay is like
All the same, before using the indicator, it is important to understand its physical meaning. In this indicator, there are logarithms in both the numerator and the denominator. What is stopping us from using exponentiation? This would make the indicator’s meaning clearer – the average Winsorised velocity. We could go even further and measure this velocity across all values in the series. This would then give us a picture like this.