//+------------------------------------------------------------------+
//|                                    FinsburyPark2_updown.mq4      |
//|  Extreme volatility + simultaneous volume spike detector         |
//|  Main chart : small arrows (cyan = Buy, red = Sell)               |
//|  Subwindow  : volume histogram + markers, color-coded by direction|
//+------------------------------------------------------------------+
#property copyright "Custom - written for Daniele"
#property version   "1.10"
#property strict
#property indicator_separate_window
#property indicator_buffers 12
#property indicator_minimum 0

//--- Subwindow plots
#property indicator_label1  "Volume Buy"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrDeepSkyBlue
#property indicator_width1  2

#property indicator_label2  "Volume Sell"
#property indicator_type2   DRAW_HISTOGRAM
#property indicator_color2  clrRed
#property indicator_width2  2

#property indicator_label3  "Volume Avg"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrOrange
#property indicator_width3  1

#property indicator_label4  "Spike Threshold"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrNavy
#property indicator_width4  1
#property indicator_style4  STYLE_DOT

#property indicator_label5  "Entry point Buy"
#property indicator_type5   DRAW_ARROW
#property indicator_color5  clrDeepSkyBlue
#property indicator_width5  2

#property indicator_label6  "Entry point Sell"
#property indicator_type6   DRAW_ARROW
#property indicator_color6  clrRed
#property indicator_width6  2

#property indicator_label7  "Marker 75% Buy"
#property indicator_type7   DRAW_ARROW
#property indicator_color7  clrDeepSkyBlue
#property indicator_width7  1

#property indicator_label8  "Marker 75% Sell"
#property indicator_type8   DRAW_ARROW
#property indicator_color8  clrRed
#property indicator_width8  1

#property indicator_label9  "Marker 50% Buy"
#property indicator_type9   DRAW_ARROW
#property indicator_color9  clrDeepSkyBlue
#property indicator_width9  1

#property indicator_label10 "Marker 50% Sell"
#property indicator_type10  DRAW_ARROW
#property indicator_color10 clrRed
#property indicator_width10 1

#property indicator_label11 "Marker 25% Buy"
#property indicator_type11  DRAW_ARROW
#property indicator_color11 clrDeepSkyBlue
#property indicator_width11 1

#property indicator_label12 "Marker 25% Sell"
#property indicator_type12  DRAW_ARROW
#property indicator_color12 clrRed
#property indicator_width12 1

double VolumeBuyBuffer[];
double VolumeSellBuffer[];
double VolumeAvgBuffer[];
double ThresholdBuffer[];
double EntryBuyBuffer[];
double EntrySellBuffer[];
double Marker75BuyBuffer[];
double Marker75SellBuffer[];
double Marker50BuyBuffer[];
double Marker50SellBuffer[];
double Marker25BuyBuffer[];
double Marker25SellBuffer[];

//--- Spike detection
input int    ATR_Period            = 14;    // ATR period (volatility baseline)
input double SpikeMultiplier       = 2.0;   // Bar range must be >= this x ATR to count as a price spike

//--- Direction logic
input bool   UseWickRejection      = true;  // Use wick rejection to determine direction (more selective)
input double WickRejectionRatio    = 0.5;   // Min wick size as fraction of bar range to confirm rejection

//--- Volume simultaneity (statistical)
input int    VolumeAvgPeriod       = 20;    // Bars used for volume mean/std dev baseline
input double VolumeZScoreThreshold = 2.0;   // Volume must be >= this many std devs above mean, on the SAME bar

//--- Extra filters
input double MinSpikePoints        = 0.0;   // Min absolute spike size in points (0 = disabled)
input bool   UseRSIFilter          = false; // Require RSI extreme aligned with signal direction
input int    RSI_Period            = 14;
input double RSI_Overbought        = 70.0;
input double RSI_Oversold          = 30.0;
input bool   UseEMAFilter          = false; // Only take signals aligned with the trend
input int    EMA_Period            = 100;
input bool   UseSessionFilter      = false; // Restrict to a server-time window
input int    StartHour             = 8;
input int    EndHour                = 20;

input bool   UseBBFilter           = true;  // Require price outside Bollinger Bands (reduces false signals)
input int    BB_Period             = 20;    // Bollinger Bands period
input double BB_Deviation          = 2.0;   // Bollinger Bands deviation
input double BB_MinExcessRatio     = 0.0;   // Extra: min excess beyond the band, as fraction of band width (0 = just needs to be outside)

input int    CooldownBars          = 3;     // Min bars between two signals

//--- NOTE: the indicator only ever evaluates fully CLOSED bars - this is hardcoded, not optional,
//--- to guarantee no repainting: a signal, once drawn, never disappears or moves.

//--- Arrow appearance (drawn as chart objects on the main window)
input int    ArrowSize             = 1;
input int    ArrowGapPoints        = 15;
input color  BuyColor              = clrDeepSkyBlue;  // All Buy volume/markers/arrows
input color  SellColor             = clrRed;          // All Sell volume/markers/arrows

input bool   AlertOnConfirmed      = false; // Alert only on volume-confirmed signals
input bool   EmailAlert            = false;
input bool   PushAlert             = false;

string OBJ_PREFIX = "FP2_";
int lastSignalBar = -1000;

//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, VolumeBuyBuffer);
   SetIndexBuffer(1, VolumeSellBuffer);
   SetIndexBuffer(2, VolumeAvgBuffer);
   SetIndexBuffer(3, ThresholdBuffer);
   SetIndexBuffer(4, EntryBuyBuffer);
   SetIndexBuffer(5, EntrySellBuffer);
   SetIndexBuffer(6, Marker75BuyBuffer);
   SetIndexBuffer(7, Marker75SellBuffer);
   SetIndexBuffer(8, Marker50BuyBuffer);
   SetIndexBuffer(9, Marker50SellBuffer);
   SetIndexBuffer(10, Marker25BuyBuffer);
   SetIndexBuffer(11, Marker25SellBuffer);

   SetIndexStyle(0, DRAW_HISTOGRAM, STYLE_SOLID, 2, BuyColor);
   SetIndexLabel(0, "Volume Buy");
   SetIndexEmptyValue(0, 0.0);

   SetIndexStyle(1, DRAW_HISTOGRAM, STYLE_SOLID, 2, SellColor);
   SetIndexLabel(1, "Volume Sell");
   SetIndexEmptyValue(1, 0.0);

   SetIndexStyle(2, DRAW_LINE, STYLE_SOLID, 1, clrOrange);
   SetIndexLabel(2, "Volume Avg");
   SetIndexEmptyValue(2, EMPTY_VALUE);

   SetIndexStyle(3, DRAW_LINE, STYLE_DOT, 1, clrNavy);
   SetIndexLabel(3, "Spike Threshold");
   SetIndexEmptyValue(3, EMPTY_VALUE);

   SetIndexStyle(4, DRAW_ARROW, STYLE_SOLID, 2, BuyColor);
   SetIndexArrow(4, 217);   // star - entry point
   SetIndexLabel(4, "Entry point Buy");
   SetIndexEmptyValue(4, 0.0);

   SetIndexStyle(5, DRAW_ARROW, STYLE_SOLID, 2, SellColor);
   SetIndexArrow(5, 217);
   SetIndexLabel(5, "Entry point Sell");
   SetIndexEmptyValue(5, 0.0);

   SetIndexStyle(6, DRAW_ARROW, STYLE_SOLID, 1, BuyColor);
   SetIndexArrow(6, 251);
   SetIndexLabel(6, "Marker 75% Buy");
   SetIndexEmptyValue(6, 0.0);

   SetIndexStyle(7, DRAW_ARROW, STYLE_SOLID, 1, SellColor);
   SetIndexArrow(7, 251);
   SetIndexLabel(7, "Marker 75% Sell");
   SetIndexEmptyValue(7, 0.0);

   SetIndexStyle(8, DRAW_ARROW, STYLE_SOLID, 1, BuyColor);
   SetIndexArrow(8, 251);
   SetIndexLabel(8, "Marker 50% Buy");
   SetIndexEmptyValue(8, 0.0);

   SetIndexStyle(9, DRAW_ARROW, STYLE_SOLID, 1, SellColor);
   SetIndexArrow(9, 251);
   SetIndexLabel(9, "Marker 50% Sell");
   SetIndexEmptyValue(9, 0.0);

   SetIndexStyle(10, DRAW_ARROW, STYLE_SOLID, 1, BuyColor);
   SetIndexArrow(10, 251);
   SetIndexLabel(10, "Marker 25% Buy");
   SetIndexEmptyValue(10, 0.0);

   SetIndexStyle(11, DRAW_ARROW, STYLE_SOLID, 1, SellColor);
   SetIndexArrow(11, 251);
   SetIndexLabel(11, "Marker 25% Sell");
   SetIndexEmptyValue(11, 0.0);

   IndicatorShortName("FinsburyPark2_updown");

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   for(int k = ObjectsTotal() - 1; k >= 0; k--)
   {
      string nm = ObjectName(k);
      if(StringFind(nm, OBJ_PREFIX) == 0)
         ObjectDelete(nm);
   }
}

//+------------------------------------------------------------------+
void DrawArrow(datetime t, double price, bool isBuy)
{
   string name = OBJ_PREFIX + (isBuy ? "B_" : "S_") + TimeToString(t, TIME_DATE|TIME_MINUTES|TIME_SECONDS);

   if(ObjectFind(name) >= 0)
      return;

   ObjectCreate(name, OBJ_ARROW, 0, t, price);   // window 0 = main chart
   ObjectSet(name, OBJPROP_ARROWCODE, isBuy ? 233 : 234);
   ObjectSet(name, OBJPROP_WIDTH, ArrowSize);
   ObjectSet(name, OBJPROP_COLOR, isBuy ? BuyColor : SellColor);
   ObjectSet(name, OBJPROP_BACK, false);
}

//+------------------------------------------------------------------+
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[])
{
   int minBars = MathMax(ATR_Period, MathMax(VolumeAvgPeriod, RSI_Period)) + 2;
   if(rates_total < minBars)
      return(0);

   int start   = (prev_calculated < 2) ? 1 : prev_calculated - 1;
   int loopEnd = rates_total - 1;   // never evaluate the still-forming bar -> guarantees no repainting

   if(prev_calculated < 2)
      lastSignalBar = -1000;

   for(int i = start; i < loopEnd && !IsStopped(); i++)
   {
      int shift = rates_total - 1 - i;

      //--- Baseline mean/std dev of volume
      long volSum = 0;
      int  volCount = 0;
      for(int k = 1; k <= VolumeAvgPeriod; k++)
      {
         int idx = i - k;
         if(idx < 0) break;
         volSum += tick_volume[idx];
         volCount++;
      }
      double volMean = (volCount > 0) ? (double)volSum / volCount : 0.0;

      double volStdDev = 0.0;
      if(volCount >= 2)
      {
         double sqDiffSum = 0.0;
         for(int k = 1; k <= volCount; k++)
         {
            int idx = i - k;
            double diff = (double)tick_volume[idx] - volMean;
            sqDiffSum += diff * diff;
         }
         volStdDev = MathSqrt(sqDiffSum / volCount);
      }

      double volZScore = (volStdDev > 0.0) ? ((double)tick_volume[i] - volMean) / volStdDev : 0.0;

      VolumeAvgBuffer[i] = volMean;
      ThresholdBuffer[i] = volMean + VolumeZScoreThreshold * volStdDev;

      VolumeBuyBuffer[i]  = 0.0;
      VolumeSellBuffer[i] = 0.0;
      EntryBuyBuffer[i]   = 0.0;
      EntrySellBuffer[i]  = 0.0;
      Marker75BuyBuffer[i]  = 0.0;
      Marker75SellBuffer[i] = 0.0;
      Marker50BuyBuffer[i]  = 0.0;
      Marker50SellBuffer[i] = 0.0;
      Marker25BuyBuffer[i]  = 0.0;
      Marker25SellBuffer[i] = 0.0;

      bool isVolumeSpike = (volStdDev > 0.0 && volZScore >= VolumeZScoreThreshold);

      //--- Price spike detection (ATR based)
      double atr = iATR(NULL, 0, ATR_Period, shift);
      if(atr <= 0.0) continue;

      double range = high[i] - low[i];
      double spikeRatio = range / atr;
      if(spikeRatio < SpikeMultiplier) continue;
      if(MinSpikePoints > 0.0 && range / Point < MinSpikePoints) continue;

      //--- Direction
      double upperWick = high[i] - MathMax(open[i], close[i]);
      double lowerWick = MathMin(open[i], close[i]) - low[i];
      bool isBuy = false, isSell = false;

      if(UseWickRejection)
      {
         if(range > 0.0 && lowerWick / range >= WickRejectionRatio) isBuy = true;
         else if(range > 0.0 && upperWick / range >= WickRejectionRatio) isSell = true;
      }
      else
      {
         isBuy  = close[i] > open[i];
         isSell = !isBuy;
      }
      if(!isBuy && !isSell) continue;

      //--- Optional filters
      if(UseRSIFilter)
      {
         double rsi = iRSI(NULL, 0, RSI_Period, PRICE_CLOSE, shift);
         if(isBuy  && rsi > RSI_Oversold)   continue;
         if(isSell && rsi < RSI_Overbought) continue;
      }
      if(UseEMAFilter)
      {
         double ema = iMA(NULL, 0, EMA_Period, 0, MODE_EMA, PRICE_CLOSE, shift);
         if(isBuy  && close[i] < ema) continue;
         if(isSell && close[i] > ema) continue;
      }
      if(UseSessionFilter)
      {
         int h = TimeHour(time[i]);
         bool inSession = (StartHour <= EndHour) ? (h >= StartHour && h < EndHour)
                                                  : (h >= StartHour || h < EndHour);
         if(!inSession) continue;
      }
      if(UseBBFilter)
      {
         double bbUpper = iBands(NULL, 0, BB_Period, BB_Deviation, 0, PRICE_CLOSE, MODE_UPPER, shift);
         double bbLower = iBands(NULL, 0, BB_Period, BB_Deviation, 0, PRICE_CLOSE, MODE_LOWER, shift);
         double bbWidth = bbUpper - bbLower;
         if(bbWidth <= 0.0) continue;

         if(isBuy)
         {
            double bbExcess = (bbLower - close[i]) / bbWidth;
            if(bbExcess < BB_MinExcessRatio) continue;
         }
         if(isSell)
         {
            double bbExcess = (close[i] - bbUpper) / bbWidth;
            if(bbExcess < BB_MinExcessRatio) continue;
         }
      }

      if(i - lastSignalBar < CooldownBars) continue;

      //--- Direction-colored volume bar (only drawn on genuine spike bars)
      double volValue = isVolumeSpike ? (double)tick_volume[i] : 0.0;
      if(isBuy)  VolumeBuyBuffer[i]  = volValue;
      if(isSell) VolumeSellBuffer[i] = volValue;

      double arrowPrice = isBuy ? low[i] - ArrowGapPoints * Point : high[i] + ArrowGapPoints * Point;
      DrawArrow(time[i], arrowPrice, isBuy);
      lastSignalBar = i;

      //--- Stacked markers + entry star, only on volume-confirmed spikes
      if(isVolumeSpike && volValue > 0.0)
      {
         if(isBuy)
         {
            EntryBuyBuffer[i]     = volValue;
            Marker75BuyBuffer[i]  = volValue * 0.75;
            Marker50BuyBuffer[i]  = volValue * 0.50;
            Marker25BuyBuffer[i]  = volValue * 0.25;
         }
         else
         {
            EntrySellBuffer[i]     = volValue;
            Marker75SellBuffer[i]  = volValue * 0.75;
            Marker50SellBuffer[i]  = volValue * 0.50;
            Marker25SellBuffer[i]  = volValue * 0.25;
         }

         if(AlertOnConfirmed && i == loopEnd - 1)
            DoAlert(isBuy, spikeRatio, volZScore);
      }
   }

   return(rates_total);
}

//+------------------------------------------------------------------+
void DoAlert(bool buySignal, double spikeRatio, double volZScore)
{
   string message = StringFormat("%s %s - FinsburyPark2: %s CONFIRMED (range %.1fx ATR, volume z=%.1f)",
                                  Symbol(), EnumToString((ENUM_TIMEFRAMES)Period()),
                                  buySignal ? "BUY" : "SELL", spikeRatio, volZScore);
   Alert(message);
   if(EmailAlert) SendMail("FinsburyPark2 - Confirmed Signal", message);
   if(PushAlert)  SendNotification(message);
}
//+------------------------------------------------------------------+
