//+------------------------------------------------------------------
//|
//+------------------------------------------------------------------
#property copyright "mladen"
#property link      "www.forex-station.com"

#property indicator_separate_window
#property indicator_buffers 7
#property indicator_color1  clrGreen
#property indicator_color2  clrGreen
#property indicator_color3  clrRed
#property indicator_color4  clrRed
#property indicator_color5  clrGreen
#property indicator_color6  clrRed
#property indicator_color7  clrRed
#property indicator_width1  2
#property indicator_width3  2
#property indicator_width5  2
#property indicator_width6  2
#property indicator_width7  2
#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_tbiased2,   // Trend biased (extreme) 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
   pr_hatbiased2, // Heiken ashi trend biased (extreme) price
   pr_habclose,   // Heiken ashi (better formula) close
   pr_habopen ,   // Heiken ashi (better formula) open
   pr_habhigh,    // Heiken ashi (better formula) high
   pr_hablow,     // Heiken ashi (better formula) low
   pr_habmedian,  // Heiken ashi (better formula) median
   pr_habtypical, // Heiken ashi (better formula) typical
   pr_habweighted,// Heiken ashi (better formula) weighted
   pr_habaverage, // Heiken ashi (better formula) average
   pr_habmedianb, // Heiken ashi (better formula) median body
   pr_habtbiased, // Heiken ashi (better formula) trend biased price
   pr_habtbiased2 // Heiken ashi (better formula) trend biased (extreme) price
};

extern ENUM_TIMEFRAMES    TimeFrame                = PERIOD_CURRENT;   // Time frame to use
extern int                GaussPeriod              = 50;               // Gaussian period
extern int                GaussOrder               = 4;                // Gaussian order
extern enPrices           GaussPrice               = pr_close;         // Price to use
extern bool               alertsOn                 = false;            // Turn alerts on?
extern bool               alertsOnCurrent          = false;            // Alerts on still opened bar?
extern bool               alertsMessage            = true;             // Alerts should display message?
extern bool               alertsOnSlope            = true;             // Alerts on slope
extern bool               alertsOnZeroCross        = true;             // Alerts on zero cross
extern bool               alertsSound              = false;            // Alerts should play a sound?
extern bool               alertsNotify             = false;            // Alerts should send a notification?
extern bool               alertsEmail              = false;            // Alerts should send an email?
extern string             soundFile                = "alert2.wav";     // Sound file
extern bool               arrowsVisible            = false;            // Show arrows?
extern string             arrowsIdentifier         = "ao arrows1";     // Arrows ID
extern bool               arrowsOnNewest           = false;            // Arrows drawn on newst bar of higher time frame bar?
extern double             arrowsUpperGap           = 0.5;              // Arrows Upper Gap
extern double             arrowsLowerGap           = 0.5;              // Arrows lower gap
extern bool               arrowsOnZeroCross        = true;             // Show arrows on zero cross?
extern color              arrowsOnZeroCrossUpColor = clrLimeGreen;     // Zero cross Up arrow color
extern color              arrowsOnZeroCrossDnColor = clrRed;           // Zero cross Down arrows color
extern int                arrowsOnZeroCrossUpCode  = 241;              // Zero cross Up arrow code
extern int                arrowsOnZeroCrossDnCode  = 242;              // Zero cross Down arrow code
extern int                arrowsOnZeroCrossUpSize  = 2;                // Zero cross Up arrow size
extern int                arrowsOnZeroCrossDnSize  = 2;                // Zero cross Down arrow size
extern bool               arrowsOnSlope            = false;            // Show arrows on slope change?      
extern color              arrowsOnSlopeUpColor     = clrLimeGreen;     // Slope change Up arrow color
extern color              arrowsOnSlopeDnColor     = clrRed;           // Slope change Down arrows color
extern int                arrowsOnSlopeUpCode      = 159;              // Slope change Up arrow code
extern int                arrowsOnSlopeDnCode      = 159;              // Slope change Down arrow code
extern int                arrowsOnSlopeUpSize      = 2;                // Slope change Up arrow size
extern int                arrowsOnSlopeDnSize      = 2;                // Slope change Down arrow size  
extern bool               Interpolate              = true;             // Interpolate in mtf mode

//
//
//
//
//

double huu[],hud[],hdd[],hdu[],vel[],velDna[],velDnb[],slope[],trend[],count[];
string indicatorFileName;
#define _mtfCall(_buff,_ind) iCustom(NULL,TimeFrame,indicatorFileName,PERIOD_CURRENT,GaussPeriod,GaussOrder,GaussPrice,alertsOn,alertsOnCurrent,alertsMessage,alertsOnSlope,alertsOnZeroCross,alertsSound,alertsNotify,alertsEmail,soundFile,arrowsVisible,arrowsIdentifier,arrowsOnNewest,arrowsUpperGap,arrowsLowerGap,arrowsOnZeroCross,arrowsOnZeroCrossUpColor,arrowsOnZeroCrossDnColor,arrowsOnZeroCrossUpCode,arrowsOnZeroCrossDnCode,arrowsOnZeroCrossUpSize,arrowsOnZeroCrossDnSize,arrowsOnSlope,arrowsOnSlopeUpColor,arrowsOnSlopeDnColor,arrowsOnSlopeUpCode,arrowsOnSlopeDnCode,arrowsOnSlopeUpSize,arrowsOnSlopeDnSize,_buff,_ind)

//+------------------------------------------------------------------
//|                                                                  
//+------------------------------------------------------------------
// 
//
//
//
//

int init()
{
   IndicatorBuffers(10);
   SetIndexBuffer(0, huu); SetIndexStyle(0, DRAW_HISTOGRAM);
   SetIndexBuffer(1, hud); SetIndexStyle(1, DRAW_HISTOGRAM);
   SetIndexBuffer(2, hdd); SetIndexStyle(2, DRAW_HISTOGRAM);
   SetIndexBuffer(3, hdu); SetIndexStyle(3, DRAW_HISTOGRAM);
   SetIndexBuffer(4, vel);
   SetIndexBuffer(5, velDna);
   SetIndexBuffer(6, velDnb);
   SetIndexBuffer(7, trend);
   SetIndexBuffer(8, slope);
   SetIndexBuffer(9, count);
   
   indicatorFileName = WindowExpertName();
   TimeFrame         = fmax(TimeFrame,_Period);
   
   IndicatorShortName(timeFrameToString(TimeFrame)+" gaussian velocity ("+(string)GaussPeriod+")");
return(0);
}
int deinit()
{
   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);
   }
return(0);
}

//+------------------------------------------------------------------
//|                                                                  
//+------------------------------------------------------------------
//
//
//
//
//

int start()
{
   int i,counted_bars=IndicatorCounted();
      if(counted_bars<0) return(-1);
      if(counted_bars>0) counted_bars--;
         int limit = MathMin(Bars-counted_bars,Bars-1); count[0] = limit;
         if (TimeFrame != _Period)
         {
            limit = (int)fmax(limit,fmin(Bars-1,_mtfCall(9,0)*TimeFrame/_Period));
            for (i=limit;i>=0 && !_StopFlag; i--)
            {
                  int y = iBarShift(NULL,TimeFrame,Time[i]);
                     huu[i]    = EMPTY_VALUE;
                     hud[i]    = EMPTY_VALUE;
                     hdd[i]    = EMPTY_VALUE;
                     hdu[i]    = EMPTY_VALUE;
   	               vel[i]    = _mtfCall(4,y);
   	               velDna[i] = EMPTY_VALUE;
                     velDnb[i] = EMPTY_VALUE;
                     slope[i]  = _mtfCall(8,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 time = iTime(NULL,TimeFrame,y);
                         for(n = 1; (i+n)<Bars && Time[i+n] >= time; n++) continue;	
                         for(k = 1; k<n && (i+n)<Bars && (i+k)<Bars; k++) _interpolate(vel);
            }
            for(i=limit; i>=0; i--) 
            {
   	         if (vel[i]>0)
               if (slope[i]==1)
                     huu[i] = vel[i];
               else  hud[i] = vel[i];
               if (vel[i]<0)
               if (slope[i]==1)
                     hdu[i] = vel[i];
               else  hdd[i] = vel[i];
               if (slope[i] ==-1) PlotPoint(i,velDna,velDnb,vel);
            }
      return(0);
      }
      
      //
      //
      //
      //
      //
      
      if (trend[limit] ==-1) CleanPoint(limit,velDna,velDnb);
      for(i=limit; i>=0; i--)
      {
         double price  = getPrice(GaussPrice,Open,Close,High,Low,i);
                vel[i] = iGFilter(price,GaussPeriod,GaussOrder,i,0)-iGFilter(price,GaussPeriod,GaussOrder+1,i,1);
                huu[i]    = EMPTY_VALUE;
                hud[i]    = EMPTY_VALUE;
                hdd[i]    = EMPTY_VALUE;
                hdu[i]    = EMPTY_VALUE;
                velDna[i] = EMPTY_VALUE;
                velDnb[i] = EMPTY_VALUE;
                slope[i]  = (i<Bars-1) ? (vel[i]>vel[i+1]) ? 1 : (vel[i]<vel[i+1]) ? -1 : slope[i+1] : 0;
                trend[i]  = (i<Bars-1) ? (vel[i]>0)        ? 1 : (vel[i]<0)        ? -1 : trend[i+1] : 0;
                if (vel[i]>0)
                if (slope[i]==1)
                      huu[i] = vel[i];
                else  hud[i] = vel[i];
                if (vel[i]<0)
                if (slope[i]==1)
                      hdu[i] = vel[i];
                else  hdd[i] = vel[i];
                if (slope[i] ==-1) PlotPoint(i,velDna,velDnb,vel);
          
                //
                //
                //
                //
                //
                if (arrowsVisible)
                {
                  ObjectDelete(arrowsIdentifier+":1:"+(string)Time[i]);
                  ObjectDelete(arrowsIdentifier+":2:"+(string)Time[i]);
                  string lookFor = arrowsIdentifier+":"+(string)Time[i]; ObjectDelete(lookFor);
                  if (i<(Bars-1) && arrowsOnZeroCross && trend[i] != trend[i+1])
                  {
                     if (trend[i] == 1) drawArrow("1",0.5,i,arrowsOnZeroCrossUpColor,arrowsOnZeroCrossUpCode,arrowsOnZeroCrossUpSize,false);
                     if (trend[i] ==-1) drawArrow("1",0.5,i,arrowsOnZeroCrossDnColor,arrowsOnZeroCrossDnCode,arrowsOnZeroCrossDnSize,true);
                  }
                  if (i<(Bars-1) && arrowsOnSlope && slope[i] != slope[i+1])
                  {
                     if (slope[i] == 1) drawArrow("2",1,i,arrowsOnSlopeUpColor,arrowsOnSlopeUpCode,arrowsOnSlopeUpSize,false);
                     if (slope[i] ==-1) drawArrow("2",1,i,arrowsOnSlopeDnColor,arrowsOnSlopeDnCode,arrowsOnSlopeDnSize,true);
                  }      
                }
      } 
      
      //
      //
      //
      //
      //
      
      if (alertsOn)
      {
         int whichBar = 1; if (alertsOnCurrent) whichBar = 0;
         static datetime time1 = 0;
         static string   mess1 = "";
         if (alertsOnSlope && slope[whichBar] != slope[whichBar+1])
         {
            if (slope[whichBar] ==  1 ) doAlert(time1,mess1,whichBar,"sloping up");
            if (slope[whichBar] == -1 ) doAlert(time1,mess1,whichBar,"sloping down");
         }         
         static datetime time2 = 0;
         static string   mess2 = "";
         if (alertsOnZeroCross && trend[whichBar] != trend[whichBar+1])
         {
            if (trend[whichBar] ==  1 ) doAlert(time2,mess2,whichBar,"crossing zero up");
            if (trend[whichBar] == -1 ) doAlert(time2,mess2,whichBar,"crossing zero down");
         }         
      }               
return(0);
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//

#define gfInstances 2
int     gfperiods[gfInstances];
double  gfcoeffs[][gfInstances*3];
double  gffilters[][gfInstances];
double  iGFilter(double price, int period, int order, int i, int instanceNo=0)
{
   if (ArrayRange(gffilters,0)!=Bars)  ArrayResize(gffilters,Bars);
   if (ArrayRange(gfcoeffs,0)<order+1) ArrayResize(gfcoeffs,order+1);
   if (gfperiods[instanceNo]!=period)
   {
      gfperiods[instanceNo]=period;
         double b = (1.0 - MathCos(2.0*M_PI/period))/(MathPow(MathSqrt(2.0),2.0/order) - 1.0);
         double a = -b + MathSqrt(b*b + 2.0*b);
         for(int r=0; r<=order; r++)
         {
             gfcoeffs[r][instanceNo*3+0] = fact(order)/(fact(order-r)*fact(r));
             gfcoeffs[r][instanceNo*3+1] = MathPow(    a,r);
             gfcoeffs[r][instanceNo*3+2] = MathPow(1.0-a,r);
         }
   }

   //
   //
   //
   //
   //
   
   i = Bars-i-1;
   if (price==EMPTY_VALUE) price=0;
   gffilters[i][instanceNo] = price*gfcoeffs[order][instanceNo*3+1];
      double sign = 1;
         for (int r=1; r <= order && i-r>=0; r++, sign *= -1.0)
                  gffilters[i][instanceNo] += sign*gfcoeffs[r][instanceNo*3+0]*gfcoeffs[r][instanceNo*3+2]*gffilters[i-r][instanceNo];
   return(gffilters[i][instanceNo]);
}

//
//
//
//
//

double fact(int n)
{
   double a=1; for(int i=1; i<=n; i++) a*=i;
   return(a);
}

//------------------------------------------------------------------
//
//------------------------------------------------------------------
//
//
//
//
//
//

#define priceInstances     1
#define priceInstancesSize 4
double workHa[][priceInstances*priceInstancesSize];
double getPrice(int tprice, const double& open[], const double& close[], const double& high[], const double& low[], int i, int instanceNo=0)
{
  if (tprice>=pr_haclose)
   {
      if (ArrayRange(workHa,0)!= Bars) ArrayResize(workHa,Bars); instanceNo*=priceInstancesSize; int r = Bars-i-1;
         
         //
         //
         //
         //
         //
         
         double haOpen  = (r>0) ? (workHa[r-1][instanceNo+2] + workHa[r-1][instanceNo+3])/2.0 : (open[i]+close[i])/2;;
         double haClose = (open[i]+high[i]+low[i]+close[i]) / 4.0;
         if (tprice>=pr_habclose)
               if (high[i]!=low[i])
                     haClose = (open[i]+close[i])/2.0+(((close[i]-open[i])/(high[i]-low[i]))*fabs((close[i]-open[i])/2.0));
               else  haClose = (open[i]+close[i])/2.0; 
         double haHigh  = fmax(high[i], fmax(haOpen,haClose));
         double haLow   = fmin(low[i] , fmin(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 (tprice)
         {
            case pr_haclose:
            case pr_habclose:    return(haClose);
            case pr_haopen:   
            case pr_habopen:     return(haOpen);
            case pr_hahigh: 
            case pr_habhigh:     return(haHigh);
            case pr_halow:    
            case pr_hablow:      return(haLow);
            case pr_hamedian:
            case pr_habmedian:   return((haHigh+haLow)/2.0);
            case pr_hamedianb:
            case pr_habmedianb:  return((haOpen+haClose)/2.0);
            case pr_hatypical:
            case pr_habtypical:  return((haHigh+haLow+haClose)/3.0);
            case pr_haweighted:
            case pr_habweighted: return((haHigh+haLow+haClose+haClose)/4.0);
            case pr_haaverage:  
            case pr_habaverage:  return((haHigh+haLow+haClose+haOpen)/4.0);
            case pr_hatbiased:
            case pr_habtbiased:
               if (haClose>haOpen)
                     return((haHigh+haClose)/2.0);
               else  return((haLow+haClose)/2.0);        
            case pr_hatbiased2:
            case pr_habtbiased2:
               if (haClose>haOpen)  return(haHigh);
               if (haClose<haOpen)  return(haLow);
                                    return(haClose);        
         }
   }
   
   //
   //
   //
   //
   //
   
   switch (tprice)
   {
      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);        
      case pr_tbiased2:   
               if (close[i]>open[i]) return(high[i]);
               if (close[i]<open[i]) return(low[i]);
                                     return(close[i]);        
   }
   return(0);
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

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; }
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

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 doAlert(datetime& previousTime, string& previousAlert, int forBar, string doWhat)
{
   string message;
   
   if (previousAlert != doWhat || previousTime != Time[forBar]) {
       previousAlert  = doWhat;
       previousTime   = Time[forBar];

       //
       //
       //
       //
       //

       message =  StringConcatenate(Symbol()," ",timeFrameToString(_Period)," at ",TimeToStr(TimeLocal(),TIME_SECONDS)," gaussian velocity ",doWhat);
          if (alertsMessage) Alert(message);
          if (alertsNotify)  SendNotification(message);
          if (alertsEmail)   SendMail(StringConcatenate(Symbol()," gaussian velocity "),message);
          if (alertsSound)   PlaySound(soundFile);
   }
}

//-------------------------------------------------------------------
//                                                                  
//-------------------------------------------------------------------
//
//
//
//
//

void drawArrow(string nameAdd, double gapMul, int i,color theColor, int theCode, int theWidth, bool up)
{
   string name = arrowsIdentifier+":"+nameAdd+":"+(string)Time[i];
   double gap  = iATR(NULL,0,20,i)*gapMul;   
   
      //
      //
      //
      //
      //
      
      int add = 0; if (!arrowsOnNewest) add = _Period*60-1;
      ObjectCreate(name,OBJ_ARROW,0,Time[i]+add,0);
         ObjectSet(name,OBJPROP_ARROWCODE,theCode);
         ObjectSet(name,OBJPROP_WIDTH,theWidth);
         ObjectSet(name,OBJPROP_COLOR,theColor);
         if (up)
               ObjectSet(name,OBJPROP_PRICE1,High[i] + arrowsUpperGap * gap);
         else  ObjectSet(name,OBJPROP_PRICE1,Low[i]  - arrowsLowerGap * gap);
}
           

  

