//------------------------------------------------------------------
#property copyright "mladen"
#property link      "mladenfx@gmail.com"
//------------------------------------------------------------------

#property indicator_separate_window
#property indicator_buffers 4
#property indicator_color1  Blue//LimeGreen
#property indicator_color2  Red//Orange
#property indicator_color3  Red//Orange
#property indicator_color4  Orange//Gold
#property indicator_width1  3
#property indicator_width2  3
#property indicator_width3  3
#property indicator_style4  STYLE_DOT
#property indicator_minimum -1
#property indicator_maximum 101

//
//
//
//
//

extern string TimeFrame = "Current time frame";
extern int    StochasticLength        = 50;
extern int    SmoothEMA               = 8;//9;
extern int    SignalEMA               = 5;
extern int    SmoothPeriod            = 1;//10;
extern int    SmoothAdaptPeriod       = 25;    
extern bool   ChangeOnDirectionChange = true;
extern bool   alertsOn                = false;
extern bool   alertsOnCurrent         = false;
extern bool   alertsMessage           = true;
extern bool   alertsSound             = false;
extern bool   alertsEmail             = false;
extern bool   alertsNotification      = false;
extern bool   arrowsVisible           = true;
extern string arrowsIdentifier        = "Arrows1";
extern double arrowsDisplacement      = 1.0;
extern color  arrowsUpColor           = Blue;
extern color  arrowsDnColor           = Red;
extern int    arrowsUpCode            = 241;
extern int    arrowsDnCode            = 242;
extern bool   arrowsOnFirst           = false;
extern bool   Interpolate             = true;

//
//
//
//
//

double dssBuffer[];
double ddaBuffer[];
double ddbBuffer[];
double sigBuffer[];
double trend[];

string indicatorFileName;
bool   returnBars;
int    timeFrame;

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

int init()
{
   for (int i=0; i<indicator_buffers; i++) SetIndexStyle(i,DRAW_LINE);
   IndicatorBuffers(5);
   SetIndexBuffer(0,dssBuffer);
   SetIndexBuffer(1,ddaBuffer);
   SetIndexBuffer(2,ddbBuffer);
   SetIndexBuffer(3,sigBuffer);
   SetIndexBuffer(4,trend);
      StochasticLength = MathMax(1,StochasticLength);
      SignalEMA        = MathMax(0,SignalEMA);
      SmoothEMA        = MathMax(0,SmoothEMA);
         if (SignalEMA<1) ChangeOnDirectionChange=true;
         indicatorFileName = WindowExpertName();
         returnBars        = TimeFrame=="returnBars";     if (returnBars)     { return(0); }
         timeFrame         = stringToTimeFrame(TimeFrame);

   //
   //
   //
   //
   //

   IndicatorShortName(timeFrameToString(timeFrame)+" DSS of adaptive smoother ("+StochasticLength+","+SmoothEMA+","+SignalEMA+","+SmoothPeriod+")");
   return(0);
}
//int deinit(){ return(0); }

int deinit()
{  
   if (arrowsVisible) deleteArrows();  
   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);
           if (returnBars) { dssBuffer[0] = MathMin(limit+1,Bars-1); return(0); }
           if (timeFrame!=Period())
           {
               limit = MathMax(limit,MathMin(Bars-1,iCustom(NULL,timeFrame,indicatorFileName,"returnBars",0,0)*timeFrame/Period()));
               if (trend[limit]==-1) CleanPoint(limit,ddaBuffer,ddbBuffer);
            	for(int i = limit; i >= 0; i--)
               {
                  int y = iBarShift(NULL,timeFrame,Time[i]);
                     dssBuffer[i] = iCustom(NULL,timeFrame,indicatorFileName,"",StochasticLength,SmoothEMA,SignalEMA,SmoothPeriod,SmoothAdaptPeriod,ChangeOnDirectionChange,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsEmail,alertsNotification,arrowsVisible,arrowsIdentifier,arrowsDisplacement,arrowsUpColor,arrowsDnColor,arrowsUpCode,arrowsDnCode,arrowsOnFirst,0,y);
                     sigBuffer[i] = iCustom(NULL,timeFrame,indicatorFileName,"",StochasticLength,SmoothEMA,SignalEMA,SmoothPeriod,SmoothAdaptPeriod,ChangeOnDirectionChange,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsEmail,alertsNotification,arrowsVisible,arrowsIdentifier,arrowsDisplacement,arrowsUpColor,arrowsDnColor,arrowsUpCode,arrowsDnCode,arrowsOnFirst,3,y);
                     trend[i]     = iCustom(NULL,timeFrame,indicatorFileName,"",StochasticLength,SmoothEMA,SignalEMA,SmoothPeriod,SmoothAdaptPeriod,ChangeOnDirectionChange,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsEmail,alertsNotification,arrowsVisible,arrowsIdentifier,arrowsDisplacement,arrowsUpColor,arrowsDnColor,arrowsUpCode,arrowsDnCode,arrowsOnFirst,4,y);
                     ddaBuffer[i] = EMPTY_VALUE;   
                     ddbBuffer[i] = EMPTY_VALUE;   

                     //
                     //
                     //
                     //
                     //
                     //
                     
                     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 k = 1; k < n; k++)
                     {
                        dssBuffer[i+k] = dssBuffer[i] + (dssBuffer[i+n]-dssBuffer[i])*k/n;
                        sigBuffer[i+k] = sigBuffer[i] + (sigBuffer[i+n]-sigBuffer[i])*k/n;
                     }               
               }
            	for(i = limit; i >= 0; i--) if (trend[i]==-1) PlotPoint(i,ddaBuffer,ddbBuffer,dssBuffer);
               return(0);
           }

   //
   //
   //
   //
   //
   
      double alpha = 2.0 / (1.0+SignalEMA);
      if (trend[limit]==-1) CleanPoint(limit,ddaBuffer,ddbBuffer);
   	for(i = limit; i >= 0; i--)
      {
         double sort[3];
         double dev    = iStdDev(NULL,0,SmoothAdaptPeriod,0,MODE_SMA,PRICE_CLOSE,i);
         double avg    = iSma(dev,SmoothAdaptPeriod,i,0);
         double period = SmoothPeriod;
                     if (dev!=0)   period = SmoothPeriod*avg/dev;
                     if (period<3) period = 3;
                     sort[0] = iSsm(iMA(NULL,0,1,0,MODE_SMA,PRICE_CLOSE,i),period,i,0);
                dev    = iStdDev(NULL,0,SmoothAdaptPeriod,0,MODE_SMA,PRICE_HIGH,i);
                avg    = iSma(dev,SmoothAdaptPeriod,i,1);
                period = SmoothPeriod;
                     if (dev!=0)   period = SmoothPeriod*avg/dev;
                     if (period<3) period = 3;
                     sort[1] = iSsm(iMA(NULL,0,1,0,MODE_SMA,PRICE_HIGH ,i),period,i,1);
                dev    = iStdDev(NULL,0,SmoothAdaptPeriod,0,MODE_SMA,PRICE_LOW,i);
                avg    = iSma(dev,SmoothAdaptPeriod,i,2);
                period = SmoothPeriod;
                     if (dev!=0)   period = SmoothPeriod*avg/dev;
                     if (period<3) period = 3;
                     sort[2] = iSsm(iMA(NULL,0,1,0,MODE_SMA,PRICE_LOW  ,i),period,i,2);
            
            //
            //
            //
            //
            //
            
            ArraySort(sort);
            dssBuffer[i] = iDss(sort[1],sort[2],sort[0],StochasticLength,SmoothEMA,i);
            ddaBuffer[i] = EMPTY_VALUE;   
            ddbBuffer[i] = EMPTY_VALUE;   
            sigBuffer[i] = sigBuffer[i+1]+alpha*(dssBuffer[i]-sigBuffer[i+1]);
            trend[i]     = trend[i+1];

               if (ChangeOnDirectionChange)
                  {
                     if (dssBuffer[i]>dssBuffer[i+1]) trend[i] =  1;
                     if (dssBuffer[i]<dssBuffer[i+1]) trend[i] = -1;
                  }
               else
                  {
                     if (dssBuffer[i]>sigBuffer[i+1]) trend[i] =  1;
                     if (dssBuffer[i]<sigBuffer[i+1]) trend[i] = -1;
                  }               
               if (trend[i]==-1) PlotPoint(i,ddaBuffer,ddbBuffer,dssBuffer); 
               manageArrow(i);               
      }
      manageAlerts();
   return(0);           
}

//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

void manageAlerts() 
{
   if (alertsOn) 
   {
      if (alertsOnCurrent)
           int whichBar = 0;
      else     whichBar = 1;
      
      //
      //
      //
      //
      //
      
      if (trend[whichBar] != trend[whichBar+1]) 
      {
         if (trend[whichBar] ==  1) doAlert(whichBar,"up");
         if (trend[whichBar] == -1) doAlert(whichBar,"down");
      }
   }
}

//
//
//
//
//

void doAlert(int forBar, string doWhat) 
{
   static string   previousAlert="nothing";
   static datetime previousTime;
   string message;
   
   if (previousAlert != doWhat || previousTime != Time[forBar]) {
       previousAlert  = doWhat;
       previousTime   = Time[forBar];

       message =  StringConcatenate(Symbol()," ",timeFrameToString(timeFrame)," at ",TimeToStr(TimeLocal(),TIME_SECONDS)," DSS of adaptive smoother trend changed to ",doWhat);
          if (alertsMessage)      Alert(message);
          if (alertsEmail)        SendMail(StringConcatenate(Symbol(),"DSS of adaptive smoother"),message);
          if (alertsNotification) SendNotification(message);
          if (alertsSound)        PlaySound("alert2.wav");
   }
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

double workDss[][5];
#define _plow  0
#define _phigh 1
#define _st1   2
#define _ss1   3
#define _dss   4

double iDss(double pClose, double pHigh, double pLow, int period, int smoothPeriod, int i, int instanceNo=0)
{
   if (ArrayRange(workDss,0)!=Bars) ArrayResize(workDss,Bars); i = Bars-i-1; instanceNo*=5;

   //
   //
   //
   //
   //
   
         double alpha = 2.0 / (1.0+smoothPeriod);
            workDss[i][instanceNo+_phigh] = pHigh;
            workDss[i][instanceNo+_plow]  = pLow;
               double min   = workDss[i][instanceNo+_plow];
               double max   = workDss[i][instanceNo+_phigh];

      
         for (int k=1; k<period && (i-k)>=0; k++)
         {
            min = MathMin(min,workDss[i-k][instanceNo+_plow] );
            max = MathMax(max,workDss[i-k][instanceNo+_phigh]);
         }
         workDss[i][instanceNo+_st1] = 0; if (min!=max) workDss[i][instanceNo+_st1] = 100*(pClose-min)/(max-min);
         
         //
         //
         //
         //
         //
         
         if (i==0)
         {
               workDss[i][instanceNo+_ss1] = workDss[i][instanceNo+_st1];
               workDss[i][instanceNo+_dss] = workDss[i][instanceNo+_st1];
         }            
         else
         {
            workDss[i][instanceNo+_ss1] = workDss[i-1][instanceNo+_ss1]+alpha*(workDss[i][instanceNo+_st1]-workDss[i-1][instanceNo+_ss1]);
               min = workDss[i][instanceNo+_ss1];
               max = workDss[i][instanceNo+_ss1];
               for (k=1; k<period && (i-k)>=0; k++)
               {
                  min = MathMin(min,workDss[i-k][instanceNo+_ss1]);
                  max = MathMax(max,workDss[i-k][instanceNo+_ss1]);
               }
               double stoch = 0; if (min!=max) stoch = 100*(workDss[i][instanceNo+_ss1]-min)/(max-min);
               workDss[i][instanceNo+_dss] = workDss[i-1][instanceNo+_dss]+alpha*(stoch-workDss[i-1][instanceNo+_dss]);
         }            
            
      //
      //
      //
      //
      //
            
   return(workDss[i][instanceNo+_dss]);
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

double workSsm[][6];
#define _price  0
#define _ssm    1

double workSsmCoeffs[][4];
#define _period 0
#define _c1     1
#define _c2     2
#define _c3     3
#define Pi 3.14159265358979323846264338327950288

//
//
//
//
//

double iSsm(double price, double period, int i, int instanceNo)
{
   if (ArrayRange(workSsm,0) !=Bars)                 ArrayResize(workSsm,Bars);
   if (ArrayRange(workSsmCoeffs,0) < (instanceNo+1)) ArrayResize(workSsmCoeffs,instanceNo+1);
   if (workSsmCoeffs[instanceNo][_period] != period)
   {
      workSsmCoeffs[instanceNo][_period] = period;
      double a1 = MathExp(-1.414*Pi/period);
      double b1 = 2.0*a1*MathCos(1.414*Pi/period);
         workSsmCoeffs[instanceNo][_c2] = b1;
         workSsmCoeffs[instanceNo][_c3] = -a1*a1;
         workSsmCoeffs[instanceNo][_c1] = 1.0 - workSsmCoeffs[instanceNo][_c2] - workSsmCoeffs[instanceNo][_c3];
   }

   //
   //
   //
   //
   //

   int s = instanceNo*2;   
      i = Bars-i-1;
      workSsm[i][s+_price] = price;
      if (i>3)
      {
          workSsm[i][s+_ssm]   = workSsmCoeffs[instanceNo][_c1]*(workSsm[i][s+_price]+workSsm[i-1][s+_price])/2.0 + 
                                 workSsmCoeffs[instanceNo][_c2]*workSsm[i-1][s+_ssm]                              + 
                                 workSsmCoeffs[instanceNo][_c3]*workSsm[i-2][s+_ssm];
      }
      else workSsm[i][s+_ssm] = price;
   return(workSsm[i][s+_ssm]);
}

//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

double workSma[][6];
double iSma(double price, int period, int r, int instanceNo=0)
{
   if (ArrayRange(workSma,0)!= Bars) ArrayResize(workSma,Bars); r = Bars-r-1; int k = period; instanceNo *= 2;

   workSma[r][instanceNo] = price;
   if (r>=period)
          workSma[r][instanceNo+1] = workSma[r-1][instanceNo+1]+workSma[r][instanceNo]-workSma[r-period][instanceNo];
   else { workSma[r][instanceNo+1] = 0; for(k=0; k<period && (r-k)>=0; k++) workSma[r][instanceNo+1] += workSma[r-k][instanceNo]; }
   return(workSma[r][instanceNo+1]/(double)k);
}  


//+-------------------------------------------------------------------
//|                                                                  
//+-------------------------------------------------------------------
//
//
//
//
//

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;
      }
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
//
//
//
//
//

void manageArrow(int i)
{
   if (arrowsVisible)
   {
      deleteArrow(Time[i]);
      if (trend[i] != trend[i+1])
      {
         if (trend[i] == 1) drawArrow(i,arrowsUpColor,arrowsUpCode,false);
         if (trend[i] ==-1) drawArrow(i,arrowsDnColor,arrowsDnCode,true);
      }
   }
}               
void drawArrow(int i,color theColor,int theCode,bool up)
{
   string name = arrowsIdentifier+":"+Time[i];
   double gap  = iATR(NULL,0,20,i);   
   
      //
      //
      //
      //
      //
      
      int add = 0; if (!arrowsOnFirst) add = _Period*60-1;
      ObjectCreate(name,OBJ_ARROW,0,Time[i]+add,0);
         ObjectSet(name,OBJPROP_ARROWCODE,theCode);
         ObjectSet(name,OBJPROP_COLOR,theColor);
         if (up)
               ObjectSet(name,OBJPROP_PRICE1,High[i] + arrowsDisplacement * gap);
         else  ObjectSet(name,OBJPROP_PRICE1,Low[i]  - arrowsDisplacement * gap);
}
void deleteArrows()
{
   string lookFor       = arrowsIdentifier+":";
   int    lookForLength = StringLen(lookFor);
   for (int i=ObjectsTotal()-1; i>=0; i--)
   {
      string objectName = ObjectName(i);
         if (StringSubstr(objectName,0,lookForLength) == lookFor) ObjectDelete(objectName);
   }
}
void deleteArrow(datetime time)
{
   string lookFor = arrowsIdentifier+":"+time; ObjectDelete(lookFor);
}


//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

string sTfTable[] = {"M1","M5","M15","M30","H1","H4","D1","W1","MN"};
int    iTfTable[] = {1,5,15,30,60,240,1440,10080,43200};

//
//
//
//
//

int stringToTimeFrame(string tfs)
{
   StringToUpper(tfs);
   for (int i=ArraySize(iTfTable)-1; i>=0; i--)
         if (tfs==sTfTable[i] || tfs==""+iTfTable[i]) return(MathMax(iTfTable[i],Period()));
                                                      return(Period());
}
string timeFrameToString(int tf)
{
   for (int i=ArraySize(iTfTable)-1; i>=0; i--) 
         if (tf==iTfTable[i]) return(sTfTable[i]);
                              return("");
}