//+------------------------------------------------------------------+
//|                                              Linear Momentum.mq4 |
//|                                Copyright 2015, Totom Sukopratomo |
//|                                       https://www.mqlmonster.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Totom Sukopratomo"
#property link      "https://www.mqlmonster.com"
#property version   "1.00"

#property indicator_separate_window
#property indicator_buffers    3
#property indicator_label1     "Linear momentum"
#property indicator_type1      DRAW_LINE
#property indicator_color1     clrMediumSeaGreen
#property indicator_width1     2
#property indicator_label2     "Linear momentum"
#property indicator_type2      DRAW_LINE
#property indicator_color2     clrOrangeRed
#property indicator_width2     2
#property indicator_label3     "Linear momentum"
#property indicator_type3      DRAW_LINE
#property indicator_color3     clrOrangeRed
#property indicator_width3     2
#property indicator_level1     0.0
#property indicator_levelcolor clrDimGray
#property indicator_levelstyle STYLE_DOT
#property strict

//
//
//

extern ENUM_TIMEFRAMES  TimeFrame       = PERIOD_CURRENT;       // Time frame to use
input int               SmoothingPeriod = 14;                   // Smoothing Period
enum  enMaTypes
      {
         ma_sma,                                                // Simple moving average
         ma_ema,                                                // Exponential moving average
         ma_smma,                                               // Smoothed MA
         ma_lwma,                                               // Linear weighted MA
      };
input enMaTypes         SmoothingMethod = ma_ema;               // Smoothing Method
input bool              alertsOn        = false;                // Alerts on true/false?
input bool              alertsOnCurrent = true;                 // Alerts open bar true/false?
input bool              alertsMessage   = true;                 // Alerts pop-up message true/false?
input bool              alertsSound     = false;                // Alerts sound true/false?
input bool              alertsNotify    = false;                // Alerts push notification true/false?
input bool              alertsEmail     = false;                // Alerts email true/false?
input string            soundFile       = "alert2.wav";         // Sound file
input bool              Interpolate     = true;                 // Interpolated in mtf mode


double  linm[],valda[],valdb[],temp[],valt[],count[];
string indicatorFileName;
#define _mtfCall(_buff,_ind) iCustom(NULL,TimeFrame,indicatorFileName,PERIOD_CURRENT,SmoothingPeriod,SmoothingMethod,alertsOn,alertsOnCurrent,alertsMessage,alertsSound,alertsNotify,alertsEmail,soundFile,_buff,_ind)

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

int OnInit()
 {
   IndicatorBuffers(6);
   SetIndexBuffer(0,linm, INDICATOR_DATA);
   SetIndexBuffer(1,valda,INDICATOR_DATA);
   SetIndexBuffer(2,valdb,INDICATOR_DATA);
   SetIndexBuffer(3,temp);
   SetIndexBuffer(4,valt);
   SetIndexBuffer(5,count);
   
   indicatorFileName = WindowExpertName();
   TimeFrame         = fmax(TimeFrame,_Period);
   
   IndicatorSetString(INDICATOR_SHORTNAME,timeFrameToString(TimeFrame)+" LM ("+(string)SmoothingPeriod+")");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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 i,limit=fmin(rates_total-prev_calculated+1,rates_total-1); count[0]=limit;
      if (TimeFrame!=_Period)
      {
         limit = (int)fmax(limit,fmin(rates_total-1,_mtfCall(5,0)*TimeFrame/_Period));
         if (valt[i]==-1) iCleanPoint(limit,valda,valdb);
         for (i=limit;i>=0 && !_StopFlag; i--)
         {
            int y = iBarShift(NULL,TimeFrame,time[i]);
               linm[i]  = _mtfCall(0,y);
               valda[i] = valdb[i] = EMPTY_VALUE; 
               valt[i]  = _mtfCall(4,y);
                 
               //
               //
               //
                     
               if (!Interpolate || (i>0 && y==iBarShift(NULL,TimeFrame,time[i-1]))) continue;
                  #define _interpolate(buff) buff[i+k] = buff[i]+(buff[i+n]-buff[i])*k/n
                  int n,k; datetime btime = iTime(NULL,TimeFrame,y);
                     for(n = 1; (i+n)<rates_total && time[i+n] >= btime; n++) continue;	
                     for(k = 1; k<n && (i+n)<rates_total && (i+k)<rates_total; k++) _interpolate(linm);                                         
        }   
        for (i=limit;i>=0 && !_StopFlag; i--)  if (valt[i] == -1) iPlotPoint(i,valda,valdb,linm); 
	return(rates_total);
	}      
   
   //
   //
   //
   
   if (valt[limit]==-1) iCleanPoint(limit,valda,valdb);
   for (i=limit;i>=0 && !_StopFlag; i--)
   { 
       temp[i] = tick_volume[i]*(close[i]-open[i]);
       linm[i] = iCustomMa(SmoothingMethod,temp[i],SmoothingPeriod,i,rates_total);
       valt[i] = (i<rates_total-1) ? (linm[i]>0) ? 1 : (linm[i]<0) ? -1 : valt[i+1]  : 0; 
       valda[i] = valdb[i] = EMPTY_VALUE; if (valt[i] == -1) iPlotPoint(i,valda,valdb,linm);  
   }
   if (alertsOn)
   {
      int whichBar = (alertsOnCurrent) ? 0 : 1;
      if (valt[whichBar] != valt[whichBar+1])
      {
         if (valt[whichBar] == 1) doAlert(" crossing zero up");
         if (valt[whichBar] ==-1) doAlert(" crossing zero down");       
      }         
   }       
return(rates_total);
}

//------------------------------------------------------------------
//                                                                  
//------------------------------------------------------------------
//
//
//
//
//

string getAvgName(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 iCleanPoint(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 iPlotPoint(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; }
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

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)+" Linear-Momentum "+doWhat;
             if (alertsMessage)  Alert(message);
             if (alertsNotify)   SendNotification(message);
             if (alertsEmail)    SendMail(_Symbol+" Linear-Momentum ",message);
             if (alertsSound)    PlaySound(soundFile);
      }
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

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("");
}