//------------------------------------------------------------------
#property copyright "mladen"
#property link      "mladenfx@gmail.com"
#property link      "www.forex-station.com"
//------------------------------------------------------------------
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1  clrLimeGreen
#property indicator_color2  clrOrangeRed
#property indicator_maximum 1
#property indicator_minimum 0
#property indicator_width1  3
#property indicator_width2  3
#property strict

//
//
//
//
//

enum enMaTypes
{
   ma_sma,    // Simple moving average
   ma_ema,    // Exponential moving average
   ma_smma,   // Smoothed MA
   ma_lwma,   // Linear weighted MA
   ma_slwma   // Smoothed LWMA
};
enum enColorOn
{
   chg_onZero,  // Change color on zero cross
   chg_onOuter, // Change color on levels cross
   chg_onSlope  // Change color on slope change
};
extern ENUM_TIMEFRAMES TimeFrame         = PERIOD_CURRENT; // Time frame
input  int             DmiPeriod         = 32;             // DMI period
input  enMaTypes       DmiMaMethod       = ma_smma;        // DMI smoothing method
input  int             Smooth            = 0;              // Smothing period (<=1 for no smoothing)
input  enMaTypes       SmoothType        = ma_ema;         // Smothing method
extern int             SignalPeriod      = 9;              // Signal period
input  enColorOn       ColorOn           = chg_onOuter;    // Change color on :
extern bool            alertsOn          = false;          // Turn alerts on?
extern bool            alertsOnCurrent   = false;          // Alerts on current (still opened) bar?
extern bool            alertsMessage     = true;           // Alerts should show pop-up message?
extern bool            alertsSound       = false;          // Alerts should play alert sound?
extern bool            alertsPushNotif   = false;          // Alerts should send push notification?
extern bool            alertsEmail       = false;          // Alerts should send email?
input bool             arrowsVisible     = false;             // Arrows visible true/false?
input bool             arrowsOnNewest    = false;          // Arrows drawn on newest bar of higher time frame bar true/false?
input string           arrowsIdentifier  = "dmi Arrows1";  // Unique ID for arrows
input double           arrowsUpperGap    = 0.1;            // Upper arrow gap
input double           arrowsLowerGap    = 0.1;            // Lower arrow gap
input color            arrowsUpColor     = clrBlue;        // Up arrow color
input color            arrowsDnColor     = clrCrimson;     // Down arrow color
input int              arrowsUpCode      = 116;            // Up arrow code
input int              arrowsDnCode      = 116;            // Down arrow code
input int              arrowsUpSize      = 2;              // Up arrow size
input int              arrowsDnSize      = 2;              // Down arrow size

double histou[],histod[],stoch[],levelu[],leveld[],state[],count[];
string indicatorFileName;
#define _mtfCall(_buff,_y) iCustom(NULL,TimeFrame,indicatorFileName,PERIOD_CURRENT,DmiPeriod,DmiMaMethod,Smooth,SmoothType,SignalPeriod,ColorOn,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsPushNotif,alertsEmail,arrowsVisible,arrowsOnNewest,arrowsIdentifier,arrowsUpperGap,arrowsLowerGap,arrowsUpColor,arrowsDnColor,arrowsUpCode,arrowsDnCode,arrowsUpSize,arrowsDnSize,_buff,_y)

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

int OnInit()
{
   IndicatorBuffers(7);
   SetIndexBuffer( 0, histou); SetIndexStyle(0,DRAW_HISTOGRAM);
   SetIndexBuffer( 1, histod); SetIndexStyle(1,DRAW_HISTOGRAM);
   SetIndexBuffer( 2, levelu);
   SetIndexBuffer( 3, leveld);
   SetIndexBuffer( 4, stoch); 
   SetIndexBuffer( 5, state); 
   SetIndexBuffer( 6, count); 
            indicatorFileName = WindowExpertName();
            TimeFrame         = MathMax(TimeFrame,_Period);
   IndicatorShortName(timeFrameToString(TimeFrame)+" DMI oscillator ("+(string)DmiPeriod+","+(string)Smooth+")");
   return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) 
{  
     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);
     }
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

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[])
{
   int counted_bars = prev_calculated;
      if(counted_bars < 0) return(-1);
      if(counted_bars > 0) counted_bars--;
         int limit=MathMin(rates_total-counted_bars,rates_total-1); count[0] = limit;
         if (TimeFrame!=_Period)
         {
            limit = (int)MathMax(limit,MathMin(Bars-1,_mtfCall(6,0)*TimeFrame/_Period));
            for(int i=limit; i>=0; i--)
            {
               int y = iBarShift(NULL,TimeFrame,Time[i]);
               histou[i]  = _mtfCall(0,y);
               histod[i]  = _mtfCall(1,y);
            }
            return(rates_total);
         }               
         
   //
   //
   //
   //
   //

   double alpha = 2.0/(1.0+SignalPeriod);
   for(int i=limit; i>=0; i--)
   {
      double dhh = (i<rates_total-1) ? high[i]-high[i+1] : 0;
      double dll = (i<rates_total-1) ? low[i+1]-low[i]   : 0;
      double tr  = (i<rates_total-1) ? MathMax(high[i],close[i+1])-MathMin(low[i],close[i+1]) : high[i]-low[i];
      double atr = iCustomMa(DmiMaMethod,tr,DmiPeriod,i,rates_total,0);

         double plusDM    = (dhh>dll && dhh>0) ? dhh : 0;
         double minusDM   = (dll>dhh && dll>0) ? dll : 0;
         double plusDI    = 100*iCustomMa(DmiMaMethod,plusDM ,DmiPeriod,i,rates_total,1)/atr;
         double minusDI   = 100*iCustomMa(DmiMaMethod,minusDM,DmiPeriod,i,rates_total,2)/atr;
                stoch[i]  = iCustomMa(SmoothType,plusDI-minusDI,Smooth,i,rates_total,3);
                levelu[i] = (i<Bars-1) ? (stoch[i]>0) ? levelu[i+1]+alpha*(stoch[i]-levelu[i+1]) : levelu[i+1] : 0;
                leveld[i] = (i<Bars-1) ? (stoch[i]<0) ? leveld[i+1]+alpha*(stoch[i]-leveld[i+1]) : leveld[i+1] : 0;
                switch(ColorOn)
                {
                  case chg_onOuter : state[i] = (stoch[i]>levelu[i]) ? 1 : (stoch[i]<leveld[i]) ? -1 : 0; break;
                  case chg_onZero  : state[i] = (stoch[i]>0)         ? 1 : (stoch[i]<0)         ? -1 : 0; break;
                  default :          state[i] = (i<rates_total-1) ? (stoch[i]>stoch[i+1]) ? 1 : (stoch[i]<stoch[i+1]) ? -1 : state[i+1] : 0;
                }
                histod[i] = (state[i]==-1) ? 1 : EMPTY_VALUE;
                histou[i] = (state[i]== 1) ? 1 : EMPTY_VALUE;
                
                //
                //
                //
                //
                //
      
                if (arrowsVisible)
                {
                  string lookFor = arrowsIdentifier+":"+(string)time[i]; ObjectDelete(lookFor);            
                  if (i<(rates_total-1) && state[i] != state[i+1])
                  {
                     if (state[i] == 1) drawArrow(i,arrowsUpColor,arrowsUpCode,arrowsUpSize,false);
                     if (state[i] ==-1) drawArrow(i,arrowsDnColor,arrowsDnCode,arrowsDnSize, true);
                  }
                }   
   }

   //
   //
   //
   //
   //
   
      if (alertsOn)
      {
        int whichBar = 1; if (alertsOnCurrent) whichBar = 0;
        if (state[whichBar] != state[whichBar+1])
        {
           if (state[whichBar] ==  1) doAlert(whichBar,"up");
           if (state[whichBar] == -1) doAlert(whichBar,"down");
        }
      }        
   
   return(rates_total);
}


//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//

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 =  timeFrameToString(_Period)+" - "+Symbol()+" at "+TimeToStr(TimeLocal(),TIME_SECONDS)+" DMI oscillator state changed to "+doWhat;
          if (alertsMessage)   Alert(message);
          if (alertsEmail)     SendMail(Symbol()+" DMI oscillator",message);
          if (alertsPushNotif) SendNotification(message);
          if (alertsSound)     PlaySound("alert2.wav");
   }
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

void drawArrow(int i,color theColor,int theCode, int theSize, bool up)
{
   string name = arrowsIdentifier+":"+(string)Time[i];
   double gap  = iATR(NULL,0,20,i);   
   
      //
      //
      //
      //
      //

      datetime atime = Time[i]; if (arrowsOnNewest) atime += _Period*60-1;      
      ObjectCreate(name,OBJ_ARROW,0,atime,0);
         ObjectSet(name,OBJPROP_ARROWCODE,theCode);
         ObjectSet(name,OBJPROP_COLOR,theColor);
         ObjectSet(name,OBJPROP_WIDTH,theSize);
         if (up)
               ObjectSet(name,OBJPROP_PRICE1,High[i] + arrowsUpperGap * gap);
         else  ObjectSet(name,OBJPROP_PRICE1,Low[i]  - arrowsLowerGap * gap);
}

//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

#define _maInstances 4
#define _maWorkBufferx1 1*_maInstances
#define _maWorkBufferx2 2*_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)length,r,bars,instanceNo));
      case ma_ema   : return(iEma(price,length,r,bars,instanceNo));
      case ma_smma  : return(iSmma(price,(int)length,r,bars,instanceNo));
      case ma_lwma  : return(iLwma(price,(int)length,r,bars,instanceNo));
      case ma_slwma : return(iSlwma(price,(int)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);
}

//
//
//
//
//


double workSlwma[][_maWorkBufferx2];
double iSlwma(double price, double period, int r, int _bars, int instanceNo=0)
{
   if (ArrayRange(workSlwma,0)!= _bars) ArrayResize(workSlwma,_bars); 

   //
   //
   //
   //
   //

      int SqrtPeriod = (int)MathFloor(MathSqrt(period)); instanceNo *= 2;
         workSlwma[r][instanceNo] = 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*workSlwma[r-k][instanceNo];  
         }             
         workSlwma[r][instanceNo+1] = (sum/sumw);

         //
         //
         //
         //
         //
         
         sumw = SqrtPeriod;
         sum  = SqrtPeriod*workSlwma[r][instanceNo+1];
            for(int k=1; k<SqrtPeriod && (r-k)>=0; k++)
            {
               double weight = SqrtPeriod-k;
                      sumw += weight;
                      sum  += weight*workSlwma[r-k][instanceNo+1];  
            }
   return(sum/sumw);
}

//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

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("");
}