//+------------------------------------------------------------------+
//|                                                          BB MACD |
//|                                 Copyright © 2009-2018, EarnForex |
//|                                       https://www.earnforex.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2009-2018, EarnForex"
#property link      "https://www.earnforex.com/metatrader-indicators/BB-MACD/"
#property version   "1.02"

#property description "An advanced version of MACD indicator for trend change detection."

#property indicator_separate_window
#property indicator_buffers 8
#property indicator_plots   3
#property indicator_color1  clrLime, clrMagenta    // Up/down bullets
#property indicator_type1   DRAW_COLOR_ARROW
#property indicator_style1  STYLE_SOLID
#property indicator_width1  0
#property indicator_label1  "bbMACD"
#property indicator_color2  clrBlue    // Upper band
#property indicator_type2   DRAW_LINE
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
#property indicator_label2  "Upper band"
#property indicator_color3  clrRed     // Lower band
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
#property indicator_label3  "Lower band"

// Indicator parameters:
input int FastLen = 12;
input int SlowLen = 26;
input int Length = 10;
input int barsCount = 400;
input double StDv = 2.5;
input bool EnableNativeAlerts = false;
input bool EnableSoundAlerts = false;
input bool EnableEmailAlerts = false;
input bool EnablePushAlerts = false;
input string SoundFileName	= "alert.wav";

// Indicator data and color buffers:
double ExtMapBuffer1[]; // bbMACD
double ExtMapBuffer2[]; // bbMACD color
double ExtMapBuffer3[]; // Upper band
double ExtMapBuffer4[]; // Lower band
double ExtMapBuffer5[]; // Data for "iMAOnArray()"

// Indicator calculation buffers:
double MABuff1[];
double MABuff2[];
double bbMACD[];

// Global variables:
int Oldest_bbMACD; // To avoid calculating EMA using bbMACD older than this.
datetime LastAlertTime;

#include <MovingAverages.mqh>
#include <AZ-INVEST/CustomBarConfig.mqh>

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit()
{
 	IndicatorSetString(INDICATOR_SHORTNAME, "BB MACD(" + IntegerToString(FastLen) + "," + IntegerToString(SlowLen) + "," + IntegerToString(Length) + ")");
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits + 1);

//---- indicator buffers mapping
   SetIndexBuffer(0, ExtMapBuffer1, INDICATOR_CALCULATIONS);//INDICATOR_DATA);
   SetIndexBuffer(1, ExtMapBuffer2, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, ExtMapBuffer3, INDICATOR_DATA);
   SetIndexBuffer(3, ExtMapBuffer4, INDICATOR_DATA);
   SetIndexBuffer(4, ExtMapBuffer5, INDICATOR_CALCULATIONS);
   SetIndexBuffer(5, MABuff1, INDICATOR_CALCULATIONS);
   SetIndexBuffer(6, MABuff2, INDICATOR_CALCULATIONS);
   SetIndexBuffer(7, bbMACD, INDICATOR_CALCULATIONS);

   // Set the correct order: 0 is the latest, N - is the oldest.
   ArraySetAsSeries(ExtMapBuffer1, true);
   ArraySetAsSeries(ExtMapBuffer2, true);
   ArraySetAsSeries(ExtMapBuffer3, true);
   ArraySetAsSeries(ExtMapBuffer4, true);
   ArraySetAsSeries(ExtMapBuffer5, true);
   ArraySetAsSeries(MABuff1, true);
   ArraySetAsSeries(MABuff2, true);
   ArraySetAsSeries(bbMACD, true);
   
   PlotIndexSetInteger(0, PLOT_ARROW, 108);
   
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   
   // For barsCount > 0, PLOT_DRAW_BEGIN is calculated in OnCalculate().
   if (barsCount == 0)
   {
      PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, Length);
      PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, Length);
      PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, Length);
   }
   
   Oldest_bbMACD = 0;
   LastAlertTime = 0;
   
   customChartIndicator.SetGetTimeFlag();
}

//+------------------------------------------------------------------+
//| Custom BB_MACD                                                   |
//+------------------------------------------------------------------+
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 limit;

   if (rates_total < Length)
   {
      Print("Not enough bars!");
      return(-1);
   }
   
   if(!customChartIndicator.OnCalculate(rates_total,prev_calculated,Time,close))
      return(0);
      
   if(!customChartIndicator.BufferSynchronizationCheck(close))
      return(0);

   int _prev_calculated = customChartIndicator.GetPrevCalculated();    
   
   int counted_bars = _prev_calculated;
   if (counted_bars < 0) return(-1);
   if (counted_bars > 0) counted_bars--;
   
   ArraySetAsSeries(customChartIndicator.Time, true);
   
   if (barsCount > 0) limit = MathMin(rates_total - counted_bars, barsCount);
   else limit = rates_total - counted_bars;

   // Adjust starting point in time for indicator output.
   if (barsCount > 0)
   {
      int draw_begin = rates_total - barsCount + Length;
      if (draw_begin < Length) draw_begin = Length;
      PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, draw_begin);
      PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, draw_begin);
      PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, draw_begin);
   }

/*
   int myMA = iMA(NULL, 0, FastLen, 0, MODE_EMA, PRICE_CLOSE);
   if (CopyBuffer(myMA, 0, 0, rates_total, MABuff1) != rates_total) return(0);
   myMA = iMA(NULL, 0, SlowLen, 0, MODE_EMA, PRICE_CLOSE);
   if (CopyBuffer(myMA, 0, 0, rates_total, MABuff2) != rates_total) return(0);
*/
/*
#ifdef IS_DEV_CODE
   int myMA = iCustom(Symbol(),_Period,"TickChart\\Indicators\\TickChart_MA",FastLen, 0, MODE_EMA, PRICE_CLOSE, true);
#else   
   int myMA = iCustom(Symbol(),_Period,"Indicators\\TickChart_MA",FastLen, 0, MODE_EMA, PRICE_CLOSE, true);
#endif

   if (CopyBuffer(myMA, 0, 0, rates_total, MABuff1) != rates_total) return(0);

*/
   ExponentialMAOnBuffer(rates_total,_prev_calculated,0, FastLen, customChartIndicator.Close, MABuff1);

/*
#ifdef IS_DEV_CODE
   myMA = iCustom(Symbol(),_Period,"TickChart\\Indicators\\TickChart_MA",SlowLen, 0, MODE_EMA, PRICE_CLOSE, true);
#else   
   myMA = iCustom(Symbol(),_Period,"Indicators\\TickChart_MA",SlowLen, 0, MODE_EMA, PRICE_CLOSE, true);
#endif

   if (CopyBuffer(myMA, 0, 0, rates_total, MABuff2) != rates_total) return(0);
*/
   ExponentialMAOnBuffer(rates_total,_prev_calculated,0, SlowLen, customChartIndicator.Close, MABuff2);

   // MA buffers can hold barsCount or even rates_total valid values.
   for (int i = 0; i < limit; i++)
       bbMACD[i] = MABuff1[i] - MABuff2[i];

   if (limit - 1 > Oldest_bbMACD) Oldest_bbMACD = limit - 1;

   // EMA can also be safely calculated on barsCount or even rates_total bars.
   CalculateEMA(limit, Length, bbMACD);
   
   // StdDev will be calculated using Length as a period on the previously calculated EMA data. Avoiding 'array out of range' errors.
   if (barsCount > 0)
      if (limit > barsCount - Length) limit = barsCount - Length;
   if (limit > rates_total - Length) limit = rates_total - Length;

   for (int i = 0; i < limit; i++)
   {
      double avg = ExtMapBuffer5[i]; // MA on Array
		double sDev = StdDevFunc(i, Length, bbMACD); // StdDev on Array
      
      ExtMapBuffer1[i] = bbMACD[i];                             // bbMACD
      if (bbMACD[i] > bbMACD[i + 1]) ExtMapBuffer2[i] = 0;      // Uptrend
      else if (bbMACD[i] < bbMACD[i + 1]) ExtMapBuffer2[i] = 1; // Downtrend
      
      ExtMapBuffer3[i] = avg + (StDv * sDev); // Upper band
      ExtMapBuffer4[i] = avg - (StDv * sDev); // Lower band

      // The last check is needed to make sure the previous value has been calculated already because the values are filled from left to right.
      if ((i == 1) && (LastAlertTime != customChartIndicator.Time[1]) && (ExtMapBuffer1[i + 1] == bbMACD[i + 1]))
      {
         if ((ExtMapBuffer2[i] == 0) && (ExtMapBuffer2[i + 1] == 1))
         {
      		string Text = Symbol() + " - " + EnumToString((ENUM_TIMEFRAMES)Period()) + " - BB_MACD: from DOWN to UP @ " + TimeToString(customChartIndicator.Time[i]) + ".";
      		if (EnableNativeAlerts) Alert(Text);
      		if (EnableEmailAlerts) SendMail(Text, Text);
      		if (EnableSoundAlerts) PlaySound(SoundFileName);
      		if (EnablePushAlerts) SendNotification(Text);
      		LastAlertTime = customChartIndicator.Time[i];
         }
         else if ((ExtMapBuffer2[i] == 1) && (ExtMapBuffer2[i + 1] == 0))
         {
      		string Text = Symbol() + " - " + EnumToString((ENUM_TIMEFRAMES)Period()) + " - BB_MACD: from UP to DOWN @ " + TimeToString(customChartIndicator.Time[i]) + ".";
      		if (EnableNativeAlerts) Alert(Text);
      		if (EnableEmailAlerts) SendMail(Text, Text);
      		if (EnableSoundAlerts) PlaySound(SoundFileName);
      		if (EnablePushAlerts) SendNotification(Text);
      		LastAlertTime = customChartIndicator.Time[i];
         }
      }
   }
   return(rates_total);
}

//+------------------------------------------------------------------+
//|  Exponential Moving Average                                      |
//|  Fills the buffer array with EMA values.									|
//+------------------------------------------------------------------+
void CalculateEMA(int begin, int period, const double &price[])
{
   double SmoothFactor = 2.0 / (1.0 + period);
	int start;
	
   // First time.
   if (begin == Oldest_bbMACD + 1)
   {
   	ExtMapBuffer5[Oldest_bbMACD] = price[Oldest_bbMACD];
   	start = Oldest_bbMACD - 1;
   }
   else start = begin;

   for (int i = start; i >= 0; i--) ExtMapBuffer5[i] = price[i] * SmoothFactor + ExtMapBuffer5[i + 1] * (1.0 - SmoothFactor);
}

//+------------------------------------------------------------------+
//| Calculate Standard Deviation                                     |
//| Returns StdDev for the given position (bar).                     |
//+------------------------------------------------------------------+
double StdDevFunc(int position, int period, const double &price[])
{
   double dTmp = 0.0;
   for (int i = 0; i < period; i++)	dTmp += MathPow(price[position + i] - ExtMapBuffer5[position], 2);
   dTmp = MathSqrt(dTmp / period);

   return(dTmp);
}
//+------------------------------------------------------------------+
