//+------------------------------------------------------------------+
//| StratAssistant.mq5                                               |
//| Ported from "Strat Assistant" (Pine Script v4) by rickyzcarroll  |
//+------------------------------------------------------------------+
#property copyright "Ported from rickyzcarroll's Strat Assistant"
#property link      ""
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 7
#property indicator_plots   3

//+------------------------------------------------------------------+
//| Static colors / constants (mirror Pine hardcoded constants)       |
//| Defined before #property lines below so the macros can be used   |
//| directly in them, keeping every reference to these colors in     |
//| exact agreement.                                                  |
//+------------------------------------------------------------------+
#define CLR_BULL      clrGreen
#define CLR_BEAR      clrRed
#define CLR_OUTSIDE   clrMagenta
#define CLR_INSIDE    C'246,190,0'   // matches Pine #F6BE00

//--- plot 0: overlay candles, recolored by STRAT bar type (mirrors barcolor())
#property indicator_label1  "Open;High;Low;Close"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrSilver,clrDimGray,C'246,190,0',clrGreen,clrRed,clrMagenta
#property indicator_width1  1

//--- plot 1: "2 up" shape above bar (mirrors plotshape(_bullShape))
#property indicator_label2  "TwoUp"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrGreen
#property indicator_width2  2

//--- plot 2: "2 down" shape above bar (mirrors plotshape(_bearShape))
#property indicator_label3  "TwoDown"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrRed
#property indicator_width3  2

//+------------------------------------------------------------------+
//| Inputs (mirrors Pine input() block)                               |
//+------------------------------------------------------------------+
input double InpActionWickPct      = 0.75;    // Action Wick Percentage
input bool   InpPrevDayHighActive  = true;    // Prev Day High Line Active?
input color  InpPrevDayHighColor   = clrLime; // Prev Day High Line Color
input bool   InpPrevDayLowActive   = true;    // Prev Day Low Line Active?
input color  InpPrevDayLowColor    = clrMaroon;// Prev Day Low Line Color
input bool   InpPrevWeekHighActive = false;   // Prev Week High Line Active?
input color  InpPrevWeekHighColor  = clrOrange;// Prev Week High Line Color
input bool   InpPrevWeekLowActive  = false;   // Prev Week Low Line Active?
input color  InpPrevWeekLowColor   = clrPurple;// Prev Week Low Line Color
input bool   InpFtcActive          = true;    // Full Time Frame Continuity Active?
input bool   InpShowArrows         = true;    // Show 2-Up/2-Down Arrows
input bool   InpDebugLabelActive   = false;   // Debug Label (Beta)
input int    InpMaxCharHistoryBars = 1000;    // Max bars to keep "1/2/3" text markers on (perf cap)

enum ENUM_FTC_CORNER
  {
   FTC_TOP_LEFT,
   FTC_TOP_RIGHT,
   FTC_BOTTOM_LEFT,
   FTC_BOTTOM_RIGHT
  };
input ENUM_FTC_CORNER InpFtcCorner  = FTC_TOP_RIGHT; // FTC Dashboard Corner
input int              InpFtcOffsetX = 0;             // FTC Dashboard Offset X (px, +right/-left)
input int              InpFtcOffsetY = 0;             // FTC Dashboard Offset Y (px, +down/-up)
input color             InpFtcBgColor = clrBlack;      // FTC Dashboard Background Color

// candle color-index buffer values (must match #property indicator_color1 order above)
#define CIDX_NEUTRAL_UP   0
#define CIDX_NEUTRAL_DOWN 1
#define CIDX_ONE          2
#define CIDX_TWOUP        3
#define CIDX_TWODOWN      4
#define CIDX_THREE        5

#define OBJ_PREFIX "STRAT_"

// FTC dashboard screen-grid layout (pixels)
#define FTC_COL_WIDTH   34
#define FTC_ROW_HEIGHT  16
#define FTC_ROW_IN      0
#define FTC_ROW_LABEL   1
#define FTC_ROW_ARROW   2
#define FTC_FONT_SIZE    9
#define FTC_MARGIN       6

//+------------------------------------------------------------------+
//| Indicator buffers                                                  |
//+------------------------------------------------------------------+
double BufCandleOpen[];
double BufCandleHigh[];
double BufCandleLow[];
double BufCandleClose[];
double BufCandleColor[];
double BufTwoUp[];
double BufTwoDown[];

//+------------------------------------------------------------------+
//| Bar-index constants (0 = current/rightmost, matches Pine comment: |
//| "0 index works right to left")                                    |
//+------------------------------------------------------------------+
#define FIRST_BAR_IDX  0
#define SECOND_BAR_IDX 1
#define THIRD_BAR_IDX  2
#define FOURTH_BAR_IDX 3
#define BAR_IDX_OFFSET 1

//+------------------------------------------------------------------+
//| Combo label text (mirrors Pine label string constants)            |
//+------------------------------------------------------------------+
#define TXT_222_BEAR_CONT      "222 Bearish\nContinuation"
#define TXT_222_BULL_CONT      "222 Bullish\nContinuation"
#define TXT_212_BEAR_CONT_MM   "212 Bearish Continuation\nMeasured Move"
#define TXT_212_BULL_CONT_MM   "212 Bullish Continuation\nMeasured Move"
#define TXT_22_BEAR_REV        "22 Bearish\nReversal"
#define TXT_22_BULL_REV        "22 Bullish\nReversal"
#define TXT_212_BEAR_REV       "212 Bearish\nReversal"
#define TXT_212_BULL_REV       "212 Bullish\nReversal"
#define TXT_322_BEAR_REV       "322 Bearish\nReversal"
#define TXT_322_BULL_REV       "322 Bullish\nReversal"
#define TXT_32_BEAR_REV        "32 Bearish\nReversal"
#define TXT_32_BULL_REV        "32 Bullish\nReversal"
#define TXT_312_BEAR_REV       "312 Bearish\nReversal"
#define TXT_312_BULL_REV       "312 Bullish\nReversal"
#define TXT_122_BEAR_REVSTRAT  "122 Bearish RevStrat\nReversal"
#define TXT_122_BULL_REVSTRAT  "122 Bullish RevStrat\nReversal"
#define TXT_2222_BEAR_RJ       "2222 Bearish\nRandyJackson"
#define TXT_2222_BULL_RJ       "2222 Bullish\nRandyJackson"
#define TXT_3_BEAR_REVSTRAT    "3 Bearish\nRevStrat Reversal"
#define TXT_3_BULL_REVSTRAT    "3 Bullish\nRevStrat Reversal"

//+------------------------------------------------------------------+
//| OnInit                                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0, BufCandleOpen,  INDICATOR_DATA);
   SetIndexBuffer(1, BufCandleHigh,  INDICATOR_DATA);
   SetIndexBuffer(2, BufCandleLow,   INDICATOR_DATA);
   SetIndexBuffer(3, BufCandleClose, INDICATOR_DATA);
   SetIndexBuffer(4, BufCandleColor, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(5, BufTwoUp,   INDICATOR_DATA);
   SetIndexBuffer(6, BufTwoDown, INDICATOR_DATA);

   PlotIndexSetInteger(1, PLOT_ARROW, 233); // Wingdings up-triangle, mirrors shape.arrowup
   PlotIndexSetInteger(2, PLOT_ARROW, 234); // Wingdings down-triangle, mirrors shape.arrowdown
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   ArraySetAsSeries(BufCandleOpen,  false);
   ArraySetAsSeries(BufCandleHigh,  false);
   ArraySetAsSeries(BufCandleLow,   false);
   ArraySetAsSeries(BufCandleClose, false);
   ArraySetAsSeries(BufCandleColor, false);
   ArraySetAsSeries(BufTwoUp,   false);
   ArraySetAsSeries(BufTwoDown, false);

   IndicatorSetString(INDICATOR_SHORTNAME, "Strat Assistant");
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| OnDeinit - clean up every chart object we ever created            |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, OBJ_PREFIX);
  }

//+------------------------------------------------------------------+
//| Primitive bar-type comparisons (mirror getOneBar/getTwoUpBar/...) |
//| right/left are ABSOLUTE array indices into ascending H[]/L[]      |
//+------------------------------------------------------------------+
bool BarIsOne(const int right, const int left, const double &H[], const double &L[])
  {
   return (H[right] <= H[left] && L[right] >= L[left]);
  }
bool BarIsTwoUp(const int right, const int left, const double &H[], const double &L[])
  {
   return (H[right] > H[left] && !(L[right] < L[left]));
  }
bool BarIsTwoDown(const int right, const int left, const double &H[], const double &L[])
  {
   return (L[right] < L[left] && !(H[right] > H[left]));
  }
bool BarIsThree(const int right, const int left, const double &H[], const double &L[])
  {
   return (H[right] > H[left] && L[right] < L[left]);
  }

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

   //--- Part 1: full-history bar-type classification (buffer based, incremental) ---
   int calcStart = (prev_calculated <= 1) ? 1 : prev_calculated - 1;
   for(int i = calcStart; i < rates_total; i++)
     {
      BufCandleOpen[i]  = open[i];
      BufCandleHigh[i]  = high[i];
      BufCandleLow[i]   = low[i];
      BufCandleClose[i] = close[i];
      BufTwoUp[i]   = EMPTY_VALUE;
      BufTwoDown[i] = EMPTY_VALUE;

      bool isOne    = BarIsOne(i, i - 1, high, low);
      bool isThree  = BarIsThree(i, i - 1, high, low);
      bool isTwoUp  = BarIsTwoUp(i, i - 1, high, low);
      bool isTwoDown= BarIsTwoDown(i, i - 1, high, low);

      // getBarColor(): one, else three, else twoUp, else twoDown, else na
      if(isOne)
         BufCandleColor[i] = CIDX_ONE;
      else if(isThree)
         BufCandleColor[i] = CIDX_THREE;
      else if(isTwoUp)
         BufCandleColor[i] = CIDX_TWOUP;
      else if(isTwoDown)
         BufCandleColor[i] = CIDX_TWODOWN;
      else
         BufCandleColor[i] = (close[i] >= open[i]) ? CIDX_NEUTRAL_UP : CIDX_NEUTRAL_DOWN;

      // Pine plots BOTH the up and down arrow shapes at location.abovebar (see source
      // comment "identify 2 up/down candle with indicator above").
      if(InpShowArrows)
        {
         double barGap = MathMax((high[i] - low[i]) * 0.3, _Point * 10);
         if(isTwoUp)
            BufTwoUp[i] = high[i] + barGap;
         if(isTwoDown)
            BufTwoDown[i] = high[i] + barGap;
        }

      UpdateCharMarker(i, time[i], low[i], isOne, isTwoUp, isTwoDown, isThree);
     }

   // drop char markers that scrolled outside the configured window
   PruneOldCharMarkers(rates_total);

   //--- Part 2: everything that Pine only ever draws on the latest bar (show_last=1) ---
   int last = rates_total - 1;
   if(last >= 4)
     {
      UpdateActionableSignals(last, time, open, high, low, close);
      UpdateStratCombos(last, time, high, low, close);
     }
   if(InpFtcActive)
      UpdateFtc();
   else
      FtcDeleteAll();
   UpdatePrevHighLowLines();
   UpdateDebugLabel(last, time, close);

   return(rates_total);
  }

//+------------------------------------------------------------------+
//| "1"/"2"/"3" text markers below/above bar (mirrors plotchar chars) |
//| Implemented as chart objects (bounded window) since MT5 DRAW_ARROW|
//| plots are locked to the Wingdings symbol font and cannot render   |
//| literal digit glyphs.                                             |
//+------------------------------------------------------------------+
void UpdateCharMarker(const int i, const datetime t, const double lowPrice, const bool isOne,
                       const bool isTwoUp, const bool isTwoDown, const bool isThree)
  {
   string name = OBJ_PREFIX + "Char_" + IntegerToString(i);
   if(!(isOne || isTwoUp || isTwoDown || isThree))
     {
      ObjectDelete(0, name);
      return;
     }

   string txt; color clr;
   if(isOne)         { txt = "1"; clr = CLR_INSIDE; }
   else if(isThree)  { txt = "3"; clr = CLR_OUTSIDE; }
   else if(isTwoUp)  { txt = "2"; clr = CLR_BULL; }
   else              { txt = "2"; clr = CLR_BEAR; }

   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_TEXT, 0, t, lowPrice);
   ObjectSetInteger(0, name, OBJPROP_TIME, t);
   ObjectSetDouble(0, name, OBJPROP_PRICE, lowPrice);
   ObjectSetString(0, name, OBJPROP_TEXT, txt);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8);
   ObjectSetString(0, name, OBJPROP_FONT, "Arial");
  }

//+------------------------------------------------------------------+
//| Remove char markers whose bar has scrolled outside the window     |
//+------------------------------------------------------------------+
void PruneOldCharMarkers(const int rates_total)
  {
   if(InpMaxCharHistoryBars <= 0)
      return;
   int cutoff = rates_total - InpMaxCharHistoryBars;
   if(cutoff <= 0)
      return;
   int total = ObjectsTotal(0, 0, OBJ_TEXT);
   for(int k = total - 1; k >= 0; k--)
     {
      string name = ObjectName(0, k, 0, OBJ_TEXT);
      if(StringFind(name, OBJ_PREFIX + "Char_") != 0)
         continue;
      string numPart = StringSubstr(name, StringLen(OBJ_PREFIX + "Char_"));
      int idx = (int)StringToInteger(numPart);
      if(idx < cutoff)
         ObjectDelete(0, name);
     }
  }

//+------------------------------------------------------------------+
//| getIndexXxxBar(barIndex) equivalents, evaluated at the last bar   |
//+------------------------------------------------------------------+
bool IndexOneBar(const int last, const int barIndex, const double &H[], const double &L[])
  {
   int right = last - barIndex;
   int left  = last - (barIndex + BAR_IDX_OFFSET);
   if(left < 0) return(false);
   return BarIsOne(right, left, H, L);
  }
bool IndexTwoUpBar(const int last, const int barIndex, const double &H[], const double &L[])
  {
   int right = last - barIndex;
   int left  = last - (barIndex + BAR_IDX_OFFSET);
   if(left < 0) return(false);
   return BarIsTwoUp(right, left, H, L);
  }
bool IndexTwoDownBar(const int last, const int barIndex, const double &H[], const double &L[])
  {
   int right = last - barIndex;
   int left  = last - (barIndex + BAR_IDX_OFFSET);
   if(left < 0) return(false);
   return BarIsTwoDown(right, left, H, L);
  }
bool IndexThreeBar(const int last, const int barIndex, const double &H[], const double &L[])
  {
   int right = last - barIndex;
   int left  = last - (barIndex + BAR_IDX_OFFSET);
   if(left < 0) return(false);
   return BarIsThree(right, left, H, L);
  }

//+------------------------------------------------------------------+
//| Pine's location.top / location.bottom pin to the visible chart    |
//| price range, not to any particular bar's price. Mirrors that.     |
//+------------------------------------------------------------------+
double PaneTopPrice()
  {
   double hi = ChartGetDouble(0, CHART_PRICE_MAX, 0);
   double lo = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   return hi - (hi - lo) * 0.03;
  }
double PaneBottomPrice()
  {
   double hi = ChartGetDouble(0, CHART_PRICE_MAX, 0);
   double lo = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   return lo + (hi - lo) * 0.03;
  }

//+------------------------------------------------------------------+
//| Actionable signals (shooter / hammer / inside bar) - last bar only|
//+------------------------------------------------------------------+
void UpdateActionableSignals(const int last, const datetime &time[], const double &open[],
                              const double &high[], const double &low[], const double &close[])
  {
   double wickHeight   = (high[last] - low[last]) * InpActionWickPct;
   double shooterTop    = high[last] - wickHeight;
   double hammerBottom  = low[last] + wickHeight;
   bool   insideBar     = IndexOneBar(last, FIRST_BAR_IDX, high, low);
   bool   isShooter      = open[last] < shooterTop && close[last] < shooterTop && !insideBar;
   bool   isHammer       = open[last] > hammerBottom && close[last] > hammerBottom && !insideBar;

   datetime tOffset = OffsetTime(time, last, 2); // Pine offset=2
   double y = PaneBottomPrice(); // Pine location.bottom

   DrawFlagBelow(OBJ_PREFIX + "Actionable_Shooter", isShooter, tOffset, y, "Shooter", CLR_BEAR);
   DrawFlagBelow(OBJ_PREFIX + "Actionable_Hammer",  isHammer,  tOffset, y, "Hammer",  CLR_BULL);
   DrawFlagBelow(OBJ_PREFIX + "Actionable_Inside",  insideBar, tOffset, y, "Inside Bar", CLR_INSIDE);
  }

//+------------------------------------------------------------------+
//| STRAT combo patterns - last bar only, drawn as flags at the top   |
//+------------------------------------------------------------------+
void UpdateStratCombos(const int last, const datetime &time[], const double &high[],
                        const double &low[], const double &close[])
  {
   bool is222BearCont = IndexTwoDownBar(last, THIRD_BAR_IDX, high, low) && IndexTwoDownBar(last, SECOND_BAR_IDX, high, low) && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is222BullCont = IndexTwoUpBar(last, THIRD_BAR_IDX, high, low)   && IndexTwoUpBar(last, SECOND_BAR_IDX, high, low)   && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is212BearContMM = IndexTwoDownBar(last, THIRD_BAR_IDX, high, low) && IndexOneBar(last, SECOND_BAR_IDX, high, low) && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is212BullContMM = IndexTwoUpBar(last, THIRD_BAR_IDX, high, low)   && IndexOneBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is22BearRev = IndexTwoUpBar(last, SECOND_BAR_IDX, high, low) && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low)
                       && !IndexOneBar(last, THIRD_BAR_IDX, high, low) && !IndexTwoDownBar(last, THIRD_BAR_IDX, high, low)
                       && !IndexTwoDownBar(last, 3, high, low) && !IndexThreeBar(last, THIRD_BAR_IDX, high, low);
   bool is22BullRev = IndexTwoDownBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low)
                       && !IndexOneBar(last, THIRD_BAR_IDX, high, low) && !IndexTwoUpBar(last, THIRD_BAR_IDX, high, low)
                       && !IndexTwoUpBar(last, 3, high, low) && !IndexThreeBar(last, THIRD_BAR_IDX, high, low);

   bool is212BearRev = IndexTwoUpBar(last, THIRD_BAR_IDX, high, low)   && IndexOneBar(last, SECOND_BAR_IDX, high, low) && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is212BullRev = IndexTwoDownBar(last, THIRD_BAR_IDX, high, low) && IndexOneBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is322BearRev = IndexThreeBar(last, THIRD_BAR_IDX, high, low) && IndexTwoUpBar(last, SECOND_BAR_IDX, high, low)   && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is322BullRev = IndexThreeBar(last, THIRD_BAR_IDX, high, low) && IndexTwoDownBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is32BearRev = IndexThreeBar(last, SECOND_BAR_IDX, high, low) && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is32BullRev = IndexThreeBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is312BearRev = IndexThreeBar(last, THIRD_BAR_IDX, high, low) && IndexOneBar(last, SECOND_BAR_IDX, high, low) && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is312BullRev = IndexThreeBar(last, THIRD_BAR_IDX, high, low) && IndexOneBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is122BearRevStrat = IndexOneBar(last, THIRD_BAR_IDX, high, low) && IndexTwoUpBar(last, SECOND_BAR_IDX, high, low)   && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is122BullRevStrat = IndexOneBar(last, THIRD_BAR_IDX, high, low) && IndexTwoDownBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is2222BearRJ = IndexTwoDownBar(last, FOURTH_BAR_IDX, high, low) && IndexTwoDownBar(last, THIRD_BAR_IDX, high, low) && IndexTwoUpBar(last, SECOND_BAR_IDX, high, low)   && IndexTwoDownBar(last, FIRST_BAR_IDX, high, low);
   bool is2222BullRJ = IndexTwoUpBar(last, FOURTH_BAR_IDX, high, low)   && IndexTwoUpBar(last, THIRD_BAR_IDX, high, low)   && IndexTwoDownBar(last, SECOND_BAR_IDX, high, low) && IndexTwoUpBar(last, FIRST_BAR_IDX, high, low);

   bool is3BearRevStrat = IndexThreeBar(last, FIRST_BAR_IDX, high, low) && close[last] < low[last - 1];
   bool is3BullRevStrat = IndexThreeBar(last, FIRST_BAR_IDX, high, low) && close[last] > low[last - 1];

   datetime tOffset = OffsetTime(time, last, 3); // Pine offset=3
   double y = PaneTopPrice(); // Pine location.top

   DrawFlagAbove(OBJ_PREFIX + "Combo_222BearCont",   is222BearCont,   tOffset, y, TXT_222_BEAR_CONT,     CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_222BullCont",   is222BullCont,   tOffset, y, TXT_222_BULL_CONT,     CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_212BearContMM", is212BearContMM, tOffset, y, TXT_212_BEAR_CONT_MM,  CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_212BullContMM", is212BullContMM, tOffset, y, TXT_212_BULL_CONT_MM,  CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_22BearRev",     is22BearRev,     tOffset, y, TXT_22_BEAR_REV,       CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_22BullRev",     is22BullRev,     tOffset, y, TXT_22_BULL_REV,       CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_212BearRev",    is212BearRev,    tOffset, y, TXT_212_BEAR_REV,      CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_212BullRev",    is212BullRev,    tOffset, y, TXT_212_BULL_REV,      CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_322BearRev",    is322BearRev,    tOffset, y, TXT_322_BEAR_REV,      CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_322BullRev",    is322BullRev,    tOffset, y, TXT_322_BULL_REV,      CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_32BearRev",     is32BearRev,     tOffset, y, TXT_32_BEAR_REV,       CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_32BullRev",     is32BullRev,     tOffset, y, TXT_32_BULL_REV,       CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_312BearRev",    is312BearRev,    tOffset, y, TXT_312_BEAR_REV,      CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_312BullRev",    is312BullRev,    tOffset, y, TXT_312_BULL_REV,      CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_122BearRS",     is122BearRevStrat, tOffset, y, TXT_122_BEAR_REVSTRAT, CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_122BullRS",     is122BullRevStrat, tOffset, y, TXT_122_BULL_REVSTRAT, CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_2222BearRJ",    is2222BearRJ,    tOffset, y, TXT_2222_BEAR_RJ,      CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_2222BullRJ",    is2222BullRJ,    tOffset, y, TXT_2222_BULL_RJ,      CLR_BULL);
   DrawFlagAbove(OBJ_PREFIX + "Combo_3BearRS",       is3BearRevStrat, tOffset, y, TXT_3_BEAR_REVSTRAT,   CLR_BEAR);
   DrawFlagAbove(OBJ_PREFIX + "Combo_3BullRS",       is3BullRevStrat, tOffset, y, TXT_3_BULL_REVSTRAT,   CLR_BULL);
  }

//+------------------------------------------------------------------+
//| Full Time Frame Continuity dashboard - last bar only, screen-     |
//| anchored (movable to any corner, fine-tunable via pixel offsets)  |
//+------------------------------------------------------------------+
void FtcDeleteAll()
  {
   ObjectsDeleteAll(0, OBJ_PREFIX + "Ftc_");
  }

#define FTC_BG_PAD 4

void FtcDrawBackground(const int usedCols, const int baseX, const int baseY)
  {
   string name = OBJ_PREFIX + "Ftc_Bg";
   if(usedCols <= 0)
     {
      ObjectDelete(0, name);
      return;
     }
   int x = baseX - FTC_BG_PAD;
   int y = baseY - FTC_BG_PAD;
   int w = usedCols * FTC_COL_WIDTH + FTC_BG_PAD * 2;
   int h = 3 * FTC_ROW_HEIGHT + FTC_BG_PAD * 2;

   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, name, OBJPROP_BACK, false);
      ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
     }
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
   ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, InpFtcBgColor);
   ObjectSetInteger(0, name, OBJPROP_COLOR, InpFtcBgColor);
  }

void FtcDeleteColumn(const string key)
  {
   ObjectDelete(0, OBJ_PREFIX + "Ftc_" + key + "_In");
   ObjectDelete(0, OBJ_PREFIX + "Ftc_" + key + "_Out");
   ObjectDelete(0, OBJ_PREFIX + "Ftc_" + key + "_Grn");
   ObjectDelete(0, OBJ_PREFIX + "Ftc_" + key + "_Red");
   ObjectDelete(0, OBJ_PREFIX + "Ftc_" + key + "_ArrUp");
   ObjectDelete(0, OBJ_PREFIX + "Ftc_" + key + "_ArrDown");
  }

// Computes the panel's top-left pixel origin from the chosen corner + fine-tune offsets.
// Internally everything is drawn CORNER_LEFT_UPPER (see DrawFtcTag) - this sidesteps
// MT5's reversed x/y distance semantics on the RIGHT/BOTTOM chart corners entirely.
void FtcComputeBase(const int usedCols, int &baseX, int &baseY)
  {
   int panelW = usedCols * FTC_COL_WIDTH;
   int panelH = 3 * FTC_ROW_HEIGHT;
   int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int chartH = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   int x, y;
   switch(InpFtcCorner)
     {
      case FTC_TOP_LEFT:    x = FTC_MARGIN;                                    y = FTC_MARGIN; break;
      case FTC_TOP_RIGHT:   x = MathMax(FTC_MARGIN, chartW - panelW - FTC_MARGIN); y = FTC_MARGIN; break;
      case FTC_BOTTOM_LEFT: x = FTC_MARGIN;                                    y = MathMax(FTC_MARGIN, chartH - panelH - FTC_MARGIN); break;
      default:              x = MathMax(FTC_MARGIN, chartW - panelW - FTC_MARGIN); y = MathMax(FTC_MARGIN, chartH - panelH - FTC_MARGIN); break; // FTC_BOTTOM_RIGHT
     }
   baseX = x + InpFtcOffsetX;
   baseY = y + InpFtcOffsetY;
  }

void UpdateFtc()
  {
   ENUM_TIMEFRAMES period = (ENUM_TIMEFRAMES)_Period;

   bool is15Valid = (period==PERIOD_M1 || period==PERIOD_M3 || period==PERIOD_M5 || period==PERIOD_M10 || period==PERIOD_M15);
   bool is30Valid = is15Valid || period==PERIOD_M30;
   bool isHourValid = is30Valid || period==PERIOD_H1;
   bool isIntraday = (PeriodSeconds(period) < PeriodSeconds(PERIOD_D1));
   bool isFourHourValid = isHourValid || isIntraday;
   bool isDayValid = isFourHourValid || period==PERIOD_D1;
   bool isWeekValid = isDayValid || period==PERIOD_W1 || isIntraday;
   bool isMonthValid = isWeekValid || period==PERIOD_MN1;
   bool isDwm = (period==PERIOD_D1 || period==PERIOD_W1 || period==PERIOD_MN1);
   bool isQuarterValid = isWeekValid || isDwm;

   string keys[8]         = {"15","30","H","4H","D","W","M","Q"};
   bool   valids[8]       = {is15Valid, is30Valid, isHourValid, isFourHourValid, isDayValid, isWeekValid, isMonthValid, isQuarterValid};
   ENUM_TIMEFRAMES tfs[7] = {PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1, PERIOD_MN1};

   int usedCols = 0;
   for(int k = 0; k < 8; k++)
      if(valids[k]) usedCols++;

   int baseX, baseY;
   FtcComputeBase(MathMax(usedCols, 1), baseX, baseY);
   FtcDrawBackground(usedCols, baseX, baseY);

   int col = 0;
   for(int k = 0; k < 8; k++)
     {
      if(!valids[k])
        {
         FtcDeleteColumn(keys[k]);
         continue;
        }
      if(k < 7)
         FtcDrawTF(keys[k], tfs[k], col, baseX, baseY);
      else
         FtcDrawQuarter(keys[k], col, baseX, baseY);
      col++;
     }
  }

void FtcDrawColumn(const string key, const int col, const int baseX, const int baseY,
                    const bool isGreen, const bool isInside, const bool isOutside)
  {
   int x      = baseX + col * FTC_COL_WIDTH;
   int yIn    = baseY + FTC_ROW_IN    * FTC_ROW_HEIGHT;
   int yLabel = baseY + FTC_ROW_LABEL * FTC_ROW_HEIGHT;
   int yArrow = baseY + FTC_ROW_ARROW * FTC_ROW_HEIGHT;

   DrawFtcTag(OBJ_PREFIX + "Ftc_" + key + "_In",      isInside,  x, yIn,    "IN", CLR_INSIDE);
   DrawFtcTag(OBJ_PREFIX + "Ftc_" + key + "_Out",     isOutside, x, yIn,    "O",  CLR_OUTSIDE);
   DrawFtcTag(OBJ_PREFIX + "Ftc_" + key + "_Grn",     isGreen,   x, yLabel, key,  CLR_BULL);
   DrawFtcTag(OBJ_PREFIX + "Ftc_" + key + "_Red",     !isGreen,  x, yLabel, key,  CLR_BEAR);
   DrawFtcTag(OBJ_PREFIX + "Ftc_" + key + "_ArrUp",   isGreen,   x, yArrow, "▲", CLR_BULL);
   DrawFtcTag(OBJ_PREFIX + "Ftc_" + key + "_ArrDown", !isGreen,  x, yArrow, "▼", CLR_BEAR);
  }

void FtcDrawTF(const string key, const ENUM_TIMEFRAMES tf, const int col, const int baseX, const int baseY)
  {
   // Pine's security() (no lookahead) reflects the live/forming HTF bar on the
   // realtime chart bar (index [0]), compared against the last closed HTF bar ([1]).
   double o1=iOpen(_Symbol,tf,0), c1=iClose(_Symbol,tf,0), h1=iHigh(_Symbol,tf,0), l1=iLow(_Symbol,tf,0);
   double h2=iHigh(_Symbol,tf,1), l2=iLow(_Symbol,tf,1);
   if(h1==0 || h2==0)
     {
      FtcDeleteColumn(key);
      return;
     }

   bool isGreen   = (o1 <= c1);
   bool isInside  = (h1 <= h2 && l1 >= l2);
   bool isOutside = (h1 > h2 && l1 < l2);

   FtcDrawColumn(key, col, baseX, baseY, isGreen, isInside, isOutside);
  }

// Pine has no native "3M" resolution; synthesize calendar quarters from 3 monthly bars.
void FtcDrawQuarter(const string key, const int col, const int baseX, const int baseY)
  {
   double o1,h1,l1,c1,o2,h2,l2,c2;
   if(!GetQuarterOHLC(0, o1,h1,l1,c1) || !GetQuarterOHLC(1, o2,h2,l2,c2))
     {
      FtcDeleteColumn(key);
      return;
     }

   bool isGreen   = (o1 <= c1);
   bool isInside  = (h1 <= h2 && l1 >= l2);
   bool isOutside = (h1 > h2 && l1 < l2);

   FtcDrawColumn(key, col, baseX, baseY, isGreen, isInside, isOutside);
  }

// quartersBack: 0 = current (possibly in-progress) calendar quarter, 1 = the last fully-closed one
bool GetQuarterOHLC(const int quartersBack, double &o, double &h, double &l, double &c)
  {
   datetime qStart = GetQuarterStartTime(TimeCurrent(), quartersBack);
   datetime qEndEx = GetQuarterStartTime(TimeCurrent(), quartersBack - 1);

   int shiftNewest = iBarShift(_Symbol, PERIOD_MN1, qEndEx - 1, false);
   int shiftOldest = iBarShift(_Symbol, PERIOD_MN1, qStart, false);
   if(shiftOldest < shiftNewest || shiftOldest < 0 || shiftNewest < 0)
      return(false);

   h = -DBL_MAX; l = DBL_MAX;
   for(int s = shiftNewest; s <= shiftOldest; s++)
     {
      double hh = iHigh(_Symbol, PERIOD_MN1, s);
      double ll = iLow(_Symbol, PERIOD_MN1, s);
      if(hh <= 0 || ll <= 0)
         return(false);
      if(hh > h) h = hh;
      if(ll < l) l = ll;
     }
   o = iOpen(_Symbol, PERIOD_MN1, shiftOldest);
   c = iClose(_Symbol, PERIOD_MN1, shiftNewest);
   return(true);
  }

datetime GetQuarterStartTime(const datetime t, const int quartersBack)
  {
   MqlDateTime dt;
   TimeToStruct(t, dt);
   int monthsFromEpoch = dt.year * 12 + (dt.mon - 1);
   int quarterIdx = monthsFromEpoch / 3;
   quarterIdx -= quartersBack;
   int startMonths = quarterIdx * 3;
   MqlDateTime sdt;
   sdt.year = startMonths / 12;
   sdt.mon  = (startMonths % 12) + 1;
   sdt.day = 1; sdt.hour = 0; sdt.min = 0; sdt.sec = 0;
   return StructToTime(sdt);
  }

//+------------------------------------------------------------------+
//| Previous day / week high-low lines                                |
//+------------------------------------------------------------------+
void UpdatePrevHighLowLines()
  {
   bool intraday = PeriodSeconds(PERIOD_CURRENT) < PeriodSeconds(PERIOD_D1);
   bool daily    = _Period == PERIOD_D1;
   bool weekly   = _Period == PERIOD_W1;

   string nHigh = OBJ_PREFIX + "PrevDayHigh";
   string nLow  = OBJ_PREFIX + "PrevDayLow";
   string nWHigh= OBJ_PREFIX + "PrevWeekHigh";
   string nWLow = OBJ_PREFIX + "PrevWeekLow";

   if(InpPrevDayHighActive && (intraday || daily))
      DrawPrevLine(nHigh, iTime(_Symbol,PERIOD_D1,1), iHigh(_Symbol,PERIOD_D1,1), InpPrevDayHighColor);
   else
      ObjectDelete(0, nHigh);

   if(InpPrevDayLowActive && (intraday || daily))
      DrawPrevLine(nLow, iTime(_Symbol,PERIOD_D1,1), iLow(_Symbol,PERIOD_D1,1), InpPrevDayLowColor);
   else
      ObjectDelete(0, nLow);

   // NB: Pine's source literally anchors x1 for both week lines off the previous
   // DAY's time (timeDayValue[1]), not the previous week's - replicated here for
   // exact parity. Harmless in practice since extend.right/RAY_RIGHT draws the
   // same visible ray regardless of the nominal left endpoint.
   if(InpPrevWeekHighActive && (intraday || daily || weekly))
      DrawPrevLine(nWHigh, iTime(_Symbol,PERIOD_D1,1), iHigh(_Symbol,PERIOD_W1,1), InpPrevWeekHighColor);
   else
      ObjectDelete(0, nWHigh);

   if(InpPrevWeekLowActive && (intraday || daily || weekly))
      DrawPrevLine(nWLow, iTime(_Symbol,PERIOD_D1,1), iLow(_Symbol,PERIOD_W1,1), InpPrevWeekLowColor);
   else
      ObjectDelete(0, nWLow);
  }

void DrawPrevLine(const string name, const datetime t1, const double price, const color clr)
  {
   if(t1 <= 0 || price <= 0)
     {
      ObjectDelete(0, name);
      return;
     }
   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_TREND, 0, t1, price, t1 + PeriodSeconds(PERIOD_CURRENT), price);
   ObjectSetInteger(0, name, OBJPROP_TIME, 0, t1);
   ObjectSetDouble(0, name, OBJPROP_PRICE, 0, price);
   ObjectSetInteger(0, name, OBJPROP_TIME, 1, t1 + PeriodSeconds(PERIOD_CURRENT));
   ObjectSetDouble(0, name, OBJPROP_PRICE, 1, price);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
   ObjectSetInteger(0, name, OBJPROP_BACK, true);
  }

//+------------------------------------------------------------------+
//| Debug label                                                       |
//+------------------------------------------------------------------+
void UpdateDebugLabel(const int last, const datetime &time[], const double &close[])
  {
   string name = OBJ_PREFIX + "DebugLabel";
   if(!InpDebugLabelActive)
     {
      ObjectDelete(0, name);
      return;
     }
   double pdh = iHigh(_Symbol, PERIOD_D1, 1);
   double pdl = iLow(_Symbol, PERIOD_D1, 1);
   double pwh = iHigh(_Symbol, PERIOD_W1, 1);
   double pwl = iLow(_Symbol, PERIOD_W1, 1);
   string txt = StringFormat("PrevDay H|L: %s|%s\n PrevWeek H|L: %s|%s",
                              DoubleToString(pdh, 2), DoubleToString(pdl, 2),
                              DoubleToString(pwh, 2), DoubleToString(pwl, 2));

   double y = close[last] - (close[last] * 0.003);
   datetime t = time[last]; // Pine anchors via x=bar_index, i.e. the current bar's time
   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_TEXT, 0, t, y);
   ObjectSetInteger(0, name, OBJPROP_TIME, t);
   ObjectSetDouble(0, name, OBJPROP_PRICE, y);
   ObjectSetString(0, name, OBJPROP_TEXT, txt);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
  }

//+------------------------------------------------------------------+
//| Shared drawing helpers                                             |
//+------------------------------------------------------------------+
datetime OffsetTime(const datetime &time[], const int last, const int barsForward)
  {
   int secs = PeriodSeconds(PERIOD_CURRENT);
   return time[last] + secs * barsForward;
  }

void DrawFlagBelow(const string name, const bool show, const datetime t, const double lowPrice,
                    const string text, const color clr)
  {
   if(!show)
     {
      ObjectDelete(0, name);
      return;
     }
   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_TEXT, 0, t, lowPrice);
   ObjectSetInteger(0, name, OBJPROP_TIME, t);
   ObjectSetDouble(0, name, OBJPROP_PRICE, lowPrice);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
  }

void DrawFlagAbove(const string name, const bool show, const datetime t, const double highPrice,
                    const string text, const color clr)
  {
   if(!show)
     {
      ObjectDelete(0, name);
      return;
     }
   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_TEXT, 0, t, highPrice);
   ObjectSetInteger(0, name, OBJPROP_TIME, t);
   ObjectSetDouble(0, name, OBJPROP_PRICE, highPrice);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
  }

void DrawFtcTag(const string name, const bool show, const int x, const int y,
                 const string text, const color clr)
  {
   if(!show)
     {
      ObjectDelete(0, name);
      return;
     }
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
      ObjectSetString(0, name, OBJPROP_FONT, "Arial");
     }
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, FTC_FONT_SIZE);
  }
//+------------------------------------------------------------------+
