//------------------------------------------------------------------
#property copyright "www.forex-TSD.com "
#property link      "www.forex-tsd.com/"
//------------------------------------------------------------------

#property indicator_separate_window
#property indicator_buffers 4
#property indicator_color1  clrLimeGreen
#property indicator_color2  clrPaleVioletRed
#property indicator_color3  clrPaleVioletRed
#property indicator_color4  clrRed
#property indicator_width1  2
#property indicator_width2  2
#property indicator_width3  2
#property indicator_style4  STYLE_DOT
#property indicator_minimum -1
#property indicator_maximum 101
#property indicator_level1  80
#property indicator_level2  20
#property indicator_levelcolor clrDarkGray

extern ENUM_TIMEFRAMES    TimeFrame     = PERIOD_CURRENT;
extern int                KPeriod       = 14;
extern int                DPeriod       = 3;
extern int                Slowing       = 3;
extern int                Price         = PRICE_CLOSE;
extern ENUM_MA_METHOD     MaMethod      = MODE_EMA; 
extern int                ZeroLagPeriod = 21;
extern bool               Interpolate   = true;
extern bool               ColorOnSlope  = true;

double Stochastic[], StochasticDa[], StochasticDb[], Stochastic_Signal[], state[];
string indicatorFileName;
bool   returnBars;
//------------------------------------------------------------------
int init()
{
   IndicatorBuffers(5);
   SetIndexBuffer(0, Stochastic);
   SetIndexBuffer(1, StochasticDa);
   SetIndexBuffer(2, StochasticDb);
   SetIndexBuffer(3, Stochastic_Signal);
   SetIndexBuffer(4, state);
   
   //
   //
   //
   //
   //
       
   indicatorFileName = WindowExpertName();
   returnBars        = TimeFrame==-99;
   TimeFrame         = MathMax(TimeFrame,_Period);
          
   //
   //
   //
   //
   //

   IndicatorShortName(timeFrameToString(TimeFrame)+ " ZeroLag Stochastic ("+KPeriod+","+DPeriod+","+Slowing+", ZL "+DoubleToStr(ZeroLagPeriod,1)+ ")");
return(0);
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

int start()
{
   int i,counted_bars=IndicatorCounted();
      if (counted_bars<0) return(-1);
      if(counted_bars>0) counted_bars--;
         int limit = MathMin(Bars-counted_bars,Bars-1);
         if (returnBars) { Stochastic[0] = limit+1; return(0); }

         //
         //
         //
         //
         //
         
         if (TimeFrame == Period())
         {
           if (state[limit]==-1) CleanPoint(limit,StochasticDa,StochasticDb);
           for (i=limit; i>=0 ; i--)
           {
              Stochastic[i]        = iZeroLag(iStochastic(NULL,0,KPeriod,DPeriod,Slowing,MaMethod,Price,MODE_MAIN,i),  ZeroLagPeriod,i,0);
              Stochastic_Signal[i] = iZeroLag(iStochastic(NULL,0,KPeriod,DPeriod,Slowing,MaMethod,Price,MODE_SIGNAL,i),ZeroLagPeriod,i,1);
              StochasticDa[i]      = EMPTY_VALUE;
              StochasticDb[i]      = EMPTY_VALUE;
              state[i] = state[i+1];
               if (ColorOnSlope)
               {
                  if (Stochastic[i]>Stochastic[i+1]) state[i] =  1;
                  if (Stochastic[i]<Stochastic[i+1]) state[i] = -1;
               }
               else
               {
                  if (Stochastic[i]>Stochastic_Signal[i+1]) state[i] =  1;
                  if (Stochastic[i]<Stochastic_Signal[i+1]) state[i] = -1;
               }
               if (state[i]==-1) PlotPoint(i,StochasticDa,StochasticDb,Stochastic);
               
         }   
   return(0);
   }
   
   //
   //
   //
   //
   //       
   
   limit = MathMax(limit,MathMin(Bars-1,iCustom(NULL,TimeFrame,indicatorFileName,-99,0,0)*TimeFrame/Period()));
   if (state[limit]==-1) CleanPoint(limit,StochasticDa,StochasticDb);
   for (i=limit;i>=0; i--)
   {
      int y = iBarShift(NULL,TimeFrame,Time[i]);
         Stochastic[i]        = iCustom(NULL,TimeFrame,indicatorFileName,PERIOD_CURRENT,KPeriod,DPeriod,Slowing,Price,MaMethod,ZeroLagPeriod,0,y);
         StochasticDa[i]      = EMPTY_VALUE;
         StochasticDb[i]      = EMPTY_VALUE; 
         Stochastic_Signal[i] = iCustom(NULL,TimeFrame,indicatorFileName,PERIOD_CURRENT,KPeriod,DPeriod,Slowing,Price,MaMethod,ZeroLagPeriod,3,y);
         state[i]             = iCustom(NULL,TimeFrame,indicatorFileName,PERIOD_CURRENT,KPeriod,DPeriod,Slowing,Price,MaMethod,ZeroLagPeriod,4,y);
         if (ColorOnSlope)
         {
            if (Stochastic[i]>Stochastic[i+1]) state[i] =  1;
            if (Stochastic[i]<Stochastic[i+1]) state[i] = -1;
         }
         else
         {
            if (Stochastic[i]>Stochastic_Signal[i+1]) state[i] =  1;
            if (Stochastic[i]<Stochastic_Signal[i+1]) state[i] = -1;
         }
         
         //
         //
         //
         //
         //
      
         if (!Interpolate || y==iBarShift(NULL,TimeFrame,Time[i-1])) continue;

         //
         //
         //
         //
         //

         datetime time = iTime(NULL,TimeFrame,y);
            for(int n = 1; i+n < Bars && Time[i+n] >= time; n++) continue;
            for(int x = 1; x < n; x++)
            { 
               Stochastic[i+x]        = Stochastic[i]        + (Stochastic[i+n]        - Stochastic[i])        * x/n;
               Stochastic_Signal[i+x] = Stochastic_Signal[i] + (Stochastic_Signal[i+n] - Stochastic_Signal[i]) * x/n;
            }
    }
    for (i=limit;i>=0;i--) if (state[i]==-1) PlotPoint(i,StochasticDa,StochasticDb,Stochastic);
return(0);
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

double workZl[][4];
#define _price 0
#define _zlema 1

double iZeroLag(double price, double length, int r, int instanceNo=0)
{
   if (ArrayRange(workZl,0)!=Bars) ArrayResize(workZl,Bars); r = Bars-r-1; instanceNo *= 2; workZl[r][_price+instanceNo] = price;

   //
   //
   //
   //
   //

   double median = 0;
   double alpha  = 2.0/(1.0+length); 
   int    per    = (length-1.0)/2.0;
   if (r<per)
          workZl[r][_zlema+instanceNo] = price;
   else   
      {
         if ((int)length%2==0)
               median = (workZl[r-per][_price+instanceNo]+workZl[r-per-1][_price+instanceNo])/2.0;
         else  median =  workZl[r-per][_price+instanceNo];
         workZl[r][_zlema+instanceNo] = workZl[r-1][_zlema+instanceNo]+alpha*(2.0*price-median-workZl[r-1][_zlema+instanceNo]);
      }            
   return(workZl[r][_zlema+instanceNo]);
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

void CleanPoint(int i,double& first[],double& second[])
{
   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 (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 sTfTable[] = {"M1","M5","M15","M30","H1","H4","D1","W1","MN"};
int    iTfTable[] = {1,5,15,30,60,240,1440,10080,43200};

string timeFrameToString(int tf)
{
   for (int i=ArraySize(iTfTable)-1; i>=0; i--) 
         if (tf==iTfTable[i]) return(sTfTable[i]);
                              return("");
}