//+------------------------------------------------------------------+
//| FXP_VWAPDeviationBands.mq5                                       |
//| FXPIPS ONLINE - https://fxpips.online/mt5-indicators/vwap-deviation-bands/
//|                                                                  |
//| 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/vwap-deviation-bands/"
#property version   "1.00"
#property description "Daily or weekly VWAP with 1, 2 and 3 standard-deviation bands."
#property description "Resets at the start of each period (broker server time)."
#property description "Uses tick volume when real volume is not available."

#property indicator_chart_window
#property indicator_buffers 10
#property indicator_plots   7

#property indicator_label1  "VWAP"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGold
#property indicator_width1  2

#property indicator_label2  "Upper band 1"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrSilver
#property indicator_style2  STYLE_DOT

#property indicator_label3  "Lower band 1"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrSilver
#property indicator_style3  STYLE_DOT

#property indicator_label4  "Upper band 2"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrDarkOrange
#property indicator_style4  STYLE_DASH

#property indicator_label5  "Lower band 2"
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrDarkOrange
#property indicator_style5  STYLE_DASH

#property indicator_label6  "Upper band 3"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrCrimson
#property indicator_style6  STYLE_SOLID

#property indicator_label7  "Lower band 3"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrCrimson
#property indicator_style7  STYLE_SOLID

enum ENUM_FXP_VWAP_PERIOD
{
   FXP_VWAP_DAILY  = 0, // Daily
   FXP_VWAP_WEEKLY = 1  // Weekly
};

input group "VWAP"
input ENUM_FXP_VWAP_PERIOD InpPeriod = FXP_VWAP_DAILY; // Reset period
input double InpBand1   = 1.0;  // Band 1 (standard deviations)
input double InpBand2   = 2.0;  // Band 2 (standard deviations)
input double InpBand3   = 3.0;  // Band 3 (standard deviations)
input bool   InpShowBand1 = true; // Show band 1
input bool   InpShowBand2 = true; // Show band 2
input bool   InpShowBand3 = true; // Show band 3
input group "Alerts"
input int    InpAlertBand = 2;     // Alert when a bar closes beyond band (1, 2 or 3)
input bool   InpAlerts    = false; // Popup alert
input bool   InpPush      = false; // Push notification

double gVwap[], gU1[], gL1[], gU2[], gL2[], gU3[], gL3[], gSPV[], gSV[], gSP2V[];
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 VWAP Bands " + _Symbol + " " + TfName() + ": " + text;
   if(InpAlerts) Alert(msg);
   if(InpPush)   SendNotification(msg);
}

// Period identifier from server time. Weeks start on Sunday so a Sunday open belongs to the new week.
long PeriodId(const datetime t)
{
   long d = (long)t / 86400;          // 1970-01-01 was a Thursday
   if(InpPeriod == FXP_VWAP_WEEKLY) return((d + 4) / 7);
   return(d);
}

int OnInit()
{
   SetIndexBuffer(0, gVwap, INDICATOR_DATA);
   SetIndexBuffer(1, gU1,   INDICATOR_DATA);
   SetIndexBuffer(2, gL1,   INDICATOR_DATA);
   SetIndexBuffer(3, gU2,   INDICATOR_DATA);
   SetIndexBuffer(4, gL2,   INDICATOR_DATA);
   SetIndexBuffer(5, gU3,   INDICATOR_DATA);
   SetIndexBuffer(6, gL3,   INDICATOR_DATA);
   SetIndexBuffer(7, gSPV,  INDICATOR_CALCULATIONS);
   SetIndexBuffer(8, gSV,   INDICATOR_CALCULATIONS);
   SetIndexBuffer(9, gSP2V, INDICATOR_CALCULATIONS);
   ArraySetAsSeries(gVwap, false); ArraySetAsSeries(gU1, false); ArraySetAsSeries(gL1, false);
   ArraySetAsSeries(gU2, false);   ArraySetAsSeries(gL2, false); ArraySetAsSeries(gU3, false);
   ArraySetAsSeries(gL3, false);   ArraySetAsSeries(gSPV, false); ArraySetAsSeries(gSV, false);
   ArraySetAsSeries(gSP2V, false);

   for(int p = 0; p < 7; p++)
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, InpShowBand1 ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, InpShowBand1 ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(3, PLOT_DRAW_TYPE, InpShowBand2 ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(4, PLOT_DRAW_TYPE, InpShowBand2 ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(5, PLOT_DRAW_TYPE, InpShowBand3 ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(6, PLOT_DRAW_TYPE, InpShowBand3 ? DRAW_LINE : DRAW_NONE);

   IndicatorSetString(INDICATOR_SHORTNAME, InpPeriod == FXP_VWAP_WEEKLY ? "FXP VWAP Bands (Weekly)" : "FXP VWAP Bands (Daily)");
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   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 < 3) return(0);
   ArraySetAsSeries(time, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(close, false);
   ArraySetAsSeries(tick_volume, false);
   ArraySetAsSeries(volume, false);

   int start;
   if(prev_calculated <= 0 || prev_calculated > rates_total)
   {
      start = 0;
      gLastAlertBar = time[rates_total - 1];
   }
   else
      start = prev_calculated - 1;

   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      double tp = (high[i] + low[i] + close[i]) / 3.0;
      double v  = (volume[i] > 0) ? (double)volume[i] : (double)tick_volume[i];
      if(v <= 0.0) v = 1.0;

      bool reset = (i == 0) || (PeriodId(time[i]) != PeriodId(time[i - 1]));
      if(reset)
      {
         gSPV[i]  = tp * v;
         gSV[i]   = v;
         gSP2V[i] = tp * tp * v;
      }
      else
      {
         gSPV[i]  = gSPV[i - 1]  + tp * v;
         gSV[i]   = gSV[i - 1]   + v;
         gSP2V[i] = gSP2V[i - 1] + tp * tp * v;
      }

      double vwap = gSPV[i] / gSV[i];
      double var  = gSP2V[i] / gSV[i] - vwap * vwap;
      double sd   = (var > 0.0) ? MathSqrt(var) : 0.0;

      gVwap[i] = vwap;
      gU1[i] = vwap + InpBand1 * sd;  gL1[i] = vwap - InpBand1 * sd;
      gU2[i] = vwap + InpBand2 * sd;  gL2[i] = vwap - InpBand2 * sd;
      gU3[i] = vwap + InpBand3 * sd;  gL3[i] = vwap - InpBand3 * sd;
   }

   if(prev_calculated > 0 && InpAlertBand >= 1 && InpAlertBand <= 3)
   {
      int b = rates_total - 2;
      if(PeriodId(time[b]) == PeriodId(time[b - 1]))
      {
         double up = gU2[b], dn = gL2[b], upP = gU2[b - 1], dnP = gL2[b - 1];
         if(InpAlertBand == 1) { up = gU1[b]; dn = gL1[b]; upP = gU1[b - 1]; dnP = gL1[b - 1]; }
         if(InpAlertBand == 3) { up = gU3[b]; dn = gL3[b]; upP = gU3[b - 1]; dnP = gL3[b - 1]; }
         if(close[b] > up && close[b - 1] <= upP)
            Notify(StringFormat("close above VWAP +%d SD band", InpAlertBand), time[rates_total - 1]);
         if(close[b] < dn && close[b - 1] >= dnP)
            Notify(StringFormat("close below VWAP -%d SD band", InpAlertBand), time[rates_total - 1]);
      }
   }
   return(rates_total);
}
//+------------------------------------------------------------------+
