//+------------------------------------------------------------------+
//|                                    Paradox_Fibonacci_SwingZZ_v9  |
//|  v1 — initial release                                            |
//|  v2 — dynamic line colours (MediumSeaGreen / OrangeRed)         |
//|  v3 — removed OBJ_FIBO and OBJ_TREND chart objects (were        |
//|        causing duplicate solid lines); vertical lines clrNONE;  |
//|        buffer dotted lines are now the sole display              |
//|  v4 — back to OBJ_FIBO + OBJ_TREND chart objects, but reused    |
//|        in place (update, not delete/recreate) so they can't     |
//|        duplicate. OBJ_FIBO levels are explicitly capped to      |
//|        0 / 23.6 / 38.2 / 50 / 61.8 / 100 — MT5's default extra  |
//|        161.8 / 261.8 extension levels are removed so 100% is    |
//|        always the outermost label, matching the reference       |
//|        layout instead of showing "261.8" past the swing high.   |
//|        Direction (which swing point is 0% vs 100%) is now       |
//|        auto-detected from the chronological order of the last   |
//|        two SwingZZ points, same as MT5's native Fibo tool.      |
//|  v5 — anchor start point changed from "second-most-recent swing |
//|        dot" to the OLDEST swing dot within the BarsBack window  |
//|        (the first swing leg in loaded history), so the Fibo/    |
//|        Trend span the whole visible swing range instead of just |
//|        the last leg. RAY_RIGHT enabled on the Fibo object so    |
//|        both the dotted level lines and their % / price labels   |
//|        extend all the way to the right edge of the chart.       |
//|  v6 — Paradox_DB_SwingZZ draws its own DodgerBlue OBJ_TREND      |
//|        segments directly on the chart (separate from its data   |
//|        buffer). The Fibo start anchor now scans the chart for   |
//|        those DodgerBlue objects and locks onto the earliest     |
//|        (leftmost) one's starting point, instead of the oldest   |
//|        dot in the raw buffer — this matches what the SwingZZ    |
//|        indicator visually treats as the first swing. Falls back |
//|        to the old buffer-scan method if no DodgerBlue objects   |
//|        are found (e.g. SwingZZ hasn't drawn any yet).           |
//|  v7 — WRONG in v6: anchoring to "first DodgerBlue line's start"  |
//|        and "newest buffer dot" ignored the actual highest peak  |
//|        and lowest trough among the swing structure. Now scans   |
//|        every vertex of every DodgerBlue segment on the chart    |
//|        (all ~5 lines) and anchors the Fibo to whichever vertex  |
//|        is the highest price (peak) and whichever is the lowest  |
//|        price (trough), regardless of which segment they belong  |
//|        to. Chronological order of peak vs trough still decides  |
//|        which is 0% and which is 100% (matches native MT5 Fibo   |
//|        convention). Falls back to the highest/lowest dot across |
//|        the whole raw buffer if no DodgerBlue objects exist yet. |
//|  v8 — WRONG in v7: DodgerBlue vertices only mark swings SwingZZ |
//|        has already CONFIRMED. While price is still pushing to a |
//|        fresh high, that high has no DodgerBlue vertex yet, so   |
//|        the Trend/Fibo line stopped short of the real high on    |
//|        screen instead of settling on it. Fix: the peak/trough   |
//|        are now read directly from real candle High/Low prices   |
//|        (iHighest/iLowest), bounded to the same time range the   |
//|        DodgerBlue swing structure covers. This guarantees the   |
//|        line always lands exactly on the true HH/LL, confirmed   |
//|        by SwingZZ or not.                                        |
//|  v9 — WRONG in v8: iHighest/iLowest/iHigh/iLow/iTime query a     |
//|        SEPARATE internal price-series cache for the symbol/     |
//|        timeframe, which is not always guaranteed to be synced   |
//|        at the exact moment OnCalculate runs (especially right   |
//|        after compiling/reattaching). If any call returned -1,   |
//|        the code silently bailed out and left whatever line was  |
//|        already drawn — which looked like "no change" even       |
//|        though the fix was logically correct. Fixed by scanning  |
//|        the high[]/low[]/time[] arrays OnCalculate itself already|
//|        receives as parameters instead — those are guaranteed    |
//|        ready and in sync with what's actually on screen, so the |
//|        update can never silently skip.                          |
//|                                                                   |
//|  REQUIRES: Paradox_DB_SwingZZ.ex5 compiled and present in        |
//|  your MQL5\Indicators folder.                                    |
//+------------------------------------------------------------------+
#property copyright "Coders' Guru / Paradox mod"
#property link      "http://www.xpworx.com"
#property version   "9.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   0

//--- inputs
input double Fibo_Level_1     = 0.236;        // Fibo Level 1
input double Fibo_Level_2     = 0.382;        // Fibo Level 2
input double Fibo_Level_3     = 0.500;        // Fibo Level 3
input double Fibo_Level_4     = 0.618;        // Fibo Level 4
input bool   Pause            = false;                  // Pause updates
input color  FiboColorHighToLow   = clrMediumSeaGreen;   // Colour when most recent swing is High -> Low
input color  FiboColorLowToHigh   = clrOrangeRed;        // Colour when most recent swing is Low -> High
input string sep1             = "=== DB SwingZZ Settings ==="; //
input string InpSwingZZFile   = "Paradox_DB_SwingZZ"; // SwingZZ indicator file name
input int    InpSwingBarsBack = 1000;         // SwingZZ: BarsBack
input int    InpSwingLookBack = 40;           // SwingZZ: LookBack

//--- fixed endpoints
#define FIBO_LEVEL_COUNT 6   // 0.0, 23.6, 38.2, 50.0, 61.8, 100.0 — nothing beyond 100%

//--- object names (created once, updated in place — never recreated per tick)
#define OBJ_NAME_FIBO  "PFibo_swzz_retracement"
#define OBJ_NAME_TREND "PFibo_swzz_trend"

//--- dummy buffer (required so the compiler accepts indicator_buffers 1;
//    it is never plotted — indicator_plots is 0)
double dummy[];

//--- handle
int _hSwingZZ = INVALID_HANDLE;

//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, dummy, INDICATOR_CALCULATIONS);

   _hSwingZZ = iCustom(_Symbol, _Period, InpSwingZZFile,
                        InpSwingBarsBack, InpSwingLookBack,
                        false, false, false);  // TextAlert, SoundAlert, ShowTargets
   if(_hSwingZZ == INVALID_HANDLE)
   {
      Print("Paradox_Fibonacci_SwingZZ: failed to load '", InpSwingZZFile,
            "'. Compile it and place the .ex5 in Indicators folder. Error: ", GetLastError());
      return INIT_FAILED;
   }

   // one-time cleanup of anything left by earlier versions of this indicator
   ObjectDelete(0, OBJ_NAME_FIBO);
   ObjectDelete(0, OBJ_NAME_TREND);
   ObjectsDeleteAll(0, "fib_swzz_");   // old v3 vertical-line objects, if any

   IndicatorSetString(INDICATOR_SHORTNAME, "Paradox Fibo (SwingZZ)");
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectDelete(0, OBJ_NAME_FIBO);
   ObjectDelete(0, OBJ_NAME_TREND);
   ObjectsDeleteAll(0, "fib_swzz_");
   if(_hSwingZZ != INVALID_HANDLE) IndicatorRelease(_hSwingZZ);
}

//+------------------------------------------------------------------+
//  Create the Fibo/Trend objects the first time, or just move/recolour
//  them on every subsequent call — this is what stops duplicates.
//+------------------------------------------------------------------+
void UpdateFiboObject(datetime t1, double p1, datetime t2, double p2, color clr)
{
   double levels[FIBO_LEVEL_COUNT] = {0.0, Fibo_Level_1, Fibo_Level_2, Fibo_Level_3, Fibo_Level_4, 1.0};

   if(ObjectFind(0, OBJ_NAME_FIBO) < 0)
      ObjectCreate(0, OBJ_NAME_FIBO, OBJ_FIBO, 0, t1, p1, t2, p2);

   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_TIME, 0, t1);
   ObjectSetDouble (0, OBJ_NAME_FIBO, OBJPROP_PRICE, 0, p1);
   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_TIME, 1, t2);
   ObjectSetDouble (0, OBJ_NAME_FIBO, OBJPROP_PRICE, 1, p2);

   // Extend both the dotted level lines and their %/price text to the
   // right edge of the chart (and keep re-extending as new bars form).
   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_RAY_RIGHT, true);
   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_RAY_LEFT,  false);
   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_STYLE, STYLE_DASH);

   // Explicitly cap the level list — this is what removes the 161.8 / 261.8
   // extension levels and makes 100.0 the outermost label instead of 261.8
   ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_LEVELS, FIBO_LEVEL_COUNT);
   for(int i = 0; i < FIBO_LEVEL_COUNT; i++)
   {
      ObjectSetDouble (0, OBJ_NAME_FIBO, OBJPROP_LEVELVALUE, i, levels[i]);
      ObjectSetString (0, OBJ_NAME_FIBO, OBJPROP_LEVELTEXT,  i,
                        DoubleToString(levels[i] * 100.0, 1) + "%%  -  %$");
      ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_LEVELCOLOR, i, clr);
      ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_LEVELSTYLE, i, STYLE_DOT);
      ObjectSetInteger(0, OBJ_NAME_FIBO, OBJPROP_LEVELWIDTH, i, 1);
   }
}

//+------------------------------------------------------------------+
void UpdateTrendObject(datetime t1, double p1, datetime t2, double p2, color clr)
{
   if(ObjectFind(0, OBJ_NAME_TREND) < 0)
      ObjectCreate(0, OBJ_NAME_TREND, OBJ_TREND, 0, t1, p1, t2, p2);

   ObjectSetInteger(0, OBJ_NAME_TREND, OBJPROP_TIME, 0, t1);
   ObjectSetDouble (0, OBJ_NAME_TREND, OBJPROP_PRICE, 0, p1);
   ObjectSetInteger(0, OBJ_NAME_TREND, OBJPROP_TIME, 1, t2);
   ObjectSetDouble (0, OBJ_NAME_TREND, OBJPROP_PRICE, 1, p2);

   ObjectSetInteger(0, OBJ_NAME_TREND, OBJPROP_RAY_RIGHT, false);
   ObjectSetInteger(0, OBJ_NAME_TREND, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, OBJ_NAME_TREND, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, OBJ_NAME_TREND, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, OBJ_NAME_TREND, OBJPROP_WIDTH, 2);
}

//+------------------------------------------------------------------+
//  Scan the chart for OBJ_TREND-style lines coloured DodgerBlue —
//  these are drawn directly by Paradox_DB_SwingZZ itself, separate
//  from its data buffer — and return the EARLIEST (leftmost) time
//  among all of them. This defines how far back to search for the
//  true Highest High / Lowest Low, without depending on any single
//  vertex price (which may lag behind the real, still-developing
//  high/low).
//+------------------------------------------------------------------+
bool FindDodgerBlueRangeStart(datetime &outTime)
{
   bool     found = false;
   datetime best  = 0;

   int total = ObjectsTotal(0, -1, -1);
   for(int k = 0; k < total; k++)
   {
      string name = ObjectName(0, k, -1, -1);
      if(StringFind(name, "PFibo_swzz_") == 0) continue;   // skip our own objects

      long objType = ObjectGetInteger(0, name, OBJPROP_TYPE);
      if(objType != OBJ_TREND && objType != OBJ_TRENDBYANGLE &&
         objType != OBJ_FIBO  && objType != OBJ_CHANNEL)
         continue;   // only consider 2-point line-style objects

      if((color)ObjectGetInteger(0, name, OBJPROP_COLOR) != clrDodgerBlue)
         continue;

      datetime t0 = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 0);
      datetime t1 = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 1);
      datetime earlier = (t0 <= t1) ? t0 : t1;

      if(!found || earlier < best) { best = earlier; found = true; }
   }

   if(found) outTime = best;
   return found;
}

//+------------------------------------------------------------------+
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(Pause) return rates_total;
   if(rates_total < InpSwingBarsBack + InpSwingLookBack + 10) return 0;

   //------------------------------------------------------------------
   //  Bound the search window using the earliest DodgerBlue swing
   //  line SwingZZ has drawn (falls back to the raw BarsBack window
   //  if none exist yet). Then find the TRUE Highest High / Lowest
   //  Low by scanning the high[]/low[]/time[] arrays OnCalculate
   //  itself provides — never a separate series cache — so the
   //  result is always in sync with what's actually on screen and
   //  can never silently skip an update.
   //------------------------------------------------------------------
   datetime rangeStart;
   int startIndex = 0;

   if(FindDodgerBlueRangeStart(rangeStart))
   {
      startIndex = -1;
      for(int i = 0; i < rates_total; i++)
      {
         if(time[i] >= rangeStart) { startIndex = i; break; }
      }
      if(startIndex < 0)
      {
         int fallbackBars = MathMin(rates_total - 1, InpSwingBarsBack + InpSwingLookBack);
         startIndex = rates_total - 1 - fallbackBars;
      }
   }
   else
   {
      int fallbackBars = MathMin(rates_total - 1, InpSwingBarsBack + InpSwingLookBack);
      startIndex = rates_total - 1 - fallbackBars;
   }

   if(startIndex < 0) startIndex = 0;
   if(startIndex >= rates_total - 1) return prev_calculated;

   double peakPrice = -DBL_MAX, troughPrice = DBL_MAX;
   int    peakIdx = -1, troughIdx = -1;

   for(int i = startIndex; i < rates_total; i++)
   {
      if(high[i] > peakPrice)   { peakPrice   = high[i]; peakIdx   = i; }
      if(low[i]  < troughPrice) { troughPrice = low[i];  troughIdx = i; }
   }

   if(peakIdx < 0 || troughIdx < 0 || peakIdx == troughIdx)
      return prev_calculated;

   datetime peakTime   = time[peakIdx];
   datetime troughTime = time[troughIdx];

   //------------------------------------------------------------------
   //  0% anchors to whichever of peak/trough came EARLIER in time,
   //  100% to whichever came LATER — same convention MT5's native
   //  Fibo tool uses. Direction/colour follows whether that move
   //  went up or down.
   //------------------------------------------------------------------
   datetime olderTime, newerTime;
   double   olderPrice, newerPrice;

   if(peakTime < troughTime)
   {
      olderTime = peakTime;  olderPrice = peakPrice;
      newerTime = troughTime; newerPrice = troughPrice;
   }
   else
   {
      olderTime = troughTime; olderPrice = troughPrice;
      newerTime = peakTime;   newerPrice = peakPrice;
   }

   //------------------------------------------------------------------
   //  Direction/colour follows whether that move went up or down.
   //------------------------------------------------------------------
   bool highToLow = (newerPrice < olderPrice);
   color activeColor = highToLow ? FiboColorHighToLow : FiboColorLowToHigh;

   UpdateFiboObject(olderTime, olderPrice, newerTime, newerPrice, activeColor);
   UpdateTrendObject(olderTime, olderPrice, newerTime, newerPrice, activeColor);

   ChartRedraw();
   return rates_total;
}
//+------------------------------------------------------------------+
