//+------------------------------------------------------------------+
//| FXP_DonchianChannelBreakout.mq5                                  |
//| FXPIPS ONLINE - https://fxpips.online/mt5-indicators/donchian-channel-breakout/
//|                                                                  |
//| License: MIT. Copyright (c) 2026 FXPIPS ONLINE.                  |
//| Permission is granted to use, copy, modify and distribute this   |
//| software provided this notice is kept. The software is provided  |
//| "as is", without warranty of any kind.                           |
//|                                                                  |
//| Educational tool - not financial advice. Trading involves        |
//| substantial risk of loss. Test on a demo account first.          |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/donchian-channel-breakout/"
#property version   "1.00"
#property description "Donchian Channel (highest high / lowest low) with midline."
#property description "Arrows mark closed bars that close beyond the prior channel."

#property indicator_chart_window
#property indicator_buffers 5
#property indicator_plots   5

#property indicator_label1  "Upper channel"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  1

#property indicator_label2  "Midline"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_style2  STYLE_DOT
#property indicator_width2  1

#property indicator_label3  "Lower channel"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrDodgerBlue
#property indicator_width3  1

#property indicator_label4  "Breakout up"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrLimeGreen
#property indicator_width4  2

#property indicator_label5  "Breakout down"
#property indicator_type5   DRAW_ARROW
#property indicator_color5  clrRed
#property indicator_width5  2

input group "Channel"
input int  InpPeriod  = 20;    // Channel period (bars)
input bool InpUseWicks = false; // Breakout on wick (high/low) instead of close
input group "Alerts"
input bool InpAlerts  = false; // Popup alert on a new breakout
input bool InpPush    = false; // Push notification on a new breakout

double gUpper[], gMid[], gLower[], gBuy[], gSell[];
int      gPeriod = 20;
datetime gLastAlertBar = 0;

string TfName() { return StringSubstr(EnumToString(Period()), 7); }

void Notify(const string text, const datetime barTime)
{
   if(!InpAlerts && !InpPush) return;
   if(barTime == gLastAlertBar) return;
   gLastAlertBar = barTime;
   string msg = "FXP Donchian " + _Symbol + " " + TfName() + ": " + text;
   if(InpAlerts) Alert(msg);
   if(InpPush)   SendNotification(msg);
}

int OnInit()
{
   gPeriod = MathMax(2, InpPeriod);
   SetIndexBuffer(0, gUpper, INDICATOR_DATA);
   SetIndexBuffer(1, gMid,   INDICATOR_DATA);
   SetIndexBuffer(2, gLower, INDICATOR_DATA);
   SetIndexBuffer(3, gBuy,   INDICATOR_DATA);
   SetIndexBuffer(4, gSell,  INDICATOR_DATA);
   ArraySetAsSeries(gUpper, false); ArraySetAsSeries(gMid, false); ArraySetAsSeries(gLower, false);
   ArraySetAsSeries(gBuy, false);   ArraySetAsSeries(gSell, false);

   PlotIndexSetInteger(3, PLOT_ARROW, 233);
   PlotIndexSetInteger(4, PLOT_ARROW, 234);
   PlotIndexSetInteger(3, PLOT_ARROW_SHIFT, 15);
   PlotIndexSetInteger(4, PLOT_ARROW_SHIFT, -15);
   for(int p = 0; p < 5; p++)
   {
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
      PlotIndexSetInteger(p, PLOT_DRAW_BEGIN, gPeriod - 1);
   }
   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("FXP Donchian (%d)", gPeriod));
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // No indicator handles or chart objects are created by this indicator.
}

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[])
{
   if(rates_total < gPeriod + 3) return(0);
   ArraySetAsSeries(time, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(close, false);

   int start;
   if(prev_calculated <= 0 || prev_calculated > rates_total)
   {
      ArrayInitialize(gUpper, EMPTY_VALUE);
      ArrayInitialize(gMid, EMPTY_VALUE);
      ArrayInitialize(gLower, EMPTY_VALUE);
      ArrayInitialize(gBuy, EMPTY_VALUE);
      ArrayInitialize(gSell, EMPTY_VALUE);
      start = gPeriod - 1;
      gLastAlertBar = time[rates_total - 1];
   }
   else
      start = prev_calculated - 1;

   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      double hh = high[i], ll = low[i];
      for(int k = 1; k < gPeriod; k++)
      {
         if(high[i - k] > hh) hh = high[i - k];
         if(low[i - k]  < ll) ll = low[i - k];
      }
      gUpper[i] = hh;
      gLower[i] = ll;
      gMid[i]   = (hh + ll) / 2.0;

      gBuy[i]  = EMPTY_VALUE;
      gSell[i] = EMPTY_VALUE;
      // Compare with the channel of the previous bar (the current bar always sits inside its own channel)
      if(i < rates_total - 1 && i >= gPeriod + 1)
      {
         double upNow  = InpUseWicks ? high[i]     : close[i];
         double upPrev = InpUseWicks ? high[i - 1] : close[i - 1];
         double dnNow  = InpUseWicks ? low[i]      : close[i];
         double dnPrev = InpUseWicks ? low[i - 1]  : close[i - 1];
         if(upNow > gUpper[i - 1] && upPrev <= gUpper[i - 2]) gBuy[i]  = low[i];
         if(dnNow < gLower[i - 1] && dnPrev >= gLower[i - 2]) gSell[i] = high[i];
      }
   }

   if(prev_calculated > 0)
   {
      int b = rates_total - 2;
      if(gBuy[b]  != EMPTY_VALUE) Notify("breakout above the Donchian channel", time[rates_total - 1]);
      if(gSell[b] != EMPTY_VALUE) Notify("breakout below the Donchian channel", time[rates_total - 1]);
   }
   return(rates_total);
}
//+------------------------------------------------------------------+
