//+------------------------------------------------------------------
//|                                   Kaufman Adaptive Moving Average 
//|                                                            mladen 
//+------------------------------------------------------------------
#property copyright "www.forex-tsd.com"
#property link      "www.forex-tsd.com"

#property indicator_chart_window
#property indicator_buffers 7
#property indicator_color1  Linen
#property indicator_color2  LimeGreen
#property indicator_color3  LimeGreen
#property indicator_color4  Orange
#property indicator_color5  Orange
#property indicator_color6  LimeGreen
#property indicator_color7  Orange
#property indicator_width1  2
#property indicator_width2  3
#property indicator_width3  3
#property indicator_width4  3
#property indicator_width5  3
#property indicator_width6  3
#property indicator_width7  3
#property strict

//
//
//
//
//

enum enPrices
{
   pr_close,      // Close
   pr_open,       // Open
   pr_high,       // High
   pr_low,        // Low
   pr_median,     // Median
   pr_typical,    // Typical
   pr_weighted,   // Weighted
   pr_average,    // Average (high+low+open+close)/4
   pr_medianb,    // Average median body (open+close)/2
   pr_tbiased,    // Trend biased price
   pr_haclose,    // Heiken ashi close
   pr_haopen ,    // Heiken ashi open
   pr_hahigh,     // Heiken ashi high
   pr_halow,      // Heiken ashi low
   pr_hamedian,   // Heiken ashi median
   pr_hatypical,  // Heiken ashi typical
   pr_haweighted, // Heiken ashi weighted
   pr_haaverage,  // Heiken ashi average
   pr_hamedianb,  // Heiken ashi median body
   pr_hatbiased   // Heiken ashi trend biased price
};

extern ENUM_TIMEFRAMES TimeFrame        = PERIOD_CURRENT; // Time frame
extern int             AmaPeriod        = 20;             // AMA period
extern enPrices        AmaPrice         = PRICE_CLOSE;    // Price to use
extern double          FastEnd          = 2;              // AMA fast end
extern double          SlowEnd          = 30;             // AMA slow end
extern double          SmoothPower      = 2;              // AMA smoothing power
extern double          FilterDeviations = 1.0;            // Filter deviations
extern int             FilterPeriod     = 20;             // Filter period
extern bool            JurikFDAdaptive  = true;           // Should it be Jurik fractal dimension adaptive?
extern bool            alertsOn         = false;          // Turn alerts on
extern bool            alertsOnCurrent  = true;           // Alerts on current (still opened) bar?
extern bool            alertsMessage    = true;           // Should alert messages be displayed?
extern bool            alertsSound      = false;          // Should alerts sound be played?
extern bool            alertsEmail      = false;          // Should alert email be sent?
extern bool            alertsPush       = false;          // Should alert [push notification be sent?
extern bool            Interpolate      = false;          // Interpolate when in multi time frame mode?


//
//
//
//
//

double ama[];
double amaUpa[];
double amaUpb[];
double amaDn[];
double amaDna[];
double amaDnb[];
double amaSUp[];
double amaSDn[];
double trend[];
double trenc[];

//
//
//
//
//

string indicatorFileName;
bool   returnBars;
bool   calculateValue;

//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

int init()
{
   IndicatorBuffers(9);
      SetIndexBuffer(0,ama);
      SetIndexBuffer(1,amaUpa);
      SetIndexBuffer(2,amaUpb);
      SetIndexBuffer(3,amaDna);
      SetIndexBuffer(4,amaDnb);
      SetIndexBuffer(5,amaSUp); SetIndexStyle(5,DRAW_ARROW); SetIndexArrow(5,159);
      SetIndexBuffer(6,amaSDn); SetIndexStyle(6,DRAW_ARROW); SetIndexArrow(6,159);
      SetIndexBuffer(7,trend);
      SetIndexBuffer(8,trenc);

      //
      //
      //
      //
      //
      
         indicatorFileName = WindowExpertName();
         calculateValue    = (TimeFrame==-98);
         returnBars        = (TimeFrame==-99);
         TimeFrame         = MathMax(TimeFrame,_Period);

      //
      //
      //
      //
      //
         
   IndicatorShortName(timeFrameToString(TimeFrame)+" Kaufman AMA ("+(string)AmaPeriod+")");
   return(0);
}
int deinit() { return(0); }


//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

double work[][4];
#define _lastLo 0
#define _lastHi 1
#define _trend  2
#define _trenc  3

//
//
//
//
//

int start()
{
   int i,k,r,limit,counted_bars=IndicatorCounted();
   
   if(counted_bars<0) return(-1);
   if(counted_bars>0) counted_bars--;
           limit=MathMin(Bars-counted_bars,Bars-1);
           if (returnBars) { ama[0] = limit+1; return(0); }
           if (ArrayRange(work,0)!=Bars) ArrayResize(work,Bars);

   //
   //
   //
   //
   //
   
   if (calculateValue || TimeFrame == Period())
   {
      if (work[Bars-limit-1][_trenc]== 1 && work[Bars-limit-2][_trend] ==  1) CleanPoint(limit,amaUpa,amaUpb);
      if (work[Bars-limit-1][_trenc]==-1 && work[Bars-limit-2][_trend] == -1) CleanPoint(limit,amaDna,amaDnb);
      for(i=limit, r=Bars-i-1; i>=0; i--,r++)
      {
         ama[i]    = iKama(AmaPeriod,FastEnd,SlowEnd,AmaPrice,SmoothPower,JurikFDAdaptive,i);
         amaUpa[i] = EMPTY_VALUE;
         amaUpb[i] = EMPTY_VALUE;
         amaDna[i] = EMPTY_VALUE;
         amaDnb[i] = EMPTY_VALUE;
         amaSUp[i] = EMPTY_VALUE;
         amaSDn[i] = EMPTY_VALUE;
         
         //
         //
         //
         //
         //
                  
            double stddev = 0;
               if (FilterDeviations>0)
               {
                  double avgdiff = 0;
                     for (k=0; k<FilterPeriod && i+k+1<Bars; k++) avgdiff += MathAbs(ama[i+k]-ama[i+k+1]);                    avgdiff /= FilterPeriod;
                     for (k=0; k<FilterPeriod && i+k+1<Bars; k++) stddev  += MathPow(MathAbs(ama[i+k]-ama[i+k+1])-avgdiff,2); stddev  /= FilterPeriod;
               }
            double filter = FilterDeviations * MathPow(stddev,0.5);

         //
         //
         //
         //
         //

            if (r-1>0)
            {
               work[r][_trenc]  = 0;
               work[r][_trend]  = work[r-1][_trend];
               work[r][_lastLo] = work[r-1][_lastLo];
               work[r][_lastHi] = work[r-1][_lastHi];

               if (ama[i]-ama[i+1] > filter) { work[r][_trend] =  1; if (work[r-1][_trend] !=  1) work[r][_lastLo] = ama[i+1]; }
               if (ama[i+1]-ama[i] > filter) { work[r][_trend] = -1; if (work[r-1][_trend] != -1) work[r][_lastHi] = ama[i+1]; }
               if (work[r][_trend] == 1 && ama[i]>ama[i+1] && (ama[i]-work[r][_lastLo])>filter && work[r][_lastLo]>0) work[r][_trenc] =  1;
               if (work[r][_trend] ==-1 && ama[i]<ama[i+1] && (work[r][_lastHi]-ama[i])>filter && work[r][_lastHi]>0) work[r][_trenc] = -1;
               if (!calculateValue)
               {
                  if (work[r][_trenc] ==  1 && work[r-1][_trend] ==  1) PlotPoint(i,amaUpa,amaUpb,ama);
                  if (work[r][_trenc] == -1 && work[r-1][_trend] == -1) PlotPoint(i,amaDna,amaDnb,ama);
                  if (work[r][_trend] != work[r-1][_trend])
                  {
                     if (work[r][_trend] == 1) amaSUp[i] = ama[i];
                     if (work[r][_trend] ==-1) amaSDn[i] = ama[i];
                  }               
               }
               else { amaSUp[i] = work[r][_trend]; amaSDn[i] = work[r][_trenc]; }                  
            }               
      }    
      manageAlerts();
      return(0);
   }
   
   //
   //
   //
   //
   //
   
   limit = (int)MathMax(limit,MathMin(Bars-1,iCustom(NULL,TimeFrame,indicatorFileName,-99,0,0)*TimeFrame/Period()));
   if (work[Bars-limit-1][_trenc]== 1 && work[Bars-limit-2][_trend] ==  1) CleanPoint(limit,amaUpa,amaUpb);
   if (work[Bars-limit-1][_trenc]==-1 && work[Bars-limit-2][_trend] == -1) CleanPoint(limit,amaDna,amaDnb);
   for(i=limit, r=Bars-i-1; i>=0; i--,r++)
   {
      int y = iBarShift(NULL,TimeFrame,Time[i]);
         ama[i]          = iCustom(NULL,TimeFrame,indicatorFileName,-98,AmaPeriod,AmaPrice,FastEnd,SlowEnd,SmoothPower,FilterDeviations,FilterPeriod,JurikFDAdaptive,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsEmail,alertsPush,0,y);
         work[r][_trend] = iCustom(NULL,TimeFrame,indicatorFileName,-98,AmaPeriod,AmaPrice,FastEnd,SlowEnd,SmoothPower,FilterDeviations,FilterPeriod,JurikFDAdaptive,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsEmail,alertsPush,5,y);
         work[r][_trenc] = iCustom(NULL,TimeFrame,indicatorFileName,-98,AmaPeriod,AmaPrice,FastEnd,SlowEnd,SmoothPower,FilterDeviations,FilterPeriod,JurikFDAdaptive,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsEmail,alertsPush,6,y);
         amaUpa[i] = EMPTY_VALUE;
         amaUpb[i] = EMPTY_VALUE;
         amaDna[i] = EMPTY_VALUE;
         amaDnb[i] = EMPTY_VALUE;
         amaSUp[i] = EMPTY_VALUE;
         amaSDn[i] = EMPTY_VALUE;
            
         //
         //
         //
         //
         //
      
         if (!Interpolate || (i>0 && y==iBarShift(NULL,TimeFrame,Time[i-1]))) continue;
              interpolate(ama,iTime(NULL,TimeFrame,iBarShift(NULL,TimeFrame,Time[i])),i);
   }
   for(i=limit, r=Bars-i-1; i>=0; i--,r++)
   {
      if ((r-1)>0)
      {
         if (work[r][_trenc] ==  1 && work[r-1][_trend] ==  1) PlotPoint(i,amaUpa,amaUpb,ama);
         if (work[r][_trenc] == -1 && work[r-1][_trend] == -1) PlotPoint(i,amaDna,amaDnb,ama);
         if (work[r][_trend] != work[r-1][_trend])
         {
            if (work[r][_trend]== 1) amaSUp[i] = ama[i];
            if (work[r][_trend]==-1) amaSDn[i] = ama[i];
         }               
      }
   }      
   return(0);
}
  
//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

void interpolate(double& buffer[], datetime time, int i)
{
   int n; for (n = 1; (i+n) < Bars && Time[i+n] >= time; n++) continue;
   
   //
   //
   //
   //
   //

   if (i+n<Bars)
   {   
      if (buffer[i] == EMPTY_VALUE || buffer[i+n] == EMPTY_VALUE) n=-1;
                  double increment = (buffer[i+n] - buffer[i])/ n;
      for (int k = 1; k<n && i+k<Bars; k++) buffer[i+k] = buffer[i] + k*increment;
   }      
}


//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

void CleanPoint(int i,double& first[],double& second[])
{
   if (i>Bars-2) 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-3) 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; }
}

//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

void manageAlerts()
{
   if (alertsOn)
   {
      int whichBar = 1; if (alertsOnCurrent) whichBar = 0;
      if (work[Bars-whichBar-1][_trend] != work[Bars-whichBar-2][_trend])
      {
         if (work[Bars-whichBar-1][_trend] == 1) doAlert(whichBar,"up");
         if (work[Bars-whichBar-1][_trend] ==-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(_Period)," at ",TimeToStr(TimeLocal(),TIME_SECONDS)," Kaufman ama trend changed to ",doWhat);
          if (alertsMessage) Alert(message);
          if (alertsEmail)   SendMail(StringConcatenate(Symbol(),"Kaufman ama"),message);
          if (alertsPush)    SendNotification(message);
          if (alertsSound)   PlaySound("alert2.wav");
   }
}


//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

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("");
}


//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//

double workAma[][2];
#define _diff 0
#define _kama 1

//
//
//
//
//

double iKama(int period, double fast, double slow, int priceType, double power, bool JurikFD, int i)
{
   if (ArrayRange(workAma,0)!=Bars) ArrayResize(workAma,Bars);

   double price = getPrice(priceType,Open,Close,High,Low,i); int r = Bars-i-1; if (r<period) return(price);
   double fastend = (2.0 /(fast + 1));
   double slowend = (2.0 /(slow + 1));
   
   //
   //
   //
   //
   //

      double efratio = 0;
      if (JurikFD) efratio = MathMin(2.0-iJurikFractalDimension(period,2,i),1.0);
      else
      {
         double signal = MathAbs(price-iMA(NULL,0,1,0,MODE_SMA,priceType,i+period));
         double noise  = 0;
            workAma[r][_diff] = MathAbs(price-iMA(NULL,0,1,0,MODE_SMA,priceType,i+1));
            for (int k=0; k<period; k++) 
                  noise += workAma[r-k][_diff];

            //
            //
            //
            //
            //

            if (noise != 0)
                  efratio = signal/noise;
            else  efratio = 1;
      }
      double smooth = MathPow(efratio*(fastend-slowend)+slowend,power);
             workAma[r][_kama] = workAma[r-1][_kama] + smooth*(price-workAma[r-1][_kama]);

   //
   //
   //
   //
   //
      
   return(workAma[r][_kama]);
}

//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

double workFd[][2];
#define _smlRange 0
#define _smlSumm  1

//
//
//
//
//

double iJurikFractalDimension(int size, int count,int i)
{
   if (ArrayRange(workFd,0)!=Bars) ArrayResize(workFd,Bars);

   int window1  = size*(count-1);
   int window2  = size* count;
   int r        = Bars-i-1;

   //
   //
   //
   //
   //

      workFd[r][_smlRange] = iFdRange(size,i);
      workFd[r][_smlSumm]  = workFd[r-1][_smlSumm]+workFd[r][_smlRange];
      
      if ((Bars-i)>window1)
      {
         workFd[r][_smlSumm] -= workFd[r-window1][_smlRange];
         return(2.0-MathLog(iFdRange(window2,i)/(workFd[r][_smlSumm]/window1))/MathLog(count));
      }
      else return(0.5);
}
double iFdRange(int size,int i) { if (size+i<Bars) return(MathMax(Close[size+i],High[ArrayMaximum(High,size,i)])-MathMin(Close[size+i],Low[ArrayMinimum(Low,size,i)])); else return(High[i]-Low[i]); }   

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//
//

double workHa[][4];
double getPrice(int price, const double& open[], const double& close[], const double& high[], const double& low[], int i, int instanceNo=0)
{
  if (price>=pr_haclose && price<=pr_hatbiased)
   {
      if (ArrayRange(workHa,0)!= Bars) ArrayResize(workHa,Bars);
         int r = Bars-i-1;
         
         //
         //
         //
         //
         //
         
         double haOpen;
         if (r>0)
                haOpen  = (workHa[r-1][instanceNo+2] + workHa[r-1][instanceNo+3])/2.0;
         else   haOpen  = (open[i]+close[i])/2;
         double haClose = (open[i] + high[i] + low[i] + close[i]) / 4.0;
         double haHigh  = MathMax(high[i], MathMax(haOpen,haClose));
         double haLow   = MathMin(low[i] , MathMin(haOpen,haClose));

         if(haOpen  <haClose) { workHa[r][instanceNo+0] = haLow;  workHa[r][instanceNo+1] = haHigh; } 
         else                 { workHa[r][instanceNo+0] = haHigh; workHa[r][instanceNo+1] = haLow;  } 
                                workHa[r][instanceNo+2] = haOpen;
                                workHa[r][instanceNo+3] = haClose;
         //
         //
         //
         //
         //
         
         switch (price)
         {
            case pr_haclose:     return(haClose);
            case pr_haopen:      return(haOpen);
            case pr_hahigh:      return(haHigh);
            case pr_halow:       return(haLow);
            case pr_hamedian:    return((haHigh+haLow)/2.0);
            case pr_hamedianb:   return((haOpen+haClose)/2.0);
            case pr_hatypical:   return((haHigh+haLow+haClose)/3.0);
            case pr_haweighted:  return((haHigh+haLow+haClose+haClose)/4.0);
            case pr_haaverage:   return((haHigh+haLow+haClose+haOpen)/4.0);
            case pr_hatbiased:
               if (haClose>haOpen)
                     return((haHigh+haClose)/2.0);
               else  return((haLow+haClose)/2.0);        
         }
   }
   
   //
   //
   //
   //
   //
   
   switch (price)
   {
      case pr_close:     return(close[i]);
      case pr_open:      return(open[i]);
      case pr_high:      return(high[i]);
      case pr_low:       return(low[i]);
      case pr_median:    return((high[i]+low[i])/2.0);
      case pr_medianb:   return((open[i]+close[i])/2.0);
      case pr_typical:   return((high[i]+low[i]+close[i])/3.0);
      case pr_weighted:  return((high[i]+low[i]+close[i]+close[i])/4.0);
      case pr_average:   return((high[i]+low[i]+close[i]+open[i])/4.0);
      case pr_tbiased:   
               if (close[i]>open[i])
                     return((high[i]+close[i])/2.0);
               else  return((low[i]+close[i])/2.0);        
   }
   return(0);
}