//+------------------------------------------------------------------+
//|                                                 Accumulation.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
#property description "Accumulation/Distribution"
#property strict

//--- indicator settings
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_color1  clrLightSeaGreen
#property indicator_width1  2
#property indicator_color2  clrDodgerBlue
#property indicator_color3  clrSandyBrown
#property indicator_color4  clrDimGray
#property indicator_style4  STYLE_DOT

enum enLevelType
{
   lev_float, // Floating levels
   lev_quan   // Quantile levels
};
enum enMaTypes
{
   ma_sma,    // Simple moving average
   ma_ema,    // Exponential moving average
   ma_smma,   // Smoothed MA
   ma_lwma    // Linear weighted MA
};

extern enLevelType     FlType               = lev_quan;       // Levels type
extern int             FlPeriod             = 35;             // Levels period
extern double          FlUp                 = 80;             // Upper level %
extern double          FlDown               = 20;             // Lower level %
extern int             PriceSmoothing       = 0;              // Price smothing (<=1 no smoothing)
extern enMaTypes       PriceSmoothingMethod = ma_ema;         // Price smothing method



//--- buffers
double ExtADbuffer[], buffer1[],buffer2[],buffer3[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit(void)
{
   IndicatorBuffers(4);
   SetIndexBuffer(0,ExtADbuffer);
   SetIndexBuffer(1, buffer1);
   SetIndexBuffer(2, buffer2);
   SetIndexBuffer(3, buffer3);
   IndicatorShortName(" A/D ("+(string)FlPeriod+","+(string)PriceSmoothing+" "+averageName(PriceSmoothingMethod)+")");
}
void OnDeinit(const int reason) { }
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 counted_bars = prev_calculated;
      if(counted_bars < 0) return(-1);
      if(counted_bars > 0) counted_bars--;
           int limit=MathMin(rates_total-counted_bars,rates_total-1);
   
   //
   //
   //
   //
   //

   for (int i=limit; i>=0 && !_StopFlag; i--)
   {
      double prices[3];
             prices[0] = iCustomMa(PriceSmoothingMethod,close[i],PriceSmoothing,i,0);
             prices[1] = iCustomMa(PriceSmoothingMethod,low[i]  ,PriceSmoothing,i,1);
             prices[2] = iCustomMa(PriceSmoothingMethod,high[i] ,PriceSmoothing,i,2);
             ArraySort(prices);
               double diff=prices[2]-prices[0];
               ExtADbuffer[i]= (diff!=0) ? (double)tick_volume[i]*((prices[1]-prices[0])-(prices[2]-prices[1]))/diff : 0;
               if(i<rates_total-1)
                  ExtADbuffer[i]+=ExtADbuffer[i+1];
         
            if (FlType==lev_float)
            {
               double min   = ExtADbuffer[ArrayMinimum(ExtADbuffer,FlPeriod,i)];
               double max   = ExtADbuffer[ArrayMaximum(ExtADbuffer,FlPeriod,i)];
               double range = max-min;
                  buffer1[i] = min+FlUp  *range/100.0;
                  buffer2[i] = min+FlDown*range/100.0;
                  buffer3[i] = min+0.5*range;
            }
            else
            {
                  buffer1[i] = iQuantile(ExtADbuffer[i],FlPeriod, FlUp            ,i,Bars);
                  buffer2[i] = iQuantile(ExtADbuffer[i],FlPeriod, FlDown          ,i,Bars);
                  buffer3[i] = iQuantile(ExtADbuffer[i],FlPeriod,(FlUp+FlDown)/2.0,i,Bars);
            }  
     }
   return(rates_total);
}
  
//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

#define _maInstances 3
#define _maWorkBufferx1 1*_maInstances
#define _maWorkBufferx2 2*_maInstances

string averageName(int mode)
{
   switch (mode)
   {
      case ma_sma   : return("SMA");
      case ma_ema   : return("EMA");
      case ma_smma  : return("SMMA");
      case ma_lwma  : return("LWMA");
   }
   return("");
}
double iCustomMa(int mode, double price, double length, int r, int instanceNo=0)
{
   r = Bars-r-1;
   switch (mode)
   {
      case ma_sma   : return(iSma(price,(int)length,r,instanceNo));
      case ma_ema   : return(iEma(price,length,r,instanceNo));
      case ma_smma  : return(iSmma(price,length,r,instanceNo));
      case ma_lwma  : return(iLwma(price,(int)length,r,instanceNo));
      default       : return(price);
   }
}

//
//
//
//
//

double workSma[][_maWorkBufferx2];
double iSma(double price, int period, int r, int instanceNo=0)
{
   if (period<=1) return(price);
   if (ArrayRange(workSma,0)!= Bars) ArrayResize(workSma,Bars); instanceNo *= 2; int k;

   //
   //
   //
   //
   //
      
   workSma[r][instanceNo+0] = price;
   workSma[r][instanceNo+1] = price; for(k=1; k<period && (r-k)>=0; k++) workSma[r][instanceNo+1] += workSma[r-k][instanceNo+0];  
   workSma[r][instanceNo+1] /= 1.0*k;
   return(workSma[r][instanceNo+1]);
}

//
//
//
//
//

double workEma[][_maWorkBufferx1];
double iEma(double price, double period, int r, int instanceNo=0)
{
   if (ArrayRange(workEma,0)!= Bars) ArrayResize(workEma,Bars);

   //
   //
   //
   //
   //
      
   workEma[r][instanceNo] = price;
   if (r>0 && period>1)
          workEma[r][instanceNo] = workEma[r-1][instanceNo]+(2.0 / (1.0+period))*(price-workEma[r-1][instanceNo]);
   return(workEma[r][instanceNo]);
}

//
//
//
//
//

double workSmma[][_maWorkBufferx1];
double iSmma(double price, double period, int r, int instanceNo=0)
{
   if (period<=1) return(price);
   if (ArrayRange(workSmma,0)!= Bars) ArrayResize(workSmma,Bars);

   //
   //
   //
   //
   //

   workSmma[r][instanceNo] = price;
   if (r>0 && period>1)
          workSmma[r][instanceNo] = workSmma[r-1][instanceNo]+(price-workSmma[r-1][instanceNo])/period;
   return(workSmma[r][instanceNo]);
}

//
//
//
//
//

double workLwma[][_maWorkBufferx1];
double iLwma(double price, double period, int r, int instanceNo=0)
{
   if (ArrayRange(workLwma,0)!= Bars) ArrayResize(workLwma,Bars);
   
   //
   //
   //
   //
   //
   
   period = MathMax(period,1);
   workLwma[r][instanceNo] = price;
      double sumw = period;
      double sum  = period*price;

      for(int k=1; k<period && (r-k)>=0; k++)
      {
         double weight = period-k;
                sumw  += weight;
                sum   += weight*workLwma[r-k][instanceNo];  
      }             
      return(sum/sumw);
}
  
//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

#define _quantileInstances 1
double _sortQuant[];
double _workQuant[][_quantileInstances];

double iQuantile(double value, int period, double qp, int i, int bars, int instanceNo=0)
{
   if (period<1) return(value);
   if (ArrayRange(_workQuant,0)!=bars) ArrayResize(_workQuant,bars); 
   if (ArraySize(_sortQuant)!=period)  ArrayResize(_sortQuant,period); 
            i=bars-i-1; _workQuant[i][instanceNo]=value;
            int k=0; for (; k<period && (i-k)>=0; k++) _sortQuant[k] = _workQuant[i-k][instanceNo];
                     for (; k<period            ; k++) _sortQuant[k] = 0;
                     ArraySort(_sortQuant);

   //
   //
   //
   //
   //
   
   double index = (period-1.0)*qp/100.00;
   int    ind   = (int)index;
   double delta = index - ind;
   if (ind == NormalizeDouble(index,5))
         return(            _sortQuant[ind]);
   else  return((1.0-delta)*_sortQuant[ind]+delta*_sortQuant[ind+1]);
}   

