//
// Fast TMA line indicator for CSwM trading system by Sai Ram
//

#property strict

#define PIPSTOFORCERECALC 10

#property indicator_chart_window
#property indicator_buffers    7

#property indicator_color1     0x00e78969
#property indicator_color2     0x002828ff
#property indicator_color3     0x00e78969
#property indicator_color4     0x002828ff
#property indicator_color5     0x00e78969
#property indicator_color6     0x002828ff
#property indicator_color7     0x00e78969

#property indicator_width1     1
#property indicator_width2     1
#property indicator_width3     1
#property indicator_width4     1
#property indicator_width5     1
#property indicator_width6     2
#property indicator_width7     2

#property indicator_style1     2
#property indicator_style2     2
#property indicator_style3     2
#property indicator_style4     2
#property indicator_style5     2
#property indicator_style6     0
#property indicator_style7     0

extern string FastTMALine_CSwM_v1_01___2015_May_20 = "Fast TMA Line CSwM Indicator v1.01"; // DEV code version
extern string note0                            = "---";
extern string note1                            = "Select TimeFrame. 0 = current TF, 1, 5, 15, 30, 60, 240, 1440, 10080, 43200";
extern int    selected_TF                      = 0;
extern string note2                            = "---";
extern string TMASettings                      = "TMA Settings";
extern int    TMAPeriod                        = 56;
extern int    Price                            = PRICE_CLOSE;
extern double ATRMultiplier1                   = 3.5;
extern double ATRMultiplier2                   = 4.2;
extern double ATRMultiplier3                   = 4.9;
extern int    ATRPeriod                        = 100;
extern double TrendThreshold                   = 0.5;
extern string note3                            = "---";
extern string note4                            = "Alerts Settings";
extern bool   alertsOn                         = false;
extern bool   alertsMessage                    = false;
extern bool   alertsSound                      = false;
extern bool   alertsEmail                      = false;
extern bool   MoveEndpointEveryTick            = false;
extern string note5                            = "---";
extern string note6                            = "Bars Back Settings";
extern int    MaxBarsBack                      = 750;


//

string   sTimeFrame   = " ";

double   tma[];
double   upperBand1[];
double   lowerBand1[];
double   upperBand2[];
double   lowerBand2[];
double   upperBand3[];
double   lowerBand3[];

int      TimeFrameValue    = 0;
int      FirstAvailableBar = 0;
bool     AlertHappened     = false;
datetime AlertTime         = 0;
double   TICK              = 0.0;
bool     AdditionalDigit   =  false;
double   SumTMAPeriod      = 0.0;
double   TickScaleFactor   = 0.0;
double   Threshold         = 0.0;
double   PriorTick         = 0.0;
double   FullSumW          = 0.0;
         
double   PriceAtFullRecalc = 0;
double   range1            = 0.0;
double   range2            = 0.0;
double   range3            = 0.0;
         
//

int init()
{
  
   // initialize & validate
       
   switch( selected_TF )
         {             
           case     1 : break;
           case     5 : break;
           case    15 : break;
           case    30 : break;
           case    60 : break;
           case   240 : break;
           case  1440 : break;
           case 10080 : break;
           case 43200 : break;
           default    : selected_TF = 0; 
         }
  
   if (  selected_TF == 0 )  selected_TF = Period();
   
   TimeFrameValue = selected_TF;
   sTimeFrame     = TimeFrameValueToString(TimeFrameValue);
   
   //
         
   AdditionalDigit = MarketInfo(Symbol(), MODE_MARGINCALCMODE) == 0 && MarketInfo(Symbol(), MODE_PROFITCALCMODE) == 0 && Digits % 2 == 1;

   TICK = MarketInfo(Symbol(), MODE_TICKSIZE);
   if ( AdditionalDigit ) 
      {
        TICK *= 10;
      }
             
   IndicatorBuffers   ( 7 );
   
   SetIndexBuffer     ( 0, tma        ); 
   SetIndexBuffer     ( 1, upperBand1 ); 
   SetIndexBuffer     ( 2, lowerBand1 ); 
   SetIndexBuffer     ( 3, upperBand2 ); 
   SetIndexBuffer     ( 4, lowerBand2 ); 
   SetIndexBuffer     ( 5, upperBand3 ); 
   SetIndexBuffer     ( 6, lowerBand3 );
  
   SetIndexLabel      ( 0, "FastTMALine CSwM (" + sTimeFrame + ") Center Line"  );  
   SetIndexLabel      ( 1, "FastTMALine CSwM"   + sTimeFrame + " Upper Line 1"  );  
   SetIndexLabel      ( 2, "FastTMALine CSwM"   + sTimeFrame + " Lower Line 1"  );  
   SetIndexLabel      ( 3, "FastTMALine CSwM"   + sTimeFrame + " Upper Line 2"  );  
   SetIndexLabel      ( 4, "FastTMALine CSwM"   + sTimeFrame + " Lower Line 2"  );  
   SetIndexLabel      ( 5, "FastTMALine CSwM"   + sTimeFrame + " Upper Line 3"  );  
   SetIndexLabel      ( 6, "FastTMALine CSwM"   + sTimeFrame + " Lower Line 3"  );  
   
   SetIndexEmptyValue ( 0, EMPTY_VALUE );
   SetIndexEmptyValue ( 1, EMPTY_VALUE );
   SetIndexEmptyValue ( 2, EMPTY_VALUE );
   SetIndexEmptyValue ( 3, EMPTY_VALUE );
   SetIndexEmptyValue ( 4, EMPTY_VALUE );
   SetIndexEmptyValue ( 5, EMPTY_VALUE );
   SetIndexEmptyValue ( 6, EMPTY_VALUE ); 
      
  //

   IndicatorShortName ( "TMA CSwM " + sTimeFrame + " (" + IntegerToString ( TMAPeriod, 0 ) + ")" );
  
   SumTMAPeriod = 0;
  
   for (int i = 1; i <= TMAPeriod; i++)
        SumTMAPeriod += i;
    
   FullSumW = TMAPeriod + 1 + 2 * SumTMAPeriod;
   TickScaleFactor = (TMAPeriod + 1) / (TMAPeriod + 1 + SumTMAPeriod); // relative weight of latest tick
   PriorTick = Close[0];
   if (Digits < 4)
     Threshold = PIPSTOFORCERECALC * 0.01;
   else
     Threshold = PIPSTOFORCERECALC * 0.0001;
    
   FirstAvailableBar = iBars(NULL, TimeFrameValue) - TMAPeriod - 1;
   return(0);
}

//
//
//

int deinit()
{
   return(0);
}

//
//
//

int start()
{
  int i, limit, counted_bars;
  
  double prevTma       = 0.0;
  double tmaVal        = 0.0;
  
  counted_bars  = IndicatorCounted();
  if ( counted_bars < 0 ) return(-1);
  
  if ( Period() > TimeFrameValue ) return(0); // don't plot lower TFs on upper TF charts
  
  // if (uncounted bars are zero and price change is small)
  if (((Bars - counted_bars) == 1) && (MathAbs(Close[0] - PriceAtFullRecalc) < Threshold))
    {
    if (MoveEndpointEveryTick)
      { // incremental change to end point
      tma[0] = CalcTmaUpdate(tma[0]);
      upperBand1[0] = tma[0] + range1;
      lowerBand1[0] = tma[0] - range1; 
      if ( ATRMultiplier2 != 0.0 )
         {
           upperBand2[0] = tma[0] + range2;
           lowerBand2[0] = tma[0] - range2;
         }  
      if ( ATRMultiplier3 != 0.0 )
         {
           upperBand3[0] = tma[0] + range3;
           lowerBand3[0] = tma[0] - range3;
         }   
      }
    else
      return(0);
    }
  else // complete recalculation
    {
    PriceAtFullRecalc = Close[0];
    if ( counted_bars > 0 ) counted_bars--;
    double barsPerTma = (TimeFrameValue / Period());
    limit = MathMin ( Bars - 1, MaxBarsBack ); 
    limit =( int ) MathMin ( limit, Bars - counted_bars + TMAPeriod * barsPerTma ); 
   
    int mtfShift = 0;
    int lastMtfShift = 999;
    
    if ( limit < Bars - 1 )
       {
         prevTma = tma[limit + 1];
         tmaVal  = tma[limit + 1];
       }
    else
       {
         prevTma = 0.0;
         tmaVal  = 0.0;
       }
       
    for (i=limit; i>=0; i--)
      {
      if ( i > Bars - 1 ) continue;   // no array out of range looking back;
      if (TimeFrameValue == Period())
        {
        mtfShift = i;
        }
      else
        {         
        mtfShift = iBarShift(Symbol(),TimeFrameValue,Time[i]);
        } 
      
      if (mtfShift > FirstAvailableBar) continue; // exceeded available historical data
      if(mtfShift == lastMtfShift)
        {       
        tma[i] =tma[i+1] + ((tmaVal - prevTma) * (1/barsPerTma));         
        upperBand1[i] = tma[i] + range1;
        lowerBand1[i] = tma[i] - range1;
        if ( ATRMultiplier2 != 0.0 )
           {
             upperBand2[i] = tma[i] + range2;
             lowerBand2[i] = tma[i] - range2;
           }  
        if ( ATRMultiplier3 != 0.0 )
           {
             upperBand3[i] = tma[i] + range3;
             lowerBand3[i] = tma[i] - range3;
           }   
           
        continue;
        }
      
      lastMtfShift = mtfShift;
      prevTma = tmaVal;
      tmaVal = CalcTma(mtfShift);
      
      range1 = iATR (NULL, TimeFrameValue, ATRPeriod, mtfShift + 10 ) * ATRMultiplier1 ;
      if ( ATRMultiplier2 != 0.0 ) range2 = iATR (NULL, TimeFrameValue, ATRPeriod, mtfShift + 10 ) * ATRMultiplier2 ;
      if ( ATRMultiplier3 != 0.0 ) range3 = iATR (NULL, TimeFrameValue, ATRPeriod, mtfShift + 10 ) * ATRMultiplier3 ;
      
      if ( range1 == 0) range1 = 1;
      if ( ATRMultiplier2 != 0.0 && range2 == 0 ) range2 = 1;
      if ( ATRMultiplier3 != 0.0 && range3 == 0 ) range3 = 1;
      
      if (barsPerTma > 1)
        {
        tma[i] =prevTma + ((tmaVal - prevTma) * (1/barsPerTma));
        }
      else
        {
        tma[i] =tmaVal;
        }
      upperBand1[i] = tma[i]+range1;
      lowerBand1[i] = tma[i]-range1;
      
      if ( ATRMultiplier2 != 0.0 )
         {
           upperBand2[i] = tma[i] + range2;
           lowerBand2[i] = tma[i] - range2;
         }  
      if ( ATRMultiplier3 != 0.0 )
         {
           upperBand3[i] = tma[i] + range3;
           lowerBand3[i] = tma[i] - range3;
         }   
      }
   }

   manageAlerts();
   return(0);
}


//---------------------------------------------------------------------
double CalcTma( int inx )
  {
  double tmas;
  if(inx >= TMAPeriod)
    tmas = CalcPureTma(inx);
  else
    tmas = CalcTmaEstimate(inx);
  return( tmas );
  }
  
//---------------------------------------------------------------------
double CalcPureTma( int i )
  {
  int j = TMAPeriod + 1; 
  int k;
  double sum = j * iClose(NULL, TimeFrameValue, i);
  for (k = 1; k <= TMAPeriod; k++)
    sum = sum + (j - k) * (iClose(NULL, TimeFrameValue, i+k) + iClose(NULL, TimeFrameValue, i-k));
  return( sum / FullSumW );
  }

//---------------------------------------------------------------------
double CalcTmaEstimate( int i )
//only returns correct result if i <= TMAPeriod
  {
  double sum = 0;
  double sumW;
  int k,
      j = TMAPeriod + 1;
      sumW = 0;
  // compute left half
  for (k = 0; k <= TMAPeriod; k++)
    {
    sum += (j - k) * iClose(NULL, TimeFrameValue, i+k);
    sumW += (j - k);
    }
  // compute right half
  j = TMAPeriod;
  for (k = i-1; k >= 0; k--)
    {
    sum += j * iClose(NULL, TimeFrameValue, k);
    sumW += j;
    j--;
    }
    
  PriorTick = Close[0];
  return( sum / sumW );
  }

//---------------------------------------------------------------------
// if the next tick arrives but it still goes in the same bar, this
// function updates the latest value without a complete recalculation
double CalcTmaUpdate( double PreviousTma )
  {
  double r = PreviousTma + (Close[0] - PriorTick) * TickScaleFactor;
  PriorTick = Close[0];
  return( r );
  }
//---------------------------------------------------------------------

void manageAlerts()
{
   if (alertsOn)
   { 
      int trend;        
      if (Close[0] > upperBand1[0]) trend =  1;
      else if (Close[0] < lowerBand1[0]) trend = -1;
      else {AlertHappened = false;}
            
      if (!AlertHappened && AlertTime != Time[0])
      {       
         if (trend == 1) doAlert("up");
         if (trend ==-1) doAlert("down");
      }         
   }
}


void doAlert(string doWhat)
{ 
   if (AlertHappened) return;
   AlertHappened = true;
   AlertTime = Time[0];
   string message;
     
   message =  StringConcatenate(Symbol()," at ",TimeToStr(TimeLocal(),TIME_SECONDS)," "+ sTimeFrame + " TMA bands price penetrated ",doWhat," band");
   if (alertsMessage) Alert(message);
   if (alertsEmail)   SendMail(StringConcatenate(Symbol(),"TMA bands "),message);
   if (alertsSound)   PlaySound("alert2.wav");

}

//+-------------------------------------------------------------------
//|   Subroutines                                                         
//+-------------------------------------------------------------------


string sTfTable[] = {"M1","M5","M15","M30","H1","H4","D1","W1","MN1"};
int    iTfTable[] = {1,5,15,30,60,240,1440,10080,43200};


int stringToTimeFrame(string tfs)
{
   for (int i=ArraySize(iTfTable)-1; i>=0; i--)
   {
      if (tfs==sTfTable[i] || tfs==""+ IntegerToString ( iTfTable[i], 0)) 
      {
         return(iTfTable[i]);
      }
   }
   return(Period());
   
}

//
//
//

string TimeFrameValueToString(int tf)
{
   for (int i=ArraySize(iTfTable)-1; i>=0; i--) 
         if (tf==iTfTable[i]) return(sTfTable[i]);
                              return("");
}

//
//
//