//+------------------------------------------------------------------+
//|                                    Fibonacci Volatility Band.mq5 |
//|                                             Abioye Israel Pelumi |
//|                                              https://Algoyin.com |
//+------------------------------------------------------------------+
#property copyright "Abioye Israel Pelumi"
#property link      "https://Algoyin.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 12
#property indicator_plots   9

//--- plot 0: Fib Top 3
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrCrimson
#property indicator_width1  1

//--- plot 1: Fib Top 2
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrCrimson
#property indicator_width2  1

//--- plot 2: Fib Top 1
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrCrimson
#property indicator_width3  1

//--- plot 3: Middle SMMA (color-changing: up vs down)
#property indicator_type4   DRAW_COLOR_LINE
#property indicator_color4  clrDarkGray,clrSilver
#property indicator_width4  2

//--- plot 4: Fib Bottom 1
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrLime
#property indicator_width5  1

//--- plot 5: Fib Bottom 2
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrLime
#property indicator_width6  1

//--- plot 6: Fib Bottom 3
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrLime
#property indicator_width7  1

//--- plot 7: Top fill (between ratio2 and ratio3)
#property indicator_type8   DRAW_FILLING
#property indicator_color8  clrCrimson

//--- plot 8: Bottom fill (between ratio2 and ratio3)
#property indicator_type9   DRAW_FILLING
#property indicator_color9  clrLime

//--- ＩＮＰＵＴＳ
input int                 InpLookbackBars = 300;         // Lookback (bars to process, 0 = all history)
input int                 InpExtend       = 30;          // Extend Bands (bars)
input int                 InpPeriod       = 20;          // Period
input ENUM_APPLIED_PRICE  InpPrice        = PRICE_CLOSE; // Source
input double              InpWidth        = 1.0;         // Width
input bool                InpHideFib      = false;       // Hide Fibonacci Lines
input double              InpFibRatio1    = 1.618;       // Fibonacci Ratio 1
input double              InpFibRatio2    = 2.618;       // Fibonacci Ratio 2
input double              InpFibRatio3    = 4.236;       // Fibonacci Ratio 3

//--- internal working arrays (not plotted)
double AtrRaw[],SmmaAtr[],SmmaPrice[],SlopeArr[];
bool   PivHigh[],PivLow[],MidTrend[];

//--- indicator buffers
double Top3[],Top2[],Top1[];
double Mid[],MidColor[];
double Bot1[],Bot2[],Bot3[];
double FillTopHi[],FillTopLo[];
double FillBotHi[],FillBotLo[];

int    g_atrHandle=INVALID_HANDLE;

//+------------------------------------------------------------------+
//| Simple average of src[] over `length` bars ending at index i.    |
//| Used to seed the Wilder/SMMA smoothing on its very first value   |
//| (returns EMPTY_VALUE if there isn't enough history yet).         |
//+------------------------------------------------------------------+
double SmaAt(const double &src[],int i,int length)
  {
   if(i<length-1)
      return(EMPTY_VALUE);
   double sum=0;
   for(int k=i-length+1;k<=i;k++)
      sum+=src[k];
   return(sum/length);
  }

//+------------------------------------------------------------------+
//| Returns the selected applied price value for a specific bar       |
//+------------------------------------------------------------------+
double GetPrice(ENUM_APPLIED_PRICE ap,const double &open[],const double &high[],
                const double &low[],const double &close[],int i)
  {
   switch(ap)
     {
      case PRICE_OPEN:
         return(open[i]);
      case PRICE_HIGH:
         return(high[i]);
      case PRICE_LOW:
         return(low[i]);
      case PRICE_MEDIAN:
         return((high[i]+low[i])/2.0);
      case PRICE_TYPICAL:
         return((high[i]+low[i]+close[i])/3.0);
      case PRICE_WEIGHTED:
         return((high[i]+low[i]+2*close[i])/4.0);
      default:
         return(close[i]);
     }
  }

//+------------------------------------------------------------------+
//| Simple average of the applied price over `length` bars ending at i|
//+------------------------------------------------------------------+
double SmaPriceAt(ENUM_APPLIED_PRICE ap,const double &open[],const double &high[],
                  const double &low[],const double &close[],int i,int length)
  {
   if(i<length-1)
      return(EMPTY_VALUE);
   double sum=0;
   for(int k=i-length+1;k<=i;k++)
      sum+=GetPrice(ap,open,high,low,close,k);
   return(sum/length);
  }
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,Top3,INDICATOR_DATA);
   SetIndexBuffer(1,Top2,INDICATOR_DATA);
   SetIndexBuffer(2,Top1,INDICATOR_DATA);
   SetIndexBuffer(3,Mid,INDICATOR_DATA);
   SetIndexBuffer(4,MidColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(5,Bot1,INDICATOR_DATA);
   SetIndexBuffer(6,Bot2,INDICATOR_DATA);
   SetIndexBuffer(7,Bot3,INDICATOR_DATA);
   SetIndexBuffer(8,FillTopHi,INDICATOR_DATA);
   SetIndexBuffer(9,FillTopLo,INDICATOR_DATA);
   SetIndexBuffer(10,FillBotHi,INDICATOR_DATA);
   SetIndexBuffer(11,FillBotLo,INDICATOR_DATA);


   ArraySetAsSeries(Top3,false);
   ArraySetAsSeries(Top2,false);
   ArraySetAsSeries(Top1,false);
   ArraySetAsSeries(Mid,false);
   ArraySetAsSeries(MidColor,false);
   ArraySetAsSeries(Bot1,false);
   ArraySetAsSeries(Bot2,false);
   ArraySetAsSeries(Bot3,false);
   ArraySetAsSeries(FillTopHi,false);
   ArraySetAsSeries(FillTopLo,false);
   ArraySetAsSeries(FillBotHi,false);
   ArraySetAsSeries(FillBotLo,false);

   g_atrHandle=iATR(_Symbol,_Period,200);
   if(g_atrHandle==INVALID_HANDLE)
     {
      Print("Failed to create ATR(200) handle");
      return(INIT_FAILED);
     }

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//---
   int minBars=310; // ATR(200) + SMMA(100) warm-up
   if(rates_total<minBars)
      return(0);

   ArrayResize(AtrRaw,rates_total);
   ArrayResize(SmmaAtr,rates_total);
   ArrayResize(SmmaPrice,rates_total);
   ArrayResize(SlopeArr,rates_total);
   ArrayResize(PivHigh,rates_total);
   ArrayResize(PivLow,rates_total);
   ArrayResize(MidTrend,rates_total);

//--- Recalculate only the current forming bar and newly added bars to improve performance by avoiding full history recalculation on every tick.
   int limit = (prev_calculated==0) ? 0 : prev_calculated-1;
   if(limit<0)
      limit=0;

//--- Limit recalculation to the selected lookback period while keeping enough historical bars for ATR and SMMA warm-up.
   int warmup=309;
   int windowStart=0;
   if(InpLookbackBars>0)
      windowStart=MathMax(0,rates_total-InpLookbackBars-warmup);
   int calcFrom=MathMax(limit,windowStart);

   if(prev_calculated==0)
     {
      //--- blank everything first so bars outside the lookback window don't
      //--- show stale/garbage buffer contents; cheap - simple array fills.
      ArrayInitialize(Top3,EMPTY_VALUE);
      ArrayInitialize(Top2,EMPTY_VALUE);
      ArrayInitialize(Top1,EMPTY_VALUE);
      ArrayInitialize(Mid,EMPTY_VALUE);
      ArrayInitialize(MidColor,0);
      ArrayInitialize(Bot1,EMPTY_VALUE);
      ArrayInitialize(Bot2,EMPTY_VALUE);
      ArrayInitialize(Bot3,EMPTY_VALUE);
      ArrayInitialize(FillTopHi,EMPTY_VALUE);
      ArrayInitialize(FillTopLo,EMPTY_VALUE);
      ArrayInitialize(FillBotHi,EMPTY_VALUE);
      ArrayInitialize(FillBotLo,EMPTY_VALUE);
      ArrayInitialize(SmmaAtr,EMPTY_VALUE);
      ArrayInitialize(SmmaPrice,EMPTY_VALUE);
     }

//--- Fetch required ATR values with extra history for SMMA initialization and convert them to chronological order.
   int atrFetchFrom = MathMax(0,calcFrom-100);
   int copyCount = rates_total-atrFetchFrom;
   double atrSeries[];
   ArraySetAsSeries(atrSeries,true);
   if(CopyBuffer(g_atrHandle,0,0,copyCount,atrSeries)<=0)
      return(0);

   ArraySetAsSeries(AtrRaw,false);
   for(int k=0; k<copyCount; k++)
     {
      AtrRaw[rates_total-1-k]=atrSeries[k];
     }

   for(int i=calcFrom;i<rates_total;i++)
     {

      //--- SMMA of ATR(200), length 100
      if(i<199+100)
         SmmaAtr[i]=EMPTY_VALUE;
      else
        {
         if(i==199+100 || SmmaAtr[i-1]==EMPTY_VALUE)
            SmmaAtr[i]=SmaAt(AtrRaw,i,100);
         else
            SmmaAtr[i]=(SmmaAtr[i-1]*99+AtrRaw[i])/100.0;
        }

      //--- SMMA of price, length InpPeriod
      double px=GetPrice(InpPrice,open,high,low,close,i);
      if(i<InpPeriod-1)
         SmmaPrice[i]=EMPTY_VALUE;
      else
        {
         if(i==InpPeriod-1 || SmmaPrice[i-1]==EMPTY_VALUE)
            SmmaPrice[i]=SmaPriceAt(InpPrice,open,high,low,close,i,InpPeriod);
         else
            SmmaPrice[i]=(SmmaPrice[i-1]*(InpPeriod-1)+px)/InpPeriod;
        }

      if(SmmaAtr[i]==EMPTY_VALUE || SmmaPrice[i]==EMPTY_VALUE)
        {
         Top3[i]=Top2[i]=Top1[i]=EMPTY_VALUE;
         Bot1[i]=Bot2[i]=Bot3[i]=EMPTY_VALUE;
         Mid[i]=EMPTY_VALUE;
         MidColor[i]=0;
         FillTopHi[i]=FillTopLo[i]=FillBotHi[i]=FillBotLo[i]=EMPTY_VALUE;
         SlopeArr[i]=0;
         MidTrend[i]=(i>0?MidTrend[i-1]:true);
         continue;
        }

      double r1=SmmaAtr[i]*InpFibRatio1*InpWidth;
      double r2=SmmaAtr[i]*InpFibRatio2*InpWidth;
      double r3=SmmaAtr[i]*InpFibRatio3*InpWidth;

      Top1[i]=SmmaPrice[i]+r1;
      Top2[i]=SmmaPrice[i]+r2;
      Top3[i]=SmmaPrice[i]+r3;
      Bot1[i]=SmmaPrice[i]-r1;
      Bot2[i]=SmmaPrice[i]-r2;
      Bot3[i]=SmmaPrice[i]-r3;
      Mid[i]=SmmaPrice[i];

      MidTrend[i]=(i>0 && SmmaPrice[i-1]!=EMPTY_VALUE) ? (SmmaPrice[i]>SmmaPrice[i-1]) : true;
      MidColor[i]=MidTrend[i]?0:1;

      FillTopHi[i]=Top3[i];
      FillTopLo[i]=Top2[i];
      FillBotHi[i]=Bot2[i];
      FillBotLo[i]=Bot3[i];

     }

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
