//Revisions by Trainman, July 26, 2012
#property copyright "Extreme TMA System"
#property link      "http://www.forexfactory.com/showthread.php?t=343533m"

#property indicator_chart_window
#property indicator_buffers    6
#property indicator_color1     CLR_NONE
#property indicator_color2     Black  //White
#property indicator_color3     Black  //White
#property indicator_color4     Blue //Lime 
#property indicator_color5     Red
#property indicator_color6     White
#property indicator_style2     STYLE_DOT
#property indicator_style3     STYLE_DOT
#property  indicator_width1 1
#property  indicator_width2 1
#property  indicator_width3 1
#property  indicator_width4 1
#property  indicator_width5 1
#property  indicator_width6 1

#define PIPSTOFORCERECALC 10

extern string TimeFrame       = "Current";
extern int    TMAPeriod      = 20;
extern int    Price           = PRICE_CLOSE;
extern double ATRMultiplier   = 2.5;
extern int    ATRPeriod       = 300;
extern double TrendThreshold = -0.1; //0.5;
extern bool ShowCenterLine = false;

extern bool   UpperBandTouchAlert = false;
extern bool   LowerBandTouchAlert = false;
extern bool	  SlopeChangeAlertBull = false;
extern bool	  SlopeChangeAlertBear = false;
extern bool   alertsMessage   = false;
extern bool   alertsSound     = false;
extern bool   alertsEmail     = false;
input bool    alertsNotify    = true;
extern bool   MoveEndpointEveryTick = false;
extern int    MaxBarsBack     = 5000;


double gTMA[];
double gUpperBand[];
double gLowerBand[];
double gBull[];
double gBear[];
double gNeutral[];
 
int    gTimeFrameValue,
       gFirstAvailableBar;
bool gAlertHappened;
datetime gAlertTime;
double gTICK;
bool gAdditionalDigit;
double  gSumTMAPeriod,
        gTickScaleFactor,
        gThreshold,
        gPriorTick,
        gFullSumW;

int gSlopeMonitor[3];	// gSlopeMonitor[0] = Neutral slope
						// gSlopeMonitor[1] = Bull slope
						// gSlopeMonitor[2] = Bear slope

int init() {
	
	if (TimeFrame == -1)
      switch(Period())
      {
         case 1:     TimeFrame = 5 ;     break;
         case 5:     TimeFrame = 15 ;    break;
         case 15:    TimeFrame = 60 ;    break;
         case 30:    TimeFrame = 240 ;    break;
         case 60:    TimeFrame = 240 ;   break;
         case 240:   TimeFrame = 1440 ;  break;
         case 1440:  TimeFrame = 10080 ; break;
         case 10080: TimeFrame = 43200 ; break;
         default :   TimeFrame = Period();
      }
	
	
	
	
	
	
	
	
	
	
	
	gAdditionalDigit = MarketInfo(Symbol(), MODE_MARGINCALCMODE) == 0 && MarketInfo(Symbol(), MODE_PROFITCALCMODE) == 0 && Digits % 2 == 1;

	gTICK = MarketInfo(Symbol(), MODE_TICKSIZE);
	if (gAdditionalDigit) {
		gTICK *= 10;
	}
  
	gTimeFrameValue = stringToTimeFrame(TimeFrame);
			 
	IndicatorBuffers(6); 
	SetIndexBuffer(0,gTMA); 
	SetIndexBuffer(1,gUpperBand); 
	SetIndexBuffer(2,gLowerBand); 
	SetIndexBuffer(3,gBull); 
	SetIndexBuffer(4,gBear); 
	SetIndexBuffer(5,gNeutral); 

	//SetIndexLabel(0, "FastTMA " + TimeFrame + ")");  
	SetIndexLabel(1, "FastTMA " + TimeFrame + " Upper line");  
	SetIndexLabel(2, "FastTMA " + TimeFrame + " Lower line");  
	SetIndexLabel(3, "FastTMA(" + TimeFrame + ")");  
	SetIndexLabel(4, "FastTMA(" + TimeFrame + ")");  
	SetIndexLabel(5, "FastTMA(" + TimeFrame + ")"); 

	IndicatorDigits(Digits);

	IndicatorShortName(TimeFrameValueToString(gTimeFrameValue)+" TMA bands ("+TMAPeriod+")");
	gSumTMAPeriod = 0;

	for (int i = 1; i <= TMAPeriod; i++) {
		gSumTMAPeriod += i;
	}

	gFullSumW = TMAPeriod + 1 + 2 * gSumTMAPeriod;
	gTickScaleFactor = (TMAPeriod + 1) / (TMAPeriod + 1 + gSumTMAPeriod); // relative weight of latest tick
	gPriorTick = Close[0];

	if (Digits < 4) {
		gThreshold = PIPSTOFORCERECALC * 0.01;
	} else {
		gThreshold = PIPSTOFORCERECALC * 0.0001;
	}
	// gThreshold = 10pips

	gFirstAvailableBar = iBars(NULL, gTimeFrameValue) - TMAPeriod - 1;

	gSlopeMonitor[0] = -1;
	gSlopeMonitor[1] = -1;
	gSlopeMonitor[2] = -1;
	
	return(0);
}

int deinit() { 
	return(0); 
}


int start() {
	int counted_bars = IndicatorCounted();
	if (counted_bars < 0) {
		return(-1);
	}
	
	int barsToProcess = (Bars-counted_bars);


	if (Period() > gTimeFrameValue) {
		return(0); // don't plot lower TFs on upper TF charts
	}

	int i,limit;
	static double PriceAtFullRecalc = 0;
	static double range = 0;
	static double slope = 0;
  
	// if (uncounted bars are zero and price change is small)
	if ((barsToProcess == 1) && (MathAbs(Close[0] - PriceAtFullRecalc) < gThreshold)) {
		if (MoveEndpointEveryTick) { 
			// incremental change to end point
			gTMA[0] = CalcTmaUpdate(gTMA[0]);
			gUpperBand[0] = gTMA[0] + range;
			gLowerBand[0] = gTMA[0] - range;
			DrawCenterLine(0, slope);   
		} else {
			return(0);
		}
	} else {
		// Perform complete recalculation
		PriceAtFullRecalc = Close[0];	// Record what the price of currency pair was when full recalc took place
		
		if (counted_bars>0) {
			counted_bars--;
		}
		
		double barsPerTma = (gTimeFrameValue / Period());
		
		limit=MathMin(Bars-1, MaxBarsBack); 
		limit=MathMin(limit, barsToProcess + TMAPeriod * barsPerTma ); 
	   
		int mtfShift = 0;
		int lastMtfShift = 999;
		double prevTma = gTMA[limit+1];
		double tmaVal = gTMA[limit+1];
	
		for (i=limit; i>=0; i--) {
			if (gTimeFrameValue == Period()) {
				mtfShift = i;
			} else {         
				mtfShift = iBarShift(Symbol(), gTimeFrameValue, Time[i]);
			} 
		  
			if (mtfShift > gFirstAvailableBar) {
				// exceeded available historical data
				continue; 
			}
		  
			if(mtfShift == lastMtfShift) {       
				gTMA[i] =gTMA[i+1] + ((tmaVal - prevTma) * (1/barsPerTma)); 

				gUpperBand[i] = gTMA[i] + range;
				gLowerBand[i] = gTMA[i] - range;

				DrawCenterLine(i, slope);  

				continue;
			}
		  
			lastMtfShift = mtfShift;
			prevTma = tmaVal;
			tmaVal = CalcTma(mtfShift);

			range = iATR(NULL, gTimeFrameValue, ATRPeriod, mtfShift+10) * ATRMultiplier;
			if (range == 0) {
				range = 1;
			}
		  
			if (barsPerTma > 1) {
				gTMA[i] =prevTma + ((tmaVal - prevTma) * (1/barsPerTma));
			} else {
				gTMA[i] =tmaVal;
			}
			
			gUpperBand[i] = gTMA[i]+range;
			gLowerBand[i] = gTMA[i]-range;

			slope = (tmaVal-prevTma) / ((range / ATRMultiplier) * 0.1);
				
			DrawCenterLine(i, slope); 
		}
	}

	manageAlerts();
	return(0);
}

void DrawCenterLine(int barID, double slope) {
	int bullCntr = 0;
	int bearCntr = 0;
	string message;
	
	gBull[barID] = EMPTY_VALUE;
	gBear[barID] = EMPTY_VALUE;          
	gNeutral[barID] = EMPTY_VALUE; 
   
	if (ShowCenterLine) {
		if (slope > TrendThreshold) {
			gBull[barID] = gTMA[barID];
		} else if (slope < (-1 * TrendThreshold)) {
			gBear[barID] = gTMA[barID];
		} else {
			gNeutral[barID] = gTMA[barID];
		}
		
		int barsToProcess = (Bars-IndicatorCounted());
		if (barsToProcess > 10) {
			// Indicator may have just been initialized, no need to display any alerts yet
		} else {
			// Indicator already initialized and we are receiving live ticks
			if (barID == 0) {
				if ((gNeutral[1] != EMPTY_VALUE) && (gNeutral[1] > 0)) {
					gSlopeMonitor[0] = 1;
					
					// gSlopeMonitor[1] = 0;
					// gSlopeMonitor[2] = 0;					
					
					if ((gSlopeMonitor[1] == -1) && (gSlopeMonitor[2] == -1)) {
						// Check what the last slope type was before turning neutral
						for (int i=1; i<=200; i++) {
							// Check if last slope was bull
							if ((gBull[i] != EMPTY_VALUE) && (gBull[i] > 0)) {
								bullCntr++;
							} else {
								bullCntr = 0;
							}
							
							if (bullCntr > 1) {
								gSlopeMonitor[1] = 1;
								gSlopeMonitor[2] = 0;
								break;
							}
							
							// Check if last slope was bear
							if ((gBear[i] != EMPTY_VALUE) && (gBear[i] > 0)) {
								bearCntr++;
							} else {
								bearCntr = 0;
							}
							
							if (bearCntr > 1) {
								gSlopeMonitor[2] = 1;
								gSlopeMonitor[1] = 0;
								break;
							}						
						}
					}
				} else if ((gBull[1] != EMPTY_VALUE) && (gBull[1] > 0)) {
					gSlopeMonitor[1] = 1;
					message = "";
					
					if (gSlopeMonitor[2] == 1) { // Previous was bear, but now bull
						gSlopeMonitor[2] = 0;
						message = StringConcatenate(Symbol()," TMA slope has changed from bear to bull");
					} else if (gSlopeMonitor[0] == 1) { // Previous was neutral, but now bull 
						gSlopeMonitor[0] = 0;
						// message = StringConcatenate(Symbol()," TMA slope has changed from neutral to bull");
					}
					
					if (message != "") {
						// ALERT ***********
						if (SlopeChangeAlertBull) {						
							//message = StringConcatenate(Symbol()," TMA slope has changed from bear to bull");

							if (alertsMessage) Alert(message);
							if (alertsEmail)   SendMail(StringConcatenate(Symbol()," TMA bands "),message);
							if (alertsSound)   PlaySound("alert2.wav");	
						}
					}
				} else if ((gBear[1] != EMPTY_VALUE) && (gBear[1] > 0)) {
					gSlopeMonitor[2] = 1;
					message = "";
					
					if (gSlopeMonitor[1] == 1) { // Previous was bull, but now bear
						gSlopeMonitor[1] = 0;
						message = StringConcatenate(Symbol()," TMA slope has changed from bull to bear");
					} else if (gSlopeMonitor[0] == 1) { // Previous was neutral, but now bear 
						gSlopeMonitor[0] = 0;
						// message = StringConcatenate(Symbol()," TMA slope has changed from neutral to bear");
					}

					if (message != "") {
						// ALERT ***********
						if (SlopeChangeAlertBear) {						
							//message = StringConcatenate(Symbol()," TMA slope has changed from bear to bull");

							if (alertsMessage) Alert(message);
							if (alertsEmail)   SendMail(StringConcatenate(Symbol()," TMA bands "),message);
							if (alertsSound)   PlaySound("alert2.wav");
							if (alertsNotify)  SendNotification(message);	
						}
					}					
				}
			}
		}
	}
}


//---------------------------------------------------------------------
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, gTimeFrameValue, i);

	for (k = 1; k <= TMAPeriod; k++) {
		sum = sum + (j - k) * (iClose(NULL, gTimeFrameValue, i+k) + iClose(NULL, gTimeFrameValue, i-k));
	}
	return( sum / gFullSumW );
}

//---------------------------------------------------------------------
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, gTimeFrameValue, i+k);
		sumW += (j - k);
	}

	// compute right half
	j = TMAPeriod;
	for (k = i-1; k >= 0; k--) {
		sum += j * iClose(NULL, gTimeFrameValue, k);
		sumW += j;
		
		j--;
	}

	gPriorTick = 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] - gPriorTick) * gTickScaleFactor;

	gPriorTick = Close[0];

	return( r );
}
//---------------------------------------------------------------------

void manageAlerts() {
   // if (BandTouchAlertOn) { 
   
	int trend; 

	if (Close[0] > gUpperBand[0]) {
		trend =  1;
	} else if (Close[0] < gLowerBand[0]) {
		trend = -1;
	} else {
		gAlertHappened = false;
	}
		
	if (!gAlertHappened && gAlertTime != Time[0]) {       
		if (trend == 1) {
			if (UpperBandTouchAlert) {
				doAlert("up");
			}
		}
		
		if (trend ==-1) {
			if (LowerBandTouchAlert) {
				doAlert("down");
			}
		} 
	}
	  
   // }
}


void doAlert(string doWhat) { 
	if (gAlertHappened) {
		return;
	}
   
	gAlertHappened = true;
	gAlertTime = Time[0];
	string message;
	 
	message =  StringConcatenate(Symbol()," at ",TimeToStr(TimeLocal(),TIME_SECONDS)," "+TimeFrameValueToString(gTimeFrameValue)+" TMA bands price penetrated ",doWhat," band");

	if (alertsMessage) Alert(message);
	if (alertsEmail)   SendMail(StringConcatenate(Symbol()," TMA bands "),message);
	if (alertsSound)   PlaySound("alert2.wav");
	if (alertsNotify)  SendNotification(message);
}

//+-------------------------------------------------------------------
//|   Time Frame Handlers                                                               
//+-------------------------------------------------------------------


string sTfTable[] = {"M1","M5","M15","M30","H1","H4","D1","W1","MN"};
int    iTfTable[] = {1,5,15,30,60,240,1440,10080,43200};


int stringToTimeFrame(string tfs)
{
   tfs = StringUpperCase(tfs);
   for (int i=ArraySize(iTfTable)-1; i>=0; i--)
   {
      if (tfs==sTfTable[i] || tfs==""+iTfTable[i]) 
      {
//         return(MathMax(iTfTable[i],Period()));
         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("");
}

string StringUpperCase(string str)
{
   string   s = str;

   for (int length=StringLen(str)-1; length>=0; length--)
   {
      int charX = StringGetChar(s, length);
         if((charX > 96 && charX < 123) || (charX > 223 && charX < 256))
                     s = StringSetChar(s, length, charX - 32);
         else if(charX > -33 && charX < 0)
                     s = StringSetChar(s, length, charX + 224);
   }
   return(s);
}