//+------------------------------------------------------------------+
//|                                             Choppiness index.mq4 |
//|                                                           mladen |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "mladen"
#property link      "mladenfx@gmail.com"

#property indicator_separate_window
#property indicator_buffers 6
#property strict


//
//
//
//
//

enum enMaTypes
{
   ma_sma,     // Simple moving average
   ma_ema,     // Exponential moving average
   ma_smma,    // Smoothed MA
   ma_lwma,    // Linear weighted MA
};

input int              CILength     = 14;                // Chopiness index period
input double           SmthPer      = 7;                 // Chopiness ma period
input enMaTypes        MaMethod     = ma_sma;            // Chopiness ma moving average type
input double           LevelUp      = 61.8;              // Level up
input double           LevelDn      = 38.2;              // Level dowm
input color            ColorNu      = clrSilver;         // Color for nuetral
input color            ColorUp      = clrSandyBrown;     // Color for chopy
input color            ColorDn      = clrLimeGreen;      // Color for trending
input color            ColorSig     = clrRed;            // Signal color
input int              LineWidth    = 2;                 // Main line width
input color            levClr       = clrMediumOrchid;   // Levels color
input ENUM_LINE_STYLE  levSty       = STYLE_DOT;         // Levels style

double chop[],chopDa[],chopDb[],chopUa[],chopUb[],chMa[],trend[];


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
//
//
//
//
//

int OnInit() 
{
   IndicatorBuffers(7);
   SetIndexBuffer(0, chop,  INDICATOR_DATA); SetIndexStyle(0,DRAW_LINE,EMPTY,LineWidth,ColorNu);
   SetIndexBuffer(1, chopUa,INDICATOR_DATA); SetIndexStyle(1,DRAW_LINE,EMPTY,LineWidth,ColorUp);
   SetIndexBuffer(2, chopUb,INDICATOR_DATA); SetIndexStyle(2,DRAW_LINE,EMPTY,LineWidth,ColorUp);
   SetIndexBuffer(3, chopDa,INDICATOR_DATA); SetIndexStyle(3,DRAW_LINE,EMPTY,LineWidth,ColorDn);
   SetIndexBuffer(4, chopDb,INDICATOR_DATA); SetIndexStyle(4,DRAW_LINE,EMPTY,LineWidth,ColorDn);
   SetIndexBuffer(5, chMa,  INDICATOR_DATA); SetIndexStyle(5,DRAW_LINE,EMPTY,LineWidth,ColorSig);
   SetIndexBuffer(6, trend);
   
   IndicatorSetInteger(INDICATOR_LEVELS,2);
   IndicatorSetDouble( INDICATOR_LEVELVALUE,0,LevelUp);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE,0,levSty);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR,0,levClr);  
   IndicatorSetDouble( INDICATOR_LEVELVALUE,1,LevelDn);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE,1,levSty);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR,1,levClr);
   
   IndicatorSetString(INDICATOR_SHORTNAME,"Choppiness index ("+(string)CILength+","+(string)SmthPer+")");
return(INIT_SUCCEEDED);
}
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[])
{
   double _log = log(CILength)/100.00;
   int i=rates_total-prev_calculated+1; if (i>=rates_total) i=rates_total-1;

   //
   //
   //

   if (trend[i] ==-1) CleanPoint(i,chopDa,chopDb);
   if (trend[i] == 1) CleanPoint(i,chopUa,chopUb);
   for (; i>=0 && !_StopFlag; i--)
   {  
      double atrSum = 0.00;
      double maxHig = high[i];
      double minLow =  low[i];
               
         for (int k = 0; k < CILength && (i+k+1)<rates_total; k++)
         {
            atrSum += fmax(high[i+k],close[i+k+1])-fmin(low[i+k],close[i+k+1]);
            maxHig  = fmax(maxHig,fmax(high[i+k],close[i+k+1]));
            minLow  = fmin(minLow,fmin( low[i+k],close[i+k+1]));
         }
         chop[i] = (maxHig!=minLow) ? log(atrSum/(maxHig-minLow))/_log : 0;
         chMa[i] = iCustomMa(MaMethod,chop[i],SmthPer,i,rates_total);  
         trend[i]  = (chop[i]>LevelUp) ? 1 : (chop[i]<LevelDn) ? -1 : 0;
         chopUa[i] = chopUb[i] = EMPTY_VALUE; if (trend[i] == 1) PlotPoint(i,chopUa,chopUb,chop);
         chopDa[i] = chopDb[i] = EMPTY_VALUE; if (trend[i] ==-1) PlotPoint(i,chopDa,chopDb,chop);
   }         
return(rates_total);
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

void CleanPoint(int i,double& first[],double& second[])
{
   if (i>=Bars-3) return;
   if ((second[i]  != EMPTY_VALUE) && (second[i+1] != EMPTY_VALUE))
        second[i+1] = EMPTY_VALUE;
   else
      if ((first[i] != EMPTY_VALUE) && (first[i+1] != EMPTY_VALUE) && (first[i+2] == EMPTY_VALUE))
          first[i+1] = EMPTY_VALUE;
}

void PlotPoint(int i,double& first[],double& second[],double& from[])
{
   if (i>=Bars-2) return;
   if (first[i+1] == EMPTY_VALUE)
      if (first[i+2] == EMPTY_VALUE) 
            { first[i]  = from[i]; first[i+1]  = from[i+1]; second[i] = EMPTY_VALUE; }
      else  { second[i] = from[i]; second[i+1] = from[i+1]; first[i]  = EMPTY_VALUE; }
   else     { first[i]  = from[i];                          second[i] = EMPTY_VALUE; }
}

//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

string getAverageName(int method)
{
      switch(method)
      {
         case ma_ema:    return("EMA");
         case ma_lwma:   return("LWMA");
         case ma_sma:    return("SMA");
         case ma_smma:   return("SMMA");
      }
return("");      
}

//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

#define _maInstances 6
#define _maWorkBufferx1 1*_maInstances
#define _maWorkBufferx2 2*_maInstances
#define _maWorkBufferx3 3*_maInstances

double iCustomMa(int mode, double price, double length, int r, int bars, int instanceNo=0)
{
   r = bars-r-1;
   switch (mode)
   {
      case ma_sma   : return(iSma(price,(int)ceil(length),r,bars,instanceNo));
      case ma_ema   : return(iEma(price,length,r,bars,instanceNo));
      case ma_smma  : return(iSmma(price,(int)ceil(length),r,bars,instanceNo));
      case ma_lwma  : return(iLwma(price,(int)ceil(length),r,bars,instanceNo));
      default       : return(price);
   }
}

//
//
//
//
//

double workSma[][_maWorkBufferx1];
double iSma(double price, int period, int r, int _bars, int instanceNo=0)
{
   if (ArrayRange(workSma,0)!= _bars) ArrayResize(workSma,_bars);

   workSma[r][instanceNo+0] = price;
   double avg = price; int k=1;  for(; k<period && (r-k)>=0; k++) avg += workSma[r-k][instanceNo+0];  
   return(avg/(double)k);
}

//
//
//
//
//

double workEma[][_maWorkBufferx1];
double iEma(double price, double period, int r, int _bars, 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 _bars, int instanceNo=0)
{
   if (ArrayRange(workSmma,0)!= _bars) ArrayResize(workSmma,_bars);

   workSmma[r][instanceNo] = price;
   if (r>1 && 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 _bars, int instanceNo=0)
{
   if (ArrayRange(workLwma,0)!= _bars) ArrayResize(workLwma,_bars);
   
   workLwma[r][instanceNo] = price; if (period<=1) return(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);
}
