Русский
preview
The Mathematics of Volatility: Why the GRI Indicator Deserves to Return to Your Trading Terminal

The Mathematics of Volatility: Why the GRI Indicator Deserves to Return to Your Trading Terminal

MetaTrader 5Examples |
1 302 3
Artyom Trishkin
Artyom Trishkin

Contents



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:

  1. For each bar, we find the maximum and minimum closing prices over the last N bars.
  2. We calculate the range (the difference between the maximum and the minimum).
  3. We take the common (base-10) logarithm of the range and divide it by the logarithm of the period.
  4. 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

Attached files |
GRI.mq5 (7.73 KB)
ExpGRI_AMA_TEMA.mq5 (91.39 KB)
Last comments | Go to discussion (3)
Maxim Kuznetsov
Maxim Kuznetsov | 20 Jan 2026 at 09:06

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

Syed Jawad Hussain Naqvi
Syed Jawad Hussain Naqvi | 10 Sep 2026 at 14:57
Amazing writeup SIR!
Aleksej Poljakov
Aleksej Poljakov | 11 Sep 2026 at 05:15

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.


Ebola Optimization Search Algorithm (EOSA) Ebola Optimization Search Algorithm (EOSA)
The article examines the EOSA algorithm, which is inspired by the mechanisms of Ebola virus transmission: short-distance transmission through close contact (exploitation) and long-distance transmission through travel (exploration). An analysis of the original publication revealed critical issues in the mathematical formulas and an epidemiological model that was impractical to implement, which required a significant overhaul of the algorithm to produce a workable implementation.
Creating a Cairo-Inspired Graphics Library for MetaTrader 5 (Part 2): Points, Contours and the Path Creating a Cairo-Inspired Graphics Library for MetaTrader 5 (Part 2): Points, Contours and the Path
This second part adds the geometry layer to a Cairo‑inspired graphics library for MetaTrader 5. It defines a path of double‑precision points grouped into contours, records open/closed intent, and stores vertices in a flat array with start indices. We implement MoveTo, LineTo, Close, provide basic shape helpers, and include a demo that visualizes the built geometry for inspection and reuse.
Master MQL5 — From Beginner to Pro (Part VII): Principles of Debugging MQL Applications Master MQL5 — From Beginner to Pro (Part VII): Principles of Debugging MQL Applications
Debugging is an integral part of the programming cycle. This article discusses common techniques for debugging any application running in the MetaTrader 5 environment.
Market Simulation: Position View (XIV) Market Simulation: Position View (XIV)
Now we will implement this solution, since MQL5 is based on the same principles as event-driven programmingю Developers often use this model when creating DLLs. I know that at first, the event-driven model will seem confusing and illogical. But in this article, I will explain the principles of event-driven programming in a way that is easier to understand, so that if you are just getting started, you will have a clear grasp of how it works. Understanding what I am about to explain in this article will help you throughout your work as a programmer.