//+------------------------------------------------------------------+
//|                                           Schaff Trend Cycle.mq4 |
//|                                                           mladen |
//+------------------------------------------------------------------+
#property copyright "mladen"
#property link      "mladenfx@gmail.com"
#property link      "www.forex-station.com"

#property indicator_separate_window
#property indicator_buffers 4
#property indicator_color1  clrDodgerBlue
#property indicator_color2  clrSandyBrown
#property indicator_color3  clrSandyBrown
#property indicator_color4  clrYellow
#property indicator_width1  2
#property indicator_width2  2
#property indicator_width3  2
#property strict

//
//
//
enum enColorOn
{
   cc_onSlope,   // Change color on slope change
   cc_onMa  // Color on STC cross MA 
};
//
//

extern ENUM_TIMEFRAMES TimeFrame    = PERIOD_CURRENT; // Time frame
extern int             STCPeriod    = 10;             // Schaff period
extern int             FastMAPeriod = 23;             // Fast macd period
extern int             SlowMAPeriod = 50;             // Slow macd period
extern double          SmoothPeriod = 3;              // Smoothing period
input int              SigPeriod    = 7;              // Signal period
enum enMaTypes
{
   ma_sma,     // Simple moving average
   ma_ema,     // Exponential moving average
   ma_smma,    // Smoothed MA
   ma_lwma,    // Linear weighted MA
};
input enMaTypes        SigMaMethod  = ma_sma;         // Signal moving average type
extern enColorOn       colorOn      = cc_onSlope;     // Color change on:
extern bool            Interpolate  = true;           // Interpolate in multi time frame mode?

double stc[],stcUA[],stcUB[],sig[],macd[],fastK[],fastD[],fastKK[],slope[],count[];
string indicatorFileName;
#define _mtfCall(_buff,_y) iCustom(NULL,TimeFrame,indicatorFileName,PERIOD_CURRENT,STCPeriod,FastMAPeriod,SlowMAPeriod,SmoothPeriod,SigPeriod,SigMaMethod,colorOn,_buff,_y)

//------------------------------------------------------------------
//
//------------------------------------------------------------------

int init()
{
   IndicatorBuffers(10);
   SetIndexBuffer(0,stc);
   SetIndexBuffer(1,stcUA);
   SetIndexBuffer(2,stcUB);
   SetIndexBuffer(3,sig);
   SetIndexBuffer(4,macd);
   SetIndexBuffer(5,fastK);
   SetIndexBuffer(6,fastD);
   SetIndexBuffer(7,fastKK);
   SetIndexBuffer(8,slope);
   SetIndexBuffer(9,count);
   
   indicatorFileName = WindowExpertName();
   TimeFrame         = MathMax(TimeFrame,_Period);
   IndicatorShortName(timeFrameToString(TimeFrame)+" Schaff Trend Cycle ("+(string)STCPeriod+","+(string)FastMAPeriod+","+(string)SlowMAPeriod+","+(string)SmoothPeriod+")");
return(0);
}

int deinit()
{
   return(0);
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

int start()
{
   int counted_bars=IndicatorCounted();
      if(counted_bars < 0) return(-1);
      if(counted_bars>0) counted_bars--;
         int limit = MathMin(Bars-counted_bars,Bars-1); count[0] = limit;
         if (TimeFrame!=_Period)
         {
            limit = (int)MathMax(limit,MathMin(Bars-1,_mtfCall(9,0)*TimeFrame/Period()));
            if (slope[limit] == 1) CleanPoint(limit,stcUA,stcUB);
            for(int i=limit; i>=0; i--)
            {
               int y = iBarShift(NULL,TimeFrame,Time[i]);
                  slope[i] = _mtfCall(8,y);
                  stc[i]   = _mtfCall(0,y);
                  sig[i]   = _mtfCall(3,y);
                  stcUA[i] = stcUB[i] = EMPTY_VALUE;
                  
                  //
                  //
                  //
                  //
                  //
                  
                  if (!Interpolate || (i>0 && y==iBarShift(NULL,TimeFrame,Time[i-1]))) continue;
                  #define _interpolate(buff,i,k,n) buff[i+k] = buff[i]+(buff[i+n]-buff[i])*k/n
                     int n,k; datetime time = iTime(NULL,TimeFrame,y);
                        for(n = 1; (i+n)<Bars && Time[i+n] >= time; n++) continue;	
                        for(k = 1; k<n && (i+n)<Bars && (i+k)<Bars; k++) 
                        {
                           _interpolate(stc,i,k,n);
                           _interpolate(sig,i,k,n);
                        }
               }
               for(int i=limit; i>=0; i--) if (slope[i]==-1) PlotPoint(i,stcUA,stcUB,stc);
               return(0);
         }
         
   //
   //
   //
   //
   //
   
   double alpha = 2.0/(1.0+SmoothPeriod);
   if (slope[limit] == 1) CleanPoint(limit,stcUA,stcUB);
      for(int i = limit; i >= 0 && !_StopFlag; i--)
      {
         macd[i] = iMA(NULL,0,FastMAPeriod,0,MODE_EMA,PRICE_CLOSE,i)-iMA(NULL,0,SlowMAPeriod,0,MODE_EMA,PRICE_CLOSE,i);

            //
            //
            //
            //
            //
      
            double lowMacd  = macd[ArrayMinimum(macd,STCPeriod,i)];
            double highMacd = macd[ArrayMaximum(macd,STCPeriod,i)]-lowMacd;
               fastK[i] = (highMacd > 0) ? 100*((macd[i]-lowMacd)/highMacd) : (i<Bars-1) ? fastK[i+1] : 0;
               fastD[i] = (i<Bars-1) ? fastD[i+1]+alpha*(fastK[i]-fastD[i+1]) : fastK[i];
               
            double lowStoch  = fastD[ArrayMinimum(fastD,STCPeriod,i)];
            double highStoch = fastD[ArrayMaximum(fastD,STCPeriod,i)]-lowStoch;
               fastKK[i] = (highStoch > 0) ? 100*((fastD[i]-lowStoch)/highStoch) : (i<Bars-1) ? fastKK[i+1] : 0;
               stc[i]    = (i<Bars-1) ? stc[i+1]+alpha*(fastKK[i]-stc[i+1]) : fastKK[i];
               sig[i]    = iCustomMa(SigMaMethod,stc[i],SigPeriod,i,Bars);   
               stcUA[i]  = stcUB[i]  = EMPTY_VALUE;
               
             
               
               switch(colorOn)
         {
            
            case cc_onMa:             slope[i]  = (stc[i]>sig[i]) ? 1 : (stc[i]<sig[i]) ? -1 :  0; break; 
            default :  if (i<Bars-1)  slope[i] = (i<Bars-1) ? (stc[i]>stc[i+1]) ? 1 : (stc[i]<stc[i+1]) ? -1 : slope[i+1] : 0; 
         }
         
               
               if (slope[i]==-1) PlotPoint(i,stcUA,stcUB,stc);
      }   
   return(0);
}

//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

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 1
#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);
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

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 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("");
}