//+------------------------------------------------------------------+
//|                                donchian-channels-alert.mq4         |
//|  Plots Donchian Channels (upper band = highest high, lower band = |
//|  lowest low of the prior N bars) and alerts when price closes     |
//|  outside either band.                                             |
//|  Source: web-forex (educational, free to use and modify)          |
//+------------------------------------------------------------------+
#property copyright "web-forex"
#property strict
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_color1 clrDodgerBlue
#property indicator_color2 clrOrange
#property indicator_color3 clrOrange

extern int    DonchianPeriod = 20;
extern bool   EnableAlert    = true;
extern bool   EnablePush     = false;

double MiddleBuffer[];
double UpperBuffer[];
double LowerBuffer[];

int lastBreak = 0; // 0 = none, 1 = closed above upper, -1 = closed below lower

int OnInit()
{
   SetIndexBuffer(0, MiddleBuffer);
   SetIndexStyle(0, DRAW_LINE);
   SetIndexLabel(0, "Donchian Middle");

   SetIndexBuffer(1, UpperBuffer);
   SetIndexStyle(1, DRAW_LINE);
   SetIndexLabel(1, "Donchian Upper");

   SetIndexBuffer(2, LowerBuffer);
   SetIndexStyle(2, DRAW_LINE);
   SetIndexLabel(2, "Donchian Lower");

   IndicatorShortName("Donchian Channels Alert (" + IntegerToString(DonchianPeriod) + ")");
   return(INIT_SUCCEEDED);
}

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 start = rates_total - prev_calculated;
   if (start > rates_total - DonchianPeriod - 1)
      start = rates_total - DonchianPeriod - 1;
   if (start < 0)
      return(rates_total);

   for (int i = start; i >= 0; i--)
      CalculateBand(i);

   CheckBreakSignal();
   return(rates_total);
}

void CalculateBand(int i)
{
   // band uses the DonchianPeriod bars BEFORE bar i (i+1 .. i+DonchianPeriod),
   // so a break at bar i is a genuine move beyond the prior range, not a
   // self-referential one.
   int highestIdx = iHighest(NULL, 0, MODE_HIGH, DonchianPeriod, i + 1);
   int lowestIdx  = iLowest(NULL, 0, MODE_LOW, DonchianPeriod, i + 1);

   double upper = High[highestIdx];
   double lower = Low[lowestIdx];

   UpperBuffer[i]  = upper;
   LowerBuffer[i]  = lower;
   MiddleBuffer[i] = (upper + lower) / 2.0;
}

void CheckBreakSignal()
{
   if (!EnableAlert && !EnablePush)
      return;

   double closeLast = Close[1];
   double upper = UpperBuffer[1];
   double lower = LowerBuffer[1];

   int brk = 0;
   if (closeLast > upper)
      brk = 1;
   else if (closeLast < lower)
      brk = -1;

   if (brk != 0 && brk != lastBreak)
   {
      string msg = (brk == 1)
         ? Symbol() + " " + EnumToString((ENUM_TIMEFRAMES)Period()) + ": price closed above the upper Donchian Channel band"
         : Symbol() + " " + EnumToString((ENUM_TIMEFRAMES)Period()) + ": price closed below the lower Donchian Channel band";

      if (EnableAlert)
         Alert(msg);
      if (EnablePush)
         SendNotification(msg);
   }

   if (brk != 0)
      lastBreak = brk;
   else
      lastBreak = 0;
}
