//+------------------------------------------------------------------+
//|                                                  Custom MACD.mq4 |
//+------------------------------------------------------------------+

#property  copyright "Copyright © 2007, Herb Spirit, Inc., portions Josh Jones"
#property  link      "http://www.herbspirit.com/mql"

/*
Original version "Custom MACD.mq4" is Copyright © 2007, Herb Spirit, Inc.,
portions by Josh Jones at http://www.herbspirit.com/mql. 

Modification "MACD_Colored_v102" implemented static macd background levels
at +/- 15/30/45, and end of macd pip count (to change histogram color).

Modification "MACD_Colored_v103" by Josh Dance (forexfactory)
implements various signals, alerts and expanded macd background levels.

Modification "MACD_Colored_v104" by Josh Dance (forexfactory) adds ability
to show signal line for any macd input values (5/13/6, 12/26/9, etc.),
draws the macd line as a border around the histogram, and variablizes
the process of defining the macd background levels to better fit different
currency pairs.

Modification "MACD_Colored_v105" by Traderathome adds a solid zero line
and expands code for the macd background levels so that all separate
timeframes can be independently further adjusted in the hard code.
*/

#define INDICATOR_NAME  "MACD_Colored"
#define INDICATOR_VERSION "v105"

//---- indicator settings
#property  indicator_separate_window
#property  indicator_buffers 5
#property  indicator_color1  Navy //moving up
#property  indicator_color2  Red  //moving down
#property  indicator_color3  MidnightBlue //signal line
#property  indicator_color4  Black //macd line
#property  indicator_color5  Black //solid study zero line
#property  indicator_width1  1
#property  indicator_width2  1
#property  indicator_width3  1
#property  indicator_width4  1
#property  indicator_width5  1
#property  indicator_level1  45	
#property  indicator_level2  30	
#property  indicator_level3  15	
#property  indicator_level4  -15	
#property  indicator_level5  -30	
#property  indicator_level6  -45	
#property  indicator_level7  0
#property  indicator_levelcolor  DimGray //for macd background levels
#property  indicator_levelstyle  STYLE_DOT

//---- indicator parameters
extern string Alert_On="";
extern bool EMail_Alert=false;
extern int Max_Alerts=1;
extern int Alert_Before_Minutes=15;
extern int Alert_Every_Minutes=5;
extern bool ShowSignal=true;
extern int FastEMA=5;
extern int SlowEMA=13;
extern int SignalSMA=6;
extern int FontSize=8;
extern color FontColor=Black;

//---- indicator buffers
double     MacdBuffer[];
double     MacdBufferUp[];
double     MacdBufferDn[];
double     SignalBuffer[];
double     BorderLine[];
double     ZeroLine[];
datetime nextbartime;
datetime alertbartime,nextalerttime;
string shortname;
string alerttype[]={"RT","RB","VT","VB","TC","ZB"};
int alertcount;
int min1level[]={15,10,5,-5,-10,-15}; //values here meaningless
int min5level[]={15,10,5,-5,-10,-15}; //just assigning space
int min15level[]={15,10,5,-5,-10,-15}; // for arrays to hold
int min30level[]={15,10,5,-5,-10,-15}; // the calculated values
int hour1level[]={45,30,15,-15,-30,-45}; // used to draw macd
int hour4level[]={90,60,30,-30,-60,-90}; // background levels
int daylevel[]={150,100,50,-50,-100,-150}; //of this indicator

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
  
//---- drawing settings  
   SetIndexStyle(0,DRAW_HISTOGRAM);
   SetIndexStyle(1,DRAW_HISTOGRAM);
   SetIndexStyle(2,DRAW_LINE);
   SetIndexStyle(3,DRAW_LINE);
   SetIndexStyle(4,DRAW_LINE,STYLE_SOLID);
  
//---- macd background levels: adjust start price to accomdate other pairs
   double lastPrice = iClose(NULL,0,1);
   if (StringFind(Symbol(), "JPY", 0) != -1)
   {lastPrice = lastPrice / 100;}


// "This is the base multiplier.  Calculated from Phillip Nel's original
// 15/30/45 levels for EURUSD.  So, if 15 is our low level for a typical
// EURUSD value of 1.3, we say 1.3 * baseMult = 15, and we get baseMult
// ~= 11.5.  Good enough!  Doesn't have to be really exact." - Josh Dance  
   double baseHour1Mult = 11.5;
 
// here is where the levels can be adjusted by applying different factors
// (*_,/_)to the "baseHour1Mult" for the other timeframes 
   double baseHour4Mult = baseHour1Mult*1.5;
   double baseDayMult = baseHour1Mult*3;
   double base30MinMult = baseHour1Mult/2;
   double base15MinMult = baseHour1Mult/3;
   double base5MinMult = baseHour1Mult/4.5;
   double base1MinMult = baseHour1Mult/9;

   double low1MinLevel = base1MinMult*lastPrice;
   double low5MinLevel = base5MinMult*lastPrice;
   double low15MinLevel = base15MinMult*lastPrice;
   double low30MinLevel = base30MinMult*lastPrice;
   double lowHour1Level = baseHour1Mult*lastPrice;
   double lowHour4Level = baseHour4Mult*lastPrice;
   double lowDayLevel = baseDayMult*lastPrice;

//---- macd background levels arrays for the timeframes
   ArrayInitialize(min1level,0);
   min1level[0] = low1MinLevel*3;
   min1level[1] = low1MinLevel*2;
   min1level[2] = low1MinLevel;
   min1level[3] = -low1MinLevel;
   min1level[4] = -low1MinLevel*2;
   min1level[5] = -low1MinLevel*3;
   
   ArrayInitialize(min5level,0);
   min5level[0] = low5MinLevel*3;
   min5level[1] = low5MinLevel*2;
   min5level[2] = low5MinLevel;
   min5level[3] = -low5MinLevel;
   min5level[4] = -low5MinLevel*2;
   min5level[5] = -low5MinLevel*3;  
   
   ArrayInitialize(min15level,0);
   min15level[0] = low15MinLevel*3;
   min15level[1] = low15MinLevel*2;
   min15level[2] = low15MinLevel;
   min15level[3] = -low15MinLevel;
   min15level[4] = -low15MinLevel*2;
   min15level[5] = -low15MinLevel*3;
  
   ArrayInitialize(min30level,0);
   min30level[0] = low30MinLevel*3;
   min30level[1] = low30MinLevel*2;
   min30level[2] = low30MinLevel;
   min30level[3] = -low30MinLevel;
   min30level[4] = -low30MinLevel*2;
   min30level[5] = -low30MinLevel*3;

   ArrayInitialize(hour1level,0);
   hour1level[0] = lowHour1Level*3;
   hour1level[1] = lowHour1Level*2;
   hour1level[2] = lowHour1Level;
   hour1level[3] = -lowHour1Level;
   hour1level[4] = -lowHour1Level*2;
   hour1level[5] = -lowHour1Level*3;
   
   ArrayInitialize(hour4level,0);
   hour4level[0] = lowHour4Level*3;
   hour4level[1] = lowHour4Level*2;
   hour4level[2] = lowHour4Level;
   hour4level[3] = -lowHour4Level;
   hour4level[4] = -lowHour4Level*2;
   hour4level[5] = -lowHour4Level*3;
  
   ArrayInitialize(daylevel,0);
   daylevel[0] = lowDayLevel*3;
   daylevel[1] = lowDayLevel*2;
   daylevel[2] = lowDayLevel;
   daylevel[3] = -lowDayLevel;
   daylevel[4] = -lowDayLevel*2;
   daylevel[5] = -lowDayLevel*3;

//---- draw macd levels
   switch(Period())
	{
		case PERIOD_M1:
		   for(int x=0;x<ArraySize(min1level);x++)
			   SetLevelValue(x,min1level[x]); 
			break;
		case PERIOD_M5:
	      for(x=0;x<ArraySize(min5level);x++)
			   SetLevelValue(x,min5level[x]);	
			break;
		case PERIOD_M15:
		   for(x=0;x<ArraySize(min15level);x++)
			   SetLevelValue(x,min15level[x]);
			break;
		case PERIOD_M30:
			for(x=0;x<ArraySize(min30level);x++)
			   SetLevelValue(x,min30level[x]);
			break;
		case PERIOD_H1:
		   for(x=0;x<ArraySize(hour1level);x++)
			   SetLevelValue(x,hour1level[x]);
			break;
		case PERIOD_H4:
			for(x=0;x<ArraySize(hour4level);x++)
			   SetLevelValue(x,hour4level[x]);
			break;
		default:
			for(x=0;x<ArraySize(daylevel);x++)
			   SetLevelValue(x,daylevel[x]);
	}
   SetIndexDrawBegin(1,SlowEMA);
   IndicatorDigits(1); 
 
 //---- indicator buffers mapping
   SetIndexBuffer(0,MacdBufferUp);
   SetIndexBuffer(1,MacdBufferDn);
   SetIndexBuffer(2,SignalBuffer);
   SetIndexBuffer(3,BorderLine);
   SetIndexBuffer(4,ZeroLine);

//---- name for DataWindow and indicator subwindow label
	shortname=WindowExpertName();
   shortname=shortname+" ("+FastEMA+","+SlowEMA+","+SignalSMA+")";
   IndicatorShortName(shortname);
   SetIndexLabel(0,"MACD Up");
   SetIndexLabel(1,"MACD Down");
   SetIndexLabel(2,"Signal");
   SetIndexLabel(3,"Border");
   ArrayResize(MacdBuffer,Bars-SlowEMA);
   ArraySetAsSeries(MacdBuffer,true); 

//---- check input parms
	ValidateAlertType();

//---- initialization done
	alertbartime=0;
	nextalerttime=0;
	alertcount=0;
	nextbartime=0;
   return(0);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int deinit()
{
	string objname=shortname+","+Symbol()+","+Period();
	int i;
	while(i<ObjectsTotal())
	{
		string nextobj=ObjectName(i);
		if(StringSubstr(nextobj,0,StringLen(objname))==objname)
			ObjectDelete(nextobj);
		else
			i++;
	}
}

//+------------------------------------------------------------------+
//| Moving Averages Convergence/Divergence                           |
//+------------------------------------------------------------------+
int start()
  {
   int limit;
   int counted_bars=IndicatorCounted();

//---- last counted bar will be recounted
	if(Time[0]!=nextbartime)
	{
		limit=Bars-SlowEMA;
	   ArrayResize(MacdBuffer,limit);
		nextbartime=Time[0];
	}
	else
   	limit=MathMin(Bars-SlowEMA,Bars-counted_bars);

//---- macd counted in the 1-st buffer
   for(int i=0;i<limit;i++) {
      MacdBuffer[i]=(iMA(NULL,0,FastEMA,0,MODE_EMA,PRICE_CLOSE,i)-
      		iMA(NULL,0,SlowEMA,0,MODE_EMA,PRICE_CLOSE,i))/Point;
   }
// macd colored set here
	bool firstsignal=true;
   for(i=0;i<limit;i++)
   {
  		if(MacdBuffer[i]>MacdBuffer[i+1])
   	{
  			MacdBufferUp[i]=MacdBuffer[i];
  			MacdBufferDn[i]=0;
   	}
  		else
  		{
  			MacdBufferDn[i]=MacdBuffer[i];
   		MacdBufferUp[i]=0;
   	}
   	if(ShowSignal||firstsignal)
   	{
   		if(!ShowTops(i))
   		{
   			if(ShowBottoms(i))
   				firstsignal=false;
   		}
   		else
  				firstsignal=false;
   	}
   }

//---- signal line counted in the 2-nd buffer
   for(i=0; i<limit; i++) {
      SignalBuffer[i]=iMAOnArray(MacdBuffer,Bars,SignalSMA,0,MODE_SMA,i);
      BorderLine[i]=MacdBuffer[i];
      ZeroLine[i]=0;
   }

//---- pips to change color calculation
	double priMACD=(iMA(NULL,0,FastEMA,0,MODE_EMA,PRICE_CLOSE,1)-
      		iMA(NULL,0,SlowEMA,0,MODE_EMA,PRICE_CLOSE,1))/Point;
   double close[];
   ArrayResize(close,Bars);
   ArraySetAsSeries(close,true);
   ArrayCopy(close,Close,0,0,ArraySize(close));
	double curMACD=(iMAOnArray(close,0,FastEMA,0,MODE_EMA,0)-
      		iMAOnArray(close,0,SlowEMA,0,MODE_EMA,0))/Point;
	int pips;
	if(curMACD<priMACD)
	{
		while(curMACD<priMACD)
		{
			pips++;
			close[0]+=Point;
			curMACD=(iMAOnArray(close,0,FastEMA,0,MODE_EMA,0)-
      		iMAOnArray(close,0,SlowEMA,0,MODE_EMA,0))/Point;
		}
	}
	else
	{
		while(curMACD>priMACD)
		{
			pips--;
			close[0]-=Point;
			curMACD=(iMAOnArray(close,0,FastEMA,0,MODE_EMA,0)-
      		iMAOnArray(close,0,SlowEMA,0,MODE_EMA,0))/Point;
		}
	}
	string objname=shortname+","+Symbol()+","+Period()+",pips";
	if(ObjectFind(objname)<0)
	   ObjectCreate(objname,OBJ_TEXT,
		      WindowFind(shortname),
		      Time[0]+Period()*60,MacdBuffer[0]/2);
	else
		ObjectMove(objname,0,Time[0]+Period()*60,MacdBuffer[0]/2);
		
	if(pips!=0)
		ObjectSetText(objname,DoubleToStr(pips,0),FontSize,"Courier",FontColor);
	else
		ObjectSetText(objname," ",FontSize,"Courier",FontColor);

//---- send alerts
	if(Max_Alerts==0)
		return(0);
	string alertmsg;
	if(!IsAlert(alertmsg))
		return(0);
	alertmsg=Symbol()+","+Period()+" : "+alertmsg;
	Alert(alertmsg);
	if(EMail_Alert)
		SendMail("MACD Colored Alert",TimeToStr(TimeLocal(),TIME_DATE|TIME_SECONDS)+" : "+alertmsg);
	Print(alertmsg);

//---- done
   return(0);
}

//+------------------------------------------------------------------+
//| macd shape recognition sub-routines                              |
//+------------------------------------------------------------------+

//---- shapes above macd zero line
bool ShowTops(int shift)
{

// check for basic pattern
	string objname=SetPatternObjectName(shift);
	bool basicpattern=(MacdBuffer[shift]<MacdBuffer[shift+1]&&
		MacdBuffer[shift+2]<MacdBuffer[shift+1]&&
		MacdBuffer[shift+3]<MacdBuffer[shift+2]);
	if(!basicpattern)
	{
		ObjectDelete(objname);
		return(false);
	}
	double diff2=MathAbs(MacdBuffer[shift+2]-MacdBuffer[shift+3]);
	double diff1=MathAbs(MacdBuffer[shift+1]-MacdBuffer[shift+2]);
	double diff0=MathAbs(MacdBuffer[shift]-MacdBuffer[shift+1]);
	bool roundpattern=(diff2>diff1);
	if(MacdBuffer[shift+2]!=0)
		double ratio2=MathAbs(MacdBuffer[shift+3]/MacdBuffer[shift+2]);
	else
		ratio2=1000;
	if(MacdBuffer[shift+1]!=0)
		double ratio1=MathAbs(MacdBuffer[shift+2]/MacdBuffer[shift+1]);
	else
		ratio1=1000;
	if(MacdBuffer[shift+1]!=0)
		double ratio0=MathAbs(MacdBuffer[shift]/MacdBuffer[shift+1]);
	else
		ratio0=1000;
	roundpattern=(roundpattern||MathAbs(ratio0-ratio1)>0.1); // 0 and 2 are close to each other
	double minratio=0.8;
	if(MacdBuffer[shift+1]<10&&MacdBuffer[shift+1]>-10)
		minratio=0.6;
	bool ratioround=(ratio0>minratio&&ratio1>minratio&&ratio2>minratio);
	bool ratiovtop=(MathAbs(ratio0-ratio1)<0.3);
	string patname=" ";
	if(ratiovtop)
		patname="VT"; // default is v-top
	if(ratioround&&roundpattern)
		if(MacdBuffer[shift+1]<5)
			return(false);
		else
			patname="RT"; // round top pattern
	if(patname==" ")
		return(false);
	if(MacdBuffer[shift+1]<3&&MacdBuffer[shift+1]>-3)
		patname="ZB"; // zero line bounce
	if(MacdBuffer[shift+1]<=-3)
		patname="TC"; // trend continue
	bool strongpattern=(MacdBuffer[shift+4]<MacdBuffer[shift+3]&&
			MacdBuffer[shift+5]<MacdBuffer[shift+4]&&
			MacdBuffer[shift+1]>10);
	if(ObjectFind(objname)<0)
	{
		ObjectCreate(objname,OBJ_TEXT,	
				WindowFind(shortname),
				Time[shift+1],0);
	}
	if(strongpattern)
		ObjectSetText(objname,patname,FontSize+2,"Arial",FontColor);
	else
		ObjectSetText(objname,patname,FontSize,"Arial",FontColor);
	return(true);
}

//---- shapes below macd zero line
bool ShowBottoms(int shift)
{

// check for basic pattern
	string objname=SetPatternObjectName(shift);
	string objdesc=ObjectDescription(objname);
	bool basicpattern=(MacdBuffer[shift]>MacdBuffer[shift+1]&&
		MacdBuffer[shift+2]>MacdBuffer[shift+1]&&
		MacdBuffer[shift+3]>MacdBuffer[shift+2]);
	if(!basicpattern)
	{
		ObjectDelete(objname);
		return(false);
	}
	double diff2=MathAbs(MacdBuffer[shift+2]-MacdBuffer[shift+3]);
	double diff1=MathAbs(MacdBuffer[shift+1]-MacdBuffer[shift+2]);
	double diff0=MathAbs(MacdBuffer[shift]-MacdBuffer[shift+1]);
	bool roundpattern=(diff2>diff1);//&&diff2>diff0);
	if(MacdBuffer[shift+3]!=0)
		double ratio2=MathAbs(MacdBuffer[shift+2]/MacdBuffer[shift+3]);
	else
		ratio2=1000;
	if(MacdBuffer[shift+2]!=0)
	double ratio1=MathAbs(MacdBuffer[shift+1]/MacdBuffer[shift+2]);
	else
		ratio1=1000;
	if(MacdBuffer[shift]!=0)
		double ratio0=MathAbs(MacdBuffer[shift+1]/MacdBuffer[shift]);
	else
		ratio0=1000;
	roundpattern=(roundpattern||MathAbs(ratio0-ratio1)>0.1); // 0 and 2 are close to each other
	double minratio=0.8;
	if(MacdBuffer[shift+1]<10&&MacdBuffer[shift+1]>-10)
		minratio=0.6;
	bool ratioround=(ratio0>minratio&&ratio1>minratio&&ratio2>minratio);
	bool ratiovtop=(MathAbs(ratio0-ratio1)<0.3);
	string patname=" ";
	if(ratiovtop)
		patname="VB"; // default is v-top
	if(ratioround&&roundpattern)
		if(MacdBuffer[shift+1]>-5)
			return(false);
		else
			patname="RB"; // round top pattern
	if(patname==" ")
		return(false);
	if(MacdBuffer[shift+1]<3&&MacdBuffer[shift+1]>-3)
		patname="ZB"; // zero line bounce
	if(MacdBuffer[shift+1]>=3)
		patname="TC"; // trend continue
	bool strongpattern=(MacdBuffer[shift+4]>MacdBuffer[shift+3]&&
			MacdBuffer[shift+5]>MacdBuffer[shift+4]&&
			MacdBuffer[shift+1]>10);
	if(ObjectFind(objname)<0)
		ObjectCreate(objname,OBJ_TEXT,	
				WindowFind(shortname),
				Time[shift+1],0);
	if(strongpattern)
		ObjectSetText(objname,patname,FontSize+2,"Arial",FontColor);
	else
		ObjectSetText(objname,patname,FontSize,"Arial",FontColor);
	return(true);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

bool IsAlert(string& alertmsg)
{
	if(ArraySize(alerttype)==0)
		return(false);
	if(alerttype[0]=="")
		return(false);
	int shift;
	if(TimeCurrent()<Time[0]+(Period()-Alert_Before_Minutes)*60)
		shift=1;
	string objname=SetPatternObjectName(shift);
	if(ObjectFind(objname)<0)
		return(false);
	string thisalert=StringTrimLeft(StringTrimRight(ObjectDescription(objname)));
	bool needalert=false;
	if(alerttype[0]=="ANY")
		needalert=(thisalert!="");
	else
	{
		for(int i=0;i<ArraySize(alerttype);i++)
		{
			if(alerttype[i]==thisalert)
			{
				needalert=true;
				break;
			}
		}
	}
	if(alertbartime!=Time[shift])
	{
		nextalerttime=0;
		alertcount=0;
	}
	if(!needalert)
		return(false);
	alertbartime=Time[shift];
	if(TimeCurrent()>nextalerttime)
	{
		if(alertcount<Max_Alerts)
		{
			alertcount++;
			nextalerttime=TimeCurrent()+Alert_Every_Minutes*60;
			int timetoalert=(TimeCurrent()-Time[shift]-Period()*60)/60;
			string alertname=SetAlertName(thisalert);
			if(timetoalert<0)
				alertmsg=(-1*timetoalert)+" minutes till "+alertname;
			else
				if(timetoalert>0)
					alertmsg=timetoalert+" minutes since "+alertname;
				else
					alertmsg=alertname;
			if(alertcount<Max_Alerts)
				alertmsg=alertmsg+". Next Alert at "+TimeToStr(
							nextalerttime+TimeLocal()-TimeCurrent(),TIME_SECONDS);
			else
				alertmsg=alertmsg+". This was the last Alert";
			return(true);
		}
	}
	return(false);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

string SetAlertName(string alertabbr)
{
	if(alertabbr=="RT")
		return("Round Top");
	if(alertabbr=="VT")
		return("V-Top");
	if(alertabbr=="RB")
		return("Round Bottom");
	if(alertabbr=="VB")
		return("V-Bottom");
	if(alertabbr=="TC")
		return("Trend Continue");
	if(alertabbr=="ZB")
		return("Zero Bounce");
	return("");
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

string SetPatternObjectName(int shift)
{
	return(shortname+","+Symbol()+","+Period()+","+Time[shift]);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

void ValidateAlertType()
{
	StringUpperCase(Alert_On);	
	StringToArray(StringTrimLeft(StringTrimRight(Alert_On)), alerttype,",");
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

void StringUpperCase(string& input)
{
	for(int i=0;i<StringLen(input);i++)
	{
		int char=StringGetChar(input,i);
		if(char>=97&&char<=122)
		input=StringSetChar(input,i,char-32);
	}
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

void StringToArray(string input, string& output[],string delim)
{
	ArrayResize(output,0);
	int start=0;
	while(start<StringLen(input))
	{
		int delpos=StringFind(input,delim,start);
		if(delpos<0)
		{
			string nextelem=StringSubstr(input,start);
			start=StringLen(input);
		}
		else
		{
			nextelem=StringSubstr(input,start,delpos-start);
			start=delpos+1;
		}
		ArrayResize(output,ArraySize(output)+1);
		output[ArraySize(output)-1]=nextelem;
	}
}

//+------------------------- End of Program --------------------------+