//+------------------------------------------------------------------+
//|                                  BB_Volatility_Excess.mq4        |
//|  Bollinger Bands volatility-excess reversal signals              |
//|  Upside excess -> SELL signal | Downside excess -> BUY signal    |
//+------------------------------------------------------------------+
#property copyright "Custom - written for Daniele"
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 4

//--- Plot 0: Buy arrow (downside excess)
#property indicator_color1 clrLimeGreen
#property indicator_width1 2

//--- Plot 1: Sell arrow (upside excess)
#property indicator_color2 clrRed
#property indicator_width2 2

double BuyArrowBuffer[];
double SellArrowBuffer[];
double SignalStateBuffer[];   // hidden: 1 = buy signal already fired this swing, -1 = sell already fired, 0 = none
double ExcessBuffer[];        // hidden: last computed excess ratio (for debugging / future use)

//--- Inputs
input int    BB_Period        = 20;     // Bollinger Bands period
input double BB_Deviation     = 2.0;    // Bollinger Bands deviation
input int    BB_MA_Shift      = 0;      // Bollinger Bands MA shift
input ENUM_APPLIED_PRICE BB_AppliedPrice = PRICE_CLOSE; // Applied price

input double ExcessThreshold  = 0.15;   // Min excess beyond band, as fraction of band width (0.15 = 15%)
input bool   RequireReEntry   = true;   // Require price back inside bands before a new signal can fire

input int    ArrowGapPoints   = 15;     // Extra distance of arrow from price, in points

input bool   AlertOnSignal    = false;  // Enable alerts
input bool   EmailAlert       = false;  // Enable email alerts
input bool   PushAlert        = false;  // Enable push notifications

//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, BuyArrowBuffer);
   SetIndexBuffer(1, SellArrowBuffer);
   SetIndexBuffer(2, SignalStateBuffer);
   SetIndexBuffer(3, ExcessBuffer);

   SetIndexStyle(0, DRAW_ARROW, STYLE_SOLID, 2, clrLimeGreen);
   SetIndexArrow(0, 233);   // up arrow
   SetIndexLabel(0, "Buy - downside volatility excess");
   SetIndexEmptyValue(0, 0.0);

   SetIndexStyle(1, DRAW_ARROW, STYLE_SOLID, 2, clrRed);
   SetIndexArrow(1, 234);   // down arrow
   SetIndexLabel(1, "Sell - upside volatility excess");
   SetIndexEmptyValue(1, 0.0);

   SetIndexStyle(2, DRAW_NONE);
   SetIndexStyle(3, DRAW_NONE);

   IndicatorShortName("BB Volatility Excess (" + IntegerToString(BB_Period) + "," + DoubleToString(BB_Deviation,1) + ")");

   return(INIT_SUCCEEDED);
}

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

   int start = (prev_calculated < 2) ? 1 : prev_calculated - 1;

   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      int shift = rates_total - 1 - i;   // ascending index -> MT4 series shift

      double upperBand = iBands(NULL, 0, BB_Period, BB_Deviation, BB_MA_Shift, BB_AppliedPrice, MODE_UPPER, shift);
      double lowerBand = iBands(NULL, 0, BB_Period, BB_Deviation, BB_MA_Shift, BB_AppliedPrice, MODE_LOWER, shift);
      double bandWidth = upperBand - lowerBand;

      BuyArrowBuffer[i]  = 0.0;
      SellArrowBuffer[i] = 0.0;

      double prevState = (i > 0) ? SignalStateBuffer[i-1] : 0.0;
      double state = prevState;

      if(bandWidth > 0.0)
      {
         double c = close[i];

         //--- Upside excess (price pushed above upper band) -> SELL
         if(c > upperBand)
         {
            double excessUp = (c - upperBand) / bandWidth;
            ExcessBuffer[i] = excessUp;

            bool canSignal = !RequireReEntry || (prevState != -1.0);
            if(excessUp >= ExcessThreshold && canSignal)
            {
               SellArrowBuffer[i] = high[i] + ArrowGapPoints * Point;
               state = -1.0;

               if(AlertOnSignal && i == rates_total - 1)
                  DoAlert(false, excessUp);
            }
         }
         //--- Downside excess (price pushed below lower band) -> BUY
         else if(c < lowerBand)
         {
            double excessDn = (lowerBand - c) / bandWidth;
            ExcessBuffer[i] = excessDn;

            bool canSignal = !RequireReEntry || (prevState != 1.0);
            if(excessDn >= ExcessThreshold && canSignal)
            {
               BuyArrowBuffer[i] = low[i] - ArrowGapPoints * Point;
               state = 1.0;

               if(AlertOnSignal && i == rates_total - 1)
                  DoAlert(true, excessDn);
            }
         }
         else
         {
            //--- Price back inside the bands -> reset state so next excursion can signal again
            ExcessBuffer[i] = 0.0;
            state = 0.0;
         }
      }

      SignalStateBuffer[i] = state;
   }

   return(rates_total);
}

//+------------------------------------------------------------------+
void DoAlert(bool buySignal, double excess)
{
   string message = StringFormat("%s %s - BB Volatility Excess: %s signal (excess %.1f%%)",
                                  Symbol(), EnumToString((ENUM_TIMEFRAMES)Period()),
                                  buySignal ? "BUY" : "SELL", excess * 100.0);
   Alert(message);
   if(EmailAlert) SendMail("BB Volatility Excess - Signal", message);
   if(PushAlert)  SendNotification(message);
}
//+------------------------------------------------------------------+
