//+------------------------------------------------------------------+
//|                                           FXP_ChandelierExit.mq5 |
//|                                                     FXPIPS ONLINE |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/chandelier-exit/"
#property version   "1.00"
#property description "ATR chandelier trailing stops for long and short trends."
#property description "Arrows mark confirmed direction flips."
/*
   License: MIT. Copyright (c) 2026 FXPIPS ONLINE.
   Permission is hereby granted, free of charge, to any person obtaining a copy of this
   software, to use, copy, modify, merge, publish and distribute it, provided this notice
   is included. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.

   Educational tool - not financial advice. Trading involves substantial risk of loss.
*/
#property indicator_chart_window
#property indicator_buffers 9
#property indicator_plots   4

#property indicator_label1  "Long stop"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrSeaGreen
#property indicator_width1  2

#property indicator_label2  "Short stop"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrCrimson
#property indicator_width2  2

#property indicator_label3  "Flip to long"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrSeaGreen
#property indicator_width3  2

#property indicator_label4  "Flip to short"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrCrimson
#property indicator_width4  2

input group "Chandelier"
input int    InpLookback  = 22;    // Highest-high / lowest-low lookback
input int    InpAtrLen    = 22;    // ATR length
input double InpMult      = 3.0;   // ATR multiplier
input bool   InpUseClose  = true;  // Use closes (not highs/lows) for the extremes
input group "Alerts"
input bool   InpAlerts    = false; // Pop-up alert on new flip
input bool   InpPush      = false; // Push notification on new flip

double g_longLine[], g_shortLine[], g_buy[], g_sell[];
double g_tr[], g_atr[], g_longStop[], g_shortStop[], g_dir[];
int g_look, g_aLen, g_begin;
datetime g_lastAlert = 0;

//+------------------------------------------------------------------+
int OnInit()
{
   g_look  = MathMax(1, InpLookback);
   g_aLen  = MathMax(1, InpAtrLen);
   g_begin = MathMax(g_look, g_aLen);

   SetIndexBuffer(0, g_longLine,  INDICATOR_DATA);
   SetIndexBuffer(1, g_shortLine, INDICATOR_DATA);
   SetIndexBuffer(2, g_buy,       INDICATOR_DATA);
   SetIndexBuffer(3, g_sell,      INDICATOR_DATA);
   SetIndexBuffer(4, g_tr,        INDICATOR_CALCULATIONS);
   SetIndexBuffer(5, g_atr,       INDICATOR_CALCULATIONS);
   SetIndexBuffer(6, g_longStop,  INDICATOR_CALCULATIONS);
   SetIndexBuffer(7, g_shortStop, INDICATOR_CALCULATIONS);
   SetIndexBuffer(8, g_dir,       INDICATOR_CALCULATIONS);

   ArraySetAsSeries(g_longLine, false); ArraySetAsSeries(g_shortLine, false);
   ArraySetAsSeries(g_buy, false);      ArraySetAsSeries(g_sell, false);
   ArraySetAsSeries(g_tr, false);       ArraySetAsSeries(g_atr, false);
   ArraySetAsSeries(g_longStop, false); ArraySetAsSeries(g_shortStop, false);
   ArraySetAsSeries(g_dir, false);

   for(int p = 0; p < 4; p++)
   {
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
      PlotIndexSetInteger(p, PLOT_DRAW_BEGIN, g_begin);
   }
   PlotIndexSetInteger(2, PLOT_ARROW, 233);
   PlotIndexSetInteger(3, PLOT_ARROW, 234);

   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("FXP Chandelier Exit (%d, %d, %.1f)", g_look, g_aLen, InpMult));
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   return(INIT_SUCCEEDED);
}

void Notify(const datetime barTime, const string msg)
{
   if(barTime == g_lastAlert) return;
   g_lastAlert = barTime;
   string text = StringFormat("%s %s: %s", _Symbol, EnumToString((ENUM_TIMEFRAMES)_Period), msg);
   if(InpAlerts) Alert(text);
   if(InpPush)   SendNotification(text);
}

//+------------------------------------------------------------------+
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 <= g_begin + 2) return(0);

   int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;

   for(int i = start; i < rates_total; i++)
   {
      g_buy[i] = EMPTY_VALUE; g_sell[i] = EMPTY_VALUE;

      // Wilder ATR
      g_tr[i] = (i == 0) ? high[i] - low[i]
                         : MathMax(high[i], close[i - 1]) - MathMin(low[i], close[i - 1]);
      if(i < g_aLen - 1) g_atr[i] = 0.0;
      else if(i == g_aLen - 1)
      {
         double s = 0.0;
         for(int k = 0; k < g_aLen; k++) s += g_tr[i - k];
         g_atr[i] = s / g_aLen;
      }
      else g_atr[i] = (g_atr[i - 1] * (g_aLen - 1) + g_tr[i]) / g_aLen;

      if(i < g_begin)
      {
         g_longLine[i] = EMPTY_VALUE; g_shortLine[i] = EMPTY_VALUE;
         g_longStop[i] = 0.0; g_shortStop[i] = 0.0; g_dir[i] = 0.0;
         continue;
      }

      double hh = InpUseClose ? close[i] : high[i];
      double ll = InpUseClose ? close[i] : low[i];
      for(int k = 1; k < g_look; k++)
      {
         hh = MathMax(hh, InpUseClose ? close[i - k] : high[i - k]);
         ll = MathMin(ll, InpUseClose ? close[i - k] : low[i - k]);
      }
      double ls = hh - InpMult * g_atr[i];
      double ss = ll + InpMult * g_atr[i];

      if(i == g_begin)
      {
         g_longStop[i] = ls; g_shortStop[i] = ss; g_dir[i] = 1.0;
      }
      else
      {
         double pls = g_longStop[i - 1], pss = g_shortStop[i - 1];
         // Stops only ratchet in the trade's favour while price respects them
         g_longStop[i]  = (close[i - 1] > pls) ? MathMax(ls, pls) : ls;
         g_shortStop[i] = (close[i - 1] < pss) ? MathMin(ss, pss) : ss;

         double d = g_dir[i - 1];
         if(close[i] > pss)      d = 1.0;
         else if(close[i] < pls) d = -1.0;
         g_dir[i] = d;
      }

      g_longLine[i]  = (g_dir[i] > 0.0) ? g_longStop[i]  : EMPTY_VALUE;
      g_shortLine[i] = (g_dir[i] < 0.0) ? g_shortStop[i] : EMPTY_VALUE;

      if(i > g_begin && i < rates_total - 1 && g_dir[i] != g_dir[i - 1])
      {
         if(g_dir[i] > 0.0) g_buy[i]  = g_longStop[i];
         else               g_sell[i] = g_shortStop[i];
      }
   }

   if(prev_calculated > 0 && (InpAlerts || InpPush))
   {
      int b = rates_total - 2;
      if(g_buy[b] != EMPTY_VALUE)  Notify(time[b], "Chandelier Exit flipped to long");
      if(g_sell[b] != EMPTY_VALUE) Notify(time[b], "Chandelier Exit flipped to short");
   }
   return(rates_total);
}

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