//+------------------------------------------------------------------+
//| FXP_RelativeVolume.mq5                                           |
//| FXPIPS ONLINE - https://fxpips.online/mt5-indicators/relative-volume/
//|                                                                  |
//| License: MIT. Copyright (c) 2026 FXPIPS ONLINE.                  |
//| Permission is granted to use, copy, modify and distribute this   |
//| software provided this notice is kept. The software is provided  |
//| "as is", without warranty of any kind.                           |
//|                                                                  |
//| Educational tool - not financial advice. Trading involves        |
//| substantial risk of loss. Test on a demo account first.          |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/relative-volume/"
#property version   "1.00"
#property description "Relative volume (RVOL): bar volume divided by the average"
#property description "volume of the previous N bars. High and low readings are coloured."

#property indicator_separate_window
#property indicator_minimum 0
#property indicator_buffers 2
#property indicator_plots   1

#property indicator_label1  "RVOL"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrSilver,clrDarkOrange,clrSteelBlue
#property indicator_width1  3

input group "RVOL"
input ENUM_APPLIED_VOLUME InpVolume = VOLUME_TICK; // Volume type (real falls back to tick when zero)
input int    InpPeriod    = 20;    // Average length (previous bars)
input double InpHighLevel = 2.0;   // Unusually high volume at or above (x average)
input double InpLowLevel  = 0.5;   // Unusually low volume at or below (x average)
input group "Alerts"
input bool   InpAlerts    = false; // Popup alert when a closed bar has high RVOL
input bool   InpPush      = false; // Push notification when a closed bar has high RVOL

double gRvol[], gClr[];
int      gPeriod = 20;
datetime gLastAlertBar = 0;

string TfName() { return StringSubstr(EnumToString(Period()), 7); }

void Notify(const string text, const datetime barTime)
{
   if(!InpAlerts && !InpPush) return;
   if(barTime == gLastAlertBar) return;
   gLastAlertBar = barTime;
   string msg = "FXP RVOL " + _Symbol + " " + TfName() + ": " + text;
   if(InpAlerts) Alert(msg);
   if(InpPush)   SendNotification(msg);
}

double Vol(const int i, const long &tick_volume[], const long &volume[])
{
   if(InpVolume == VOLUME_REAL && volume[i] > 0) return((double)volume[i]);
   return((double)tick_volume[i]);
}

int OnInit()
{
   gPeriod = MathMax(1, InpPeriod);
   SetIndexBuffer(0, gRvol, INDICATOR_DATA);
   SetIndexBuffer(1, gClr,  INDICATOR_COLOR_INDEX);
   ArraySetAsSeries(gRvol, false);
   ArraySetAsSeries(gClr, false);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, gPeriod);

   IndicatorSetInteger(INDICATOR_LEVELS, 3);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 1.0);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, InpHighLevel);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, InpLowLevel);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE, STYLE_DOT);
   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("FXP RVOL (%d)", gPeriod));
   IndicatorSetInteger(INDICATOR_DIGITS, 2);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // No indicator handles or chart objects are created by this indicator.
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < gPeriod + 2) return(0);
   ArraySetAsSeries(time, false);
   ArraySetAsSeries(tick_volume, false);
   ArraySetAsSeries(volume, false);

   int start;
   if(prev_calculated <= 0 || prev_calculated > rates_total)
   {
      ArrayInitialize(gRvol, EMPTY_VALUE);
      ArrayInitialize(gClr, 0.0);
      start = gPeriod;
      gLastAlertBar = time[rates_total - 1];
   }
   else
      start = prev_calculated - 1;

   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      double sum = 0.0;
      for(int k = 1; k <= gPeriod; k++) sum += Vol(i - k, tick_volume, volume);
      double avg = sum / gPeriod;
      double r = (avg > 0.0) ? Vol(i, tick_volume, volume) / avg : 0.0;
      gRvol[i] = r;
      gClr[i]  = (r >= InpHighLevel) ? 1.0 : ((r <= InpLowLevel) ? 2.0 : 0.0);
   }

   if(prev_calculated > 0)
   {
      int b = rates_total - 2;
      if(gRvol[b] != EMPTY_VALUE && gRvol[b] >= InpHighLevel)
         Notify(StringFormat("high relative volume %.2fx on the last closed bar", gRvol[b]), time[rates_total - 1]);
   }
   return(rates_total);
}
//+------------------------------------------------------------------+
