//------------------------------------------------------------------
#property copyright   "www.forex-station.com"
#property link        "www.forex-station.com"
//------------------------------------------------------------------
#property indicator_separate_window
#property indicator_buffers 10
#property indicator_color1  clrSilver
#property indicator_color2  clrSilver
#property indicator_color3  clrSilver
#property indicator_color4  clrDimGray
#property indicator_color5  clrDimGray
#property indicator_color6  clrLightGray
#property indicator_style2  STYLE_DOT
#property strict

//
//
//
//
//

enum enMaTypes
{
   avgSma,    // Simple moving average
   avgEma,    // Exponential moving average
   avgSmma,   // Smoothed MA
   avgLwma    // Linear weighted MA
};
enum enColorOn
{
   cc_onSlope,   // Change color on slope change
   cc_onMiddle,  // Change color on middle line cross
   cc_onLevels   // Change color on outer levels cross
};

extern ENUM_TIMEFRAMES    TimeFrame           = PERIOD_CURRENT;    // Time frame
extern string             ForSymbol           = "";                // For symbol (leave empty for current chart symbol)
input int                 MomentumPeriod      = 14;                // Momentum period
input int                 AvgPeriod           =  0;                // Momentum average period (0 -> same as momentum period
input enMaTypes           AvgType             = avgEma;            // Momentum average method
input double              ZoneUp              = 70;                // Upper zone limit
input double              ZoneDown            = 30;                // Lower zone limit
extern enColorOn          ColorOn             = cc_onLevels;       // Color change :
extern color              ColorUp             = clrLimeGreen;      // Color for up
extern color              ColorDown           = clrOrangeRed;      // Color for down
extern int                LineWidth           = 3;                 // Main line width
input bool                alertsOn            = true;              // Turn alerts on?
input bool                alertsOnCurrent     = false;             // Alerts on still open bar?
input bool                alertsMessage       = true;              // Alerts should show popup message?
input bool                alertsSound         = false;             // Alerts should play a sound?
input bool                alertsEmail         = false;             // Alerts should send email?
input bool                alertsPushNotif     = false;             // Alerts should send notification?
extern bool               Interpolate         = true;              // Interpolate in multi time frame?

//
//
//
//
//

double mom[];
double momUa[];
double momUb[];
double momDa[];
double momDb[];
double levup[];
double levmi[];
double levdn[];
double trend[],shadowa[],shadowb[],diff[];

string indicatorFileName,shortName;
bool   returnBars;

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

int init()
{
   IndicatorBuffers(12);
   SetIndexBuffer(0,levup);
   SetIndexBuffer(1,levmi);
   SetIndexBuffer(2,levdn);
   SetIndexBuffer(3,shadowa); SetIndexStyle(3,EMPTY,EMPTY,LineWidth+4);
   SetIndexBuffer(4,shadowb); SetIndexStyle(4,EMPTY,EMPTY,LineWidth+4);
   SetIndexBuffer(5,mom);     SetIndexStyle(5,EMPTY,EMPTY,LineWidth);
   SetIndexBuffer(6,momUa);   SetIndexStyle(6,EMPTY,EMPTY,LineWidth,ColorUp);
   SetIndexBuffer(7,momUb);   SetIndexStyle(7,EMPTY,EMPTY,LineWidth,ColorUp);
   SetIndexBuffer(8,momDa);   SetIndexStyle(8,EMPTY,EMPTY,LineWidth,ColorDown);
   SetIndexBuffer(9,momDb);   SetIndexStyle(9,EMPTY,EMPTY,LineWidth,ColorDown);
   SetIndexBuffer(10,trend); 
   SetIndexBuffer(11,diff); 
   
       //
       //
       //
       //
       //
      
       indicatorFileName = WindowExpertName();
       returnBars        = (TimeFrame==-99);
       TimeFrame         = MathMax(TimeFrame,_Period);
       if (ForSymbol=="") ForSymbol = _Symbol;
         shortName = ForSymbol+" "+timeFrameToString(TimeFrame)+" momentum pinball ("+(string)MomentumPeriod+","+(string)AvgPeriod+")";
   IndicatorShortName(shortName);
   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 window = WindowFind(shortName);
            int limit  = MathMin(Bars-counted_bars,Bars-1);
            if (returnBars) { levup[0] = limit+1; return(0); }
            if (TimeFrame != _Period || ForSymbol!=_Symbol)
            {
               limit = (int)MathMax(limit,MathMin(Bars-1,iCustom(ForSymbol,TimeFrame,indicatorFileName,-99,0,0)*TimeFrame/Period())); 
               if (trend[limit]== 1) { CleanPoint(limit,momUa,momUb); CleanPoint(limit,shadowa,shadowb); }
               if (trend[limit]==-1) { CleanPoint(limit,momDa,momDb); CleanPoint(limit,shadowa,shadowb); }
               for(int i=limit; i>=0; i--)
               {
                  int y = iBarShift(NULL,TimeFrame,Time[i]);
                     mom[i]   = iCustom(ForSymbol,TimeFrame,indicatorFileName,PERIOD_CURRENT,"",MomentumPeriod,AvgPeriod,AvgType,ZoneUp,ZoneDown,ColorOn, 5,y);
                     trend[i] = iCustom(ForSymbol,TimeFrame,indicatorFileName,PERIOD_CURRENT,"",MomentumPeriod,AvgPeriod,AvgType,ZoneUp,ZoneDown,ColorOn,10,y);
                     momDa[i] = EMPTY_VALUE;
                     momDb[i] = EMPTY_VALUE;
                     momUa[i] = EMPTY_VALUE;
                     momUb[i] = EMPTY_VALUE;
                     levup[i] = ZoneUp;
                     levdn[i] = ZoneDown;
                     levmi[i] = (levup[i]+levdn[i])/2.0;
                     shadowa[i] = EMPTY_VALUE;
                     shadowb[i] = EMPTY_VALUE;
                     
                     if (!Interpolate || (i>0 && y==iBarShift(NULL,TimeFrame,Time[i-1]))) continue;
                  
                     //
                     //
                     //
                     //
                     //
                  
                        int n,j; datetime time = iTime(NULL,TimeFrame,y);
                           for(n = 1; (i+n)<Bars && Time[i+n] >= time; n++) continue;	
                           for(j = 1; j<n && (i+n)<Bars && (i+j)<Bars; j++)
                              mom[i+j]   = mom[i]   + (mom[i+n]   - mom[i]  )*j/n;
               }
               for(int i=limit; i>=0; i--)
               {
                  if (i<Bars-1 && trend[i] ==  1) { PlotPoint(i,momUa,momUb,mom); PlotPoint(i,shadowa,shadowb,mom); }
                  if (i<Bars-1 && trend[i] == -1) { PlotPoint(i,momDa,momDb,mom); PlotPoint(i,shadowa,shadowb,mom); }
               }
               return(0);
            }

   //
   //
   //
   //
   //

   int avgPeriod = AvgPeriod; if (avgPeriod<=1) avgPeriod = MomentumPeriod;
     if (trend[limit]== 1) { CleanPoint(limit,momUa,momUb); CleanPoint(limit,shadowa,shadowb); }
     if (trend[limit]==-1) { CleanPoint(limit,momDa,momDb); CleanPoint(limit,shadowa,shadowb); }
     for (int i=limit; i>=0; i--)
     {  
      if (i>=Bars-MomentumPeriod) { diff[i] = 0; iCustomMa(AvgType,0,avgPeriod,i,0); iCustomMa(AvgType,0,avgPeriod,i,1); continue; }
            diff[i] = Close[i]-Close[i+MomentumPeriod];
            double u=0,d=0;
               if (diff[i]>diff[i+1]) u=diff[i]-diff[i+1];
               if (diff[i]<diff[i+1]) d=diff[i+1]-diff[i];

               //
               //
               //
               //
               //
                  
               double avgu = iCustomMa(AvgType,u,avgPeriod,i,0);
               double avgd = iCustomMa(AvgType,d,avgPeriod,i,1);
               if (avgd!=0)
                     mom[i]  = 100.0-100.0/(1.0+(avgu/avgd));
               else  mom[i]  = 50;            

         //
         //
         //
         //
         //
                  
            levup[i] = ZoneUp;
            levdn[i] = ZoneDown;
            levmi[i] = (levup[i]+levdn[i])/2.0;
            momDa[i]   = momDb[i]   = EMPTY_VALUE;
            momUa[i]   = momUb[i]   = EMPTY_VALUE;
            shadowa[i] = shadowb[i] = EMPTY_VALUE;
            trend[i]   = 0;
            switch(ColorOn)
            {
               case cc_onLevels:
                  if (mom[i]>levup[i]) trend[i] =  1;
                  if (mom[i]<levdn[i]) trend[i] = -1;
                  break;
               case cc_onMiddle:                  
                  if (mom[i]>levmi[i]) trend[i] =  1;
                  if (mom[i]<levmi[i]) trend[i] = -1;
                  break;
               default :
                  if (i<Bars-1)
                  {
                     if (mom[i]>mom[i+1]) trend[i] =  1;
                     if (mom[i]<mom[i+1]) trend[i] = -1;
                  }                  
            }                  
         
         //
         //
         //
         //
         //
         
         
         if (trend[i] ==  1) { PlotPoint(i,momUa,momUb,mom); PlotPoint(i,shadowa,shadowb,mom); }
         if (trend[i] == -1) { PlotPoint(i,momDa,momDb,mom); PlotPoint(i,shadowa,shadowb,mom); }
   }
   if (alertsOn)
   {
      int whichBar = (alertsOnCurrent) ? 0 : 1;
      if (trend[whichBar] != trend[whichBar+1])
      if (trend[whichBar] == 1)
            doAlert("up");
      else  doAlert("down");       
   }     
return(0);
}   

//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
//
//
//
//
//

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("");
}

//
//
//
//
//

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; }
}

//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

#define _maInstances 2
#define _maWorkBufferx1 1*_maInstances
#define _maWorkBufferx2 2*_maInstances

double iCustomMa(int mode, double price, double length, int r, int instanceNo=0)
{
   r = Bars-r-1;
   switch (mode)
   {
      case avgSma   : return(iSma(price,(int)length,r,instanceNo));
      case avgEma   : return(iEma(price,length,r,instanceNo));
      case avgSmma  : return(iSmma(price,(int)length,r,instanceNo));
      case avgLwma  : return(iLwma(price,(int)length,r,instanceNo));
      default       : return(price);
   }
}

//
//
//
//
//

double workSma[][_maWorkBufferx2];
double iSma(double price, int period, int r, int instanceNo=0)
{
   if (period<=1) return(price);
   if (ArrayRange(workSma,0)!= Bars) ArrayResize(workSma,Bars); instanceNo *= 2; int k;

   //
   //
   //
   //
   //
      
   workSma[r][instanceNo+0] = price;
   workSma[r][instanceNo+1] = price; for(k=1; k<period && (r-k)>=0; k++) workSma[r][instanceNo+1] += workSma[r-k][instanceNo+0];  
   workSma[r][instanceNo+1] /= 1.0*k;
   return(workSma[r][instanceNo+1]);
}

//
//
//
//
//

double workEma[][_maWorkBufferx1];
double iEma(double price, double period, int r, int instanceNo=0)
{
   if (period<=1) return(price);
   if (ArrayRange(workEma,0)!= Bars) ArrayResize(workEma,Bars);

   //
   //
   //
   //
   //
      
   workEma[r][instanceNo] = price;
   double alpha = 2.0 / (1.0+period);
   if (r>0)
          workEma[r][instanceNo] = workEma[r-1][instanceNo]+alpha*(price-workEma[r-1][instanceNo]);
   return(workEma[r][instanceNo]);
}

//
//
//
//
//

double workSmma[][_maWorkBufferx1];
double iSmma(double price, double period, int r, int instanceNo=0)
{
   if (period<=1) return(price);
   if (ArrayRange(workSmma,0)!= Bars) ArrayResize(workSmma,Bars);

   //
   //
   //
   //
   //

   if (r<period)
         workSmma[r][instanceNo] = price;
   else  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 instanceNo=0)
{
   if (period<=1) return(price);
   if (ArrayRange(workLwma,0)!= Bars) ArrayResize(workLwma,Bars);
   
   //
   //
   //
   //
   //
   
   workLwma[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*workLwma[r-k][instanceNo];  
      }             
      return(sum/sumw);
}

//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

void doAlert(string doWhat)
{
   static string   previousAlert="nothing";
   static datetime previousTime;
   string message;
   
      if (previousAlert != doWhat || previousTime != Time[0]) {
          previousAlert  = doWhat;
          previousTime   = Time[0];

          //
          //
          //
          //
          //

          message = timeFrameToString(_Period)+" "+_Symbol+" at "+TimeToStr(TimeLocal(),TIME_SECONDS)+" Momentum pinball "+doWhat;
             if (alertsMessage)   Alert(message);
             if (alertsPushNotif) SendNotification(message);
             if (alertsEmail)     SendMail(_Symbol+" Momentum pinball ",message);
             if (alertsSound)     PlaySound("alert2.wav");
      }
}