//+------------------------------------------------------------------+
//|                                                 Better_Volume.mq5|
//|                  Copyright 2019-2025, Virologista Kerntopf Corp. |
//|                 https://www.mql5.com/pt/users/virologustavo/news |
//|  Revised version 1.01: bug fixes and English translation.        |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019-2025, Virologista Kerntopf Corp."
#property link      "https://www.mql5.com/pt/users/virologustavo/news"
#property version   "1.01"
#property description "Better Volume: highlights climax, churn and low-volume bars."
#property description "Colors: Crimson = Buy Climax, WhiteSmoke = Sell Climax,"
#property description "LimeGreen = Churn, Magenta = Climax Churn, Gold = Low Volume,"
#property description "DarkTurquoise = normal volume."
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   2
#property indicator_minimum 0.0
//--- Better Volume plot
#property indicator_label1  "Better Volume"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrCrimson, clrDarkTurquoise, clrGold, clrLimeGreen, clrWhiteSmoke, clrMagenta
#property indicator_style1  STYLE_SOLID
#property indicator_width1  5
//--- Moving average plot
#property indicator_label2  "Moving Average"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrMaroon
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2
//--- Input parameters
input int                InpMAPeriod   = 100;         // Moving average period
input int                InpLookBack   = 20;          // Lookback window for comparisons
input ENUM_APPLIED_VOLUME InpVolumeType = VOLUME_TICK; // Volume type (VOLUME_REAL is 0 on most forex/CFD symbols)
//--- Color indexes (must match the order in indicator_color1)
#define CLR_BUY_CLIMAX    0
#define CLR_NORMAL        1
#define CLR_LOW_VOLUME    2
#define CLR_CHURN         3
#define CLR_SELL_CLIMAX   4
#define CLR_CLIMAX_CHURN  5
//--- Indicator buffers
double ExtBVBuffer[];
double ExtBVColors[];
double ExtMABuffer[];
//--- Index of the first bar with enough history to be calculated
int    g_first = 0;
//+------------------------------------------------------------------+
//| Returns the volume of a bar according to the selected type       |
//+------------------------------------------------------------------+
double GetVolume(const long &tick_volume[], const long &volume[], const int index)
  {
   return((InpVolumeType == VOLUME_TICK) ? (double)tick_volume[index] : (double)volume[index]);
  }
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Validate inputs
   if(InpMAPeriod < 1 || InpLookBack < 1)
     {
      Print("Better Volume: InpMAPeriod and InpLookBack must both be >= 1.");
      return(INIT_PARAMETERS_INCORRECT);
     }
//--- First bar index that has a full window of history behind it
   g_first = MathMax(InpMAPeriod, InpLookBack) - 1;
//--- Map indicator buffers
   SetIndexBuffer(0, ExtBVBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, ExtBVColors, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, ExtMABuffer, INDICATOR_DATA);
//--- Do not draw bars that have not been calculated
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, g_first);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, g_first);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
//--- Volume is shown without decimals
   IndicatorSetInteger(INDICATOR_DIGITS, 0);
//--- Short name
   string short_name = StringFormat("Better Volume (%d,%d)", InpMAPeriod, InpLookBack);
   IndicatorSetString(INDICATOR_SHORTNAME, short_name);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//--- Not enough history yet: report nothing calculated so the next call restarts cleanly
   if(rates_total <= g_first)
      return(0);
//--- Determine the starting bar
   int limit;
   if(prev_calculated == 0)
     {
      limit = g_first;
      //--- Initialize the bars that will not be calculated
      for(int i = 0; i < g_first; i++)
        {
         ExtBVBuffer[i] = EMPTY_VALUE;
         ExtBVColors[i] = CLR_NORMAL;
         ExtMABuffer[i] = EMPTY_VALUE;
        }
     }
   else
      limit = MathMax(prev_calculated - 1, g_first);
//--- Iterate from left to right
   for(int i = limit; i < rates_total && !IsStopped(); i++)
     {
      //--- Basic values for the current bar
      double vol          = GetVolume(tick_volume, volume, i);
      double candleRange  = high[i] - low[i];
      double timesRange   = vol * candleRange;
      double dividedRange = (candleRange > 0) ? vol / candleRange : 0;
      double midPrice     = (high[i] + low[i]) / 2;

      ExtBVBuffer[i] = vol;
      ExtBVColors[i] = CLR_NORMAL;

      //--- Moving average of volume
      double sumVolumes = 0;
      for(int n = i - InpMAPeriod + 1; n <= i; n++)
         sumVolumes += GetVolume(tick_volume, volume, n);
      ExtMABuffer[i] = sumVolumes / InpMAPeriod;

      //--- Extremes inside the lookback window (the current bar is included)
      double maxTimesRange   = 0;
      double maxDividedRange = 0;
      double minVolume       = DBL_MAX;
      for(int n = i - InpLookBack + 1; n <= i; n++)
        {
         double v = GetVolume(tick_volume, volume, n);
         double r = high[n] - low[n];
         double tr = v * r;

         if(v < minVolume)
            minVolume = v;
         if(tr > maxTimesRange)
            maxTimesRange = tr;
         if(tr > 0)                       // implies v > 0 and r > 0, so the division is safe
           {
            double dr = v / r;
            if(dr > maxDividedRange)
               maxDividedRange = dr;
           }
        }

      //--- Bar categories.
      //--- The > 0 guards prevent false signals when volume or range is zero
      //--- (for example VOLUME_REAL on symbols that have no real volume).
      bool isLowVolume   = (vol == minVolume);
      bool isMaxTimes    = (maxTimesRange > 0   && timesRange   == maxTimesRange);
      bool isMaxDivided  = (maxDividedRange > 0 && dividedRange == maxDividedRange);

      //--- Apply colors from lowest to highest priority
      if(isLowVolume)                        // Low Volume
         ExtBVColors[i] = CLR_LOW_VOLUME;
      if(isMaxDivided)                       // Churn
         ExtBVColors[i] = CLR_CHURN;
      if(isMaxTimes)                         // Buy Climax / Sell Climax
         ExtBVColors[i] = (close[i] > midPrice) ? CLR_BUY_CLIMAX : CLR_SELL_CLIMAX;
      if(isMaxTimes && isMaxDivided)         // Climax Churn (highest priority)
         ExtBVColors[i] = CLR_CLIMAX_CHURN;
     }
//--- Return the number of calculated bars for the next call
   return(rates_total);
  }
//+------------------------------------------------------------------+