//+------------------------------------------------------------------+
//|                                 FTLM hist arrows alerts BT.mq5   |
//|                              Original MQL4 algorithm by mladen   |
//+------------------------------------------------------------------+
#property copyright   "© 2025, Forex-Station"
#property link        "https://forex-station.com/post1295576812.html#p1295576812"
#property version     "1.02"
#property description "FTLM (Fast Trend Line Momentum) with Histogram, Arrows, Alerts & On/Off Buttons"

#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   2

//--- Plot 1: FTLM Histogram (5 colors)
#property indicator_label1  "FTLM Histogram"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrRed,clrMagenta,clrGray,clrTeal,clrSpringGreen
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- Plot 2: FTLM Line (covers edges of histogram)
#property indicator_label2  "FTLM"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

#property indicator_level1     0.0
#property indicator_levelcolor clrBlueViolet
#property indicator_levelstyle STYLE_DOT

//+------------------------------------------------------------------+
//| 33 Price Types Enumeration                                       |
//+------------------------------------------------------------------+
enum enPrices
{
   pr_close,       // Close
   pr_open,        // Open
   pr_high,        // High
   pr_low,         // Low
   pr_median,      // Median (high+low)/2
   pr_typical,     // Typical (high+low+close)/3
   pr_weighted,    // Weighted (high+low+close+close)/4
   pr_average,     // Average (high+low+open+close)/4
   pr_medianb,     // Average median body (open+close)/2
   pr_tbiased,     // Trend biased price
   pr_tbiased2,    // Trend biased (extreme) price
   pr_haclose,     // Heiken ashi close
   pr_haopen,      // Heiken ashi open
   pr_hahigh,      // Heiken ashi high
   pr_halow,       // Heiken ashi low
   pr_hamedian,    // Heiken ashi median
   pr_hatypical,   // Heiken ashi typical
   pr_haweighted,  // Heiken ashi weighted
   pr_haaverage,   // Heiken ashi average
   pr_hamedianb,   // Heiken ashi median body
   pr_hatbiased,   // Heiken ashi trend biased price
   pr_hatbiased2,  // Heiken ashi trend biased (extreme) price
   pr_habclose,    // Heiken ashi (better formula) close
   pr_habopen,     // Heiken ashi (better formula) open
   pr_habhigh,     // Heiken ashi (better formula) high
   pr_hablow,      // Heiken ashi (better formula) low
   pr_habmedian,   // Heiken ashi (better formula) median
   pr_habtypical,  // Heiken ashi (better formula) typical
   pr_habweighted, // Heiken ashi (better formula) weighted
   pr_habaverage,  // Heiken ashi (better formula) average
   pr_habmedianb,  // Heiken ashi (better formula) median body
   pr_habtbiased,  // Heiken ashi (better formula) trend biased price
   pr_habtbiased2  // Heiken ashi (better formula) trend biased (extreme) price
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "=== Price & FTLM Settings ==="
input enPrices           Price                 = pr_close;          // Price to use for FTLM calculation
input bool               ShowEdgeLine          = true;              // Show line covering histogram edges?
input int                MaxBarsToCalculate    = 1000;              // Max bars to calculate (0 = all bars)

input group "=== Arrows Settings ==="
input bool               arrowsVisible         = true;              // Arrows visible?
input bool               arrowsOnFirst         = true;              // Arrows shift? (true=bar open, false=shift to bar close)
input string             arrowsIdentifier      = "FTLM_arrows";     // Unique ID for arrows
input double             arrowsUpperGap        = 1.0;               // Upper arrow gap
input double             arrowsLowerGap        = 1.0;               // Lower arrow gap
input color              arrowsUpColor         = clrDarkGreen;      // Up arrow color
input color              arrowsDnColor         = clrCrimson;        // Down arrow color
input int                arrowsUpCode          = 221;               // Up arrow code
input int                arrowsDnCode          = 222;               // Down arrow code
input int                ArrowSize             = 1;                 // Arrow size

input group "=== Alerts Settings ==="
input bool               alertsOn              = false;             // Turn alerts on?
input bool               alertsOnCurrent       = false;             // Alerts on still opened bar?
input bool               alertsMessage         = false;             // Alerts should display message?
input bool               alertsSound           = false;             // Alerts should play a sound?
input bool               alertsNotify          = false;             // Alerts should send a notification?
input bool               alertsEmail           = false;             // Alerts should send an email?
input string             soundFile             = "alert2.wav";      // Sound file

//Forex-Station button template start41; copy and paste
input group "=== Toggle Button Settings ==="
input string             button_note1          = "------------------------------";
input int                btn_Subwindow         = 0;                 // What window to put the button on (<0 = subwindow)
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER; // chart btn_corner for anchoring
input string             btn_Font              = "Arial";           // button font name
input int                btn_FontSize          = 9;                 // btn__font size
input color              btn_border_color      = clrBlack;          // border color the button
input int                button_x              = 20;                // Horizontal location
input int                button_y              = 115;               // Vertical location
input int                btn_Width             = 45;                // btn__width
input int                btn_Height            = 20;                // btn__height
input string             UniqueButtonID        = "FTLM_BT";         // Unique Button ID
input string             button_note2          = "------------------------------";
//Forex-Station button template end41; copy and paste

//+------------------------------------------------------------------+
//| Indicator Buffers                                                |
//+------------------------------------------------------------------+
double FTLMBuffer[];        // Data buffer (Plot 0)
double FTLMColorBuffer[];   // Color index buffer (Plot 0)
double FTLMLineBuffer[];    // Line data buffer (Plot 1: covers edges of histogram)
double FTLMSlopeBuffer[];   // Calculation buffer: Slope (+1 or -1)
double PricesBuffer[];      // Calculation buffer: 33-price series

//+------------------------------------------------------------------+
//| Button & State Tracking Variables                                |
//+------------------------------------------------------------------+
bool     currentArrowsState;
bool     currentHistogramState;
string   arrowsBtnName;
string   histogramBtnName;
string   gvArrows;
string   gvHisto;
string   IndicatorName;
datetime lastProcessedBar   = 0;
bool     forceRecalculation = false;

//+------------------------------------------------------------------+
//| FTLM FIR Filter Coefficients                                     |
//+------------------------------------------------------------------+
const double coeff1[39] =
{
   +0.4360409450, +0.3658689069, +0.2460452079, +0.1104506886, -0.0054034585,
   -0.0760367731, -0.0933058722, -0.0670110374, -0.0190795053, +0.0259609206,
   +0.0502044896, +0.0477818607, +0.0249252327, -0.0047706151, -0.0272432537,
   -0.0338917071, -0.0244141482, -0.0055774838, +0.0128149838, +0.0226522218,
   +0.0208778257, +0.0100299086, -0.0036771622, -0.0136744850, -0.0160483392,
   -0.0108597376, -0.0016060704, +0.0069480557, +0.0110573605, +0.0095711419,
   +0.0040444064, -0.0023824623, -0.0067093714, -0.0072003400, -0.0047717710,
   +0.0005541115, +0.0007860160, +0.0130129076, +0.0040364019
};

const double coeff2[44] =
{
   -0.0025097319, +0.0513007762, +0.1142800493, +0.1699342860, +0.2025269304,
   +0.2025269304, +0.1699342860, +0.1142800493, +0.0513007762, -0.0025097319,
   -0.0353166244, -0.0433375629, -0.0311244617, -0.0088618137, +0.0120580088,
   +0.0233183633, +0.0221931304, +0.0115769653, -0.0022157966, -0.0126536111,
   -0.0157416029, -0.0113395830, -0.0025905610, +0.0059521459, +0.0105212252,
   +0.0096970755, +0.0046585685, -0.0017079230, -0.0063513565, -0.0074539350,
   -0.0050439973, -0.0007459678, +0.0032271474, +0.0051357867, +0.0044454862,
   +0.0018784961, -0.0011065767, -0.0031162862, -0.0033443253, -0.0022163335,
   +0.0002573669, +0.0003650790, +0.0060440751, +0.0018747783
};

//+------------------------------------------------------------------+
//| Price Engine: 33 Price calculations                              |
//+------------------------------------------------------------------+
#define _prHABF(_prtype) (_prtype>=pr_habclose && _prtype<=pr_habtbiased2)
#define priceInstances 1
double workHa[][priceInstances*4];

double getPrice(int tprice, const double &open[], const double &close[], const double &high[], const double &low[], int i, int _bars, int instanceNo=0)
{
   if(tprice >= pr_haclose)
   {
      if(ArrayRange(workHa, 0) != _bars)
         ArrayResize(workHa, _bars);
      instanceNo *= 4;

      double haOpen;
      if(i > 0)
      {
         if(workHa[i-1][instanceNo+2] != 0 || workHa[i-1][instanceNo+3] != 0)
            haOpen = (workHa[i-1][instanceNo+2] + workHa[i-1][instanceNo+3]) / 2.0;
         else
            haOpen = (open[i-1] + close[i-1]) / 2.0;
      }
      else
         haOpen = (open[i] + close[i]) / 2.0;

      double haClose = (open[i] + high[i] + low[i] + close[i]) * 0.25;
      if(_prHABF(tprice))
      {
         if(high[i] != low[i])
            haClose = (open[i] + close[i]) / 2.0 + (((close[i] - open[i]) / (high[i] - low[i])) * MathAbs((close[i] - open[i]) / 2.0));
         else
            haClose = (open[i] + close[i]) / 2.0;
      }
      double haHigh = MathMax(high[i], MathMax(haOpen, haClose));
      double haLow  = MathMin(low[i],  MathMin(haOpen, haClose));

      if(haOpen < haClose)
      {
         workHa[i][instanceNo+0] = haLow;
         workHa[i][instanceNo+1] = haHigh;
      }
      else
      {
         workHa[i][instanceNo+0] = haHigh;
         workHa[i][instanceNo+1] = haLow;
      }
      workHa[i][instanceNo+2] = haOpen;
      workHa[i][instanceNo+3] = haClose;

      switch(tprice)
      {
         case pr_haclose:
         case pr_habclose:    return(haClose);
         case pr_haopen:
         case pr_habopen:     return(haOpen);
         case pr_hahigh:
         case pr_habhigh:     return(haHigh);
         case pr_halow:
         case pr_hablow:      return(haLow);
         case pr_hamedian:
         case pr_habmedian:   return((haHigh + haLow) / 2.0);
         case pr_hamedianb:
         case pr_habmedianb:  return((haOpen + haClose) / 2.0);
         case pr_hatypical:
         case pr_habtypical:  return((haHigh + haLow + haClose) / 3.0);
         case pr_haweighted:
         case pr_habweighted: return((haHigh + haLow + haClose + haClose) / 4.0);
         case pr_haaverage:
         case pr_habaverage:  return((haHigh + haLow + haClose + haOpen) / 4.0);
         case pr_hatbiased:
         case pr_habtbiased:
            if(haClose > haOpen) return((haHigh + haClose) / 2.0);
            else                 return((haLow  + haClose) / 2.0);
         case pr_hatbiased2:
         case pr_habtbiased2:
            if(haClose > haOpen) return(haHigh);
            if(haClose < haOpen) return(haLow);
            return(haClose);
      }
   }

   switch(tprice)
   {
      case pr_close:     return(close[i]);
      case pr_open:      return(open[i]);
      case pr_high:      return(high[i]);
      case pr_low:       return(low[i]);
      case pr_median:    return((high[i] + low[i]) / 2.0);
      case pr_medianb:   return((open[i] + close[i]) / 2.0);
      case pr_typical:   return((high[i] + low[i] + close[i]) / 3.0);
      case pr_weighted:  return((high[i] + low[i] + close[i] + close[i]) / 4.0);
      case pr_average:   return((high[i] + low[i] + close[i] + open[i]) / 4.0);
      case pr_tbiased:
         if(close[i] > open[i]) return((high[i] + close[i]) / 2.0);
         else                   return((low[i]  + close[i]) / 2.0);
      case pr_tbiased2:
         if(close[i] > open[i]) return(high[i]);
         if(close[i] < open[i]) return(low[i]);
         return(close[i]);
   }
   return(0.0);
}

//+------------------------------------------------------------------+
//| Get Timeframe Name String                                        |
//+------------------------------------------------------------------+
string GetStringTimeframe(ENUM_TIMEFRAMES timeframe)
{
   return(StringSubstr(EnumToString(timeframe), 7));
}

//+------------------------------------------------------------------+
//| Arrow Drawing & Management                                       |
//+------------------------------------------------------------------+
void CleanArrows()
{
   string lookFor = arrowsIdentifier + ":";
   ObjectsDeleteAll(0, lookFor);
   ObjectsDeleteAll(0, "FTLM_arrows:");
   ObjectsDeleteAll(0, "FTLM arrows:");
   ObjectsDeleteAll(0, "mt5FTLMarrow:");

   for(int i = ObjectsTotal(0, -1, -1) - 1; i >= 0; i--)
   {
      string objectName = ObjectName(0, i, -1, -1);
      if(StringFind(objectName, lookFor) == 0 ||
         StringFind(objectName, "FTLM_arrows:") == 0 ||
         StringFind(objectName, "FTLM arrows:") == 0 ||
         StringFind(objectName, "mt5FTLMarrow:") == 0)
      {
         ObjectDelete(0, objectName);
      }
   }
}

void DrawArrow(int bar, color arrowColor, int arrowCode, int size, bool isUp, const double &high[], const double &low[], const datetime &time[])
{
   if(bar < 0 || bar >= ArraySize(high)) return;

   string name = arrowsIdentifier + ":" + (string)time[bar];

   double avg_range = 0.0;
   int count = 0;
   for(int k = 0; k < 20 && bar - k >= 0; k++)
   {
      avg_range += (high[bar - k] - low[bar - k]);
      count++;
   }
   double gap = (count > 0) ? (avg_range / count) : (high[bar] - low[bar]);
   if(gap <= 0.0) gap = 10 * _Point;

   double priceLevel;
   ENUM_ARROW_ANCHOR anchor;
   if(isUp)
   {
      priceLevel = low[bar] - arrowsLowerGap * gap;
      anchor     = ANCHOR_TOP;
   }
   else
   {
      priceLevel = high[bar] + arrowsUpperGap * gap;
      anchor     = ANCHOR_BOTTOM;
   }

   int shift_sec = 0;
   if(!arrowsOnFirst)
      shift_sec = PeriodSeconds(_Period) - 1;

   datetime arrow_time = time[bar] + shift_sec;

   if(!ObjectCreate(0, name, OBJ_ARROW, 0, arrow_time, priceLevel))
   {
      ObjectMove(0, name, 0, arrow_time, priceLevel);
   }

   ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
   ObjectSetInteger(0, name, OBJPROP_COLOR,     arrowColor);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR,    anchor);
   ObjectSetInteger(0, name, OBJPROP_WIDTH,     size);
   ObjectSetInteger(0, name, OBJPROP_BACK,      false);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE,false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN,    true);
   ObjectSetInteger(0, name, OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS);
}

//+------------------------------------------------------------------+
//| Alerts System                                                    |
//+------------------------------------------------------------------+
void DoAlert(string alertAction, datetime barTime)
{
   static string   previousAlert = "";
   static datetime previousTime  = 0;

   if(previousAlert != alertAction || previousTime != barTime)
   {
      previousAlert = alertAction;
      previousTime  = barTime;

      string message = StringFormat("%s %s at %s FTLM %s", Symbol(), GetStringTimeframe(_Period), TimeToString(TimeLocal(), TIME_SECONDS), alertAction);

      if(alertsMessage) Alert(message);
      if(alertsNotify)  SendNotification(message);
      if(alertsEmail)   SendMail(Symbol() + " FTLM Alert", message);
      if(alertsSound)   PlaySound(soundFile);
   }
}

//+------------------------------------------------------------------+
//| Button State Persistence Functions                               |
//+------------------------------------------------------------------+
void SaveToggleState()
{
   if(gvArrows != "")
      GlobalVariableSet(gvArrows, currentArrowsState ? 1.0 : 0.0);
   if(gvHisto != "")
      GlobalVariableSet(gvHisto, currentHistogramState ? 1.0 : 0.0);
}

void LoadToggleState()
{
   if(gvArrows != "" && GlobalVariableCheck(gvArrows))
      currentArrowsState = (GlobalVariableGet(gvArrows) > 0.5);
   else
      currentArrowsState = arrowsVisible;

   if(gvHisto != "" && GlobalVariableCheck(gvHisto))
      currentHistogramState = (GlobalVariableGet(gvHisto) > 0.5);
   else
      currentHistogramState = true;
}

//+------------------------------------------------------------------+
//| Toggle Functions                                                 |
//+------------------------------------------------------------------+
void ToggleArrows()
{
   currentArrowsState = !currentArrowsState;
   SaveToggleState();
   UpdateButtons();

   CleanArrows();

   if(!currentArrowsState)
   {
   }
   else
   {
      forceRecalculation = true;
      lastProcessedBar   = 0;

      // Draw arrows on chart immediately without waiting for tick
      int total = ArraySize(FTLMSlopeBuffer);
      if(total > 45)
      {
         int toCopy = MathMin(1000, total);
         MqlRates rates[];
         ArraySetAsSeries(rates, false);
         int copied = CopyRates(_Symbol, _Period, 0, toCopy, rates);
         if(copied > 45)
         {
            double highs[], lows[];
            datetime times[];
            ArrayResize(highs, copied);
            ArrayResize(lows, copied);
            ArrayResize(times, copied);
            for(int k = 0; k < copied; k++)
            {
               highs[k] = rates[k].high;
               lows[k]  = rates[k].low;
               times[k] = rates[k].time;
            }

            int start_idx = MathMax(45, total - copied);
            for(int b = start_idx; b < total - 1; b++)
            {
               int r_idx = copied - (total - b);
               if(r_idx >= 1 && r_idx < copied)
               {
                  if(FTLMSlopeBuffer[b] != FTLMSlopeBuffer[b - 1])
                  {
                     if(FTLMSlopeBuffer[b] == 1.0)
                        DrawArrow(r_idx, arrowsUpColor, arrowsUpCode, ArrowSize, true, highs, lows, times);
                     else if(FTLMSlopeBuffer[b] == -1.0)
                        DrawArrow(r_idx, arrowsDnColor, arrowsDnCode, ArrowSize, false, highs, lows, times);
                  }
               }
            }
         }
      }
   }
   ChartRedraw(0);
}

void ToggleHistogram()
{
   currentHistogramState = !currentHistogramState;
   SaveToggleState();
   UpdateButtons();

   if(currentHistogramState)
   {
      PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_HISTOGRAM);
      PlotIndexSetInteger(0, PLOT_SHOW_DATA, true);
      PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);

      if(ShowEdgeLine)
      {
         PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE);
         PlotIndexSetInteger(1, PLOT_SHOW_DATA, true);
         PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 2);
      }
      else
      {
         PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
         PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
      }
   }
   else
   {
      PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(0, PLOT_SHOW_DATA, false);

      PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
   }

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Button UI Creation and Updates                                   |
//+------------------------------------------------------------------+
void CreateToggleButtons()
{
   int target_window = btn_Subwindow;
   if(target_window < 0)
   {
      target_window = ChartWindowFind(0, IndicatorName);
      if(target_window < 0) target_window = 0;
   }

   if(ObjectFind(0, histogramBtnName) >= 0)
      ObjectDelete(0, histogramBtnName);
   if(ObjectFind(0, arrowsBtnName) >= 0)
      ObjectDelete(0, arrowsBtnName);

   // 1. Histogram Button (HISTO)
   ObjectCreate(0, histogramBtnName, OBJ_BUTTON, target_window, 0, 0);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_XDISTANCE,    button_x);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_YDISTANCE,    button_y);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_YSIZE,        btn_Height);
   ObjectSetString (0, histogramBtnName, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_COLOR,        clrWhite);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_BGCOLOR,      currentHistogramState ? clrGreen : clrRed);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
   ObjectSetString (0, histogramBtnName, OBJPROP_TEXT,         "HISTO");
   ObjectSetInteger(0, histogramBtnName, OBJPROP_SELECTABLE,   false);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_HIDDEN,       true);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
   ObjectSetInteger(0, histogramBtnName, OBJPROP_ZORDER,       100);

   // 2. Arrows Button (ARR)
   ObjectCreate(0, arrowsBtnName, OBJ_BUTTON, target_window, 0, 0);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_XDISTANCE,    button_x + btn_Width + 2);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_YDISTANCE,    button_y);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_YSIZE,        btn_Height);
   ObjectSetString (0, arrowsBtnName, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_COLOR,        clrWhite);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_BGCOLOR,      currentArrowsState ? clrGreen : clrRed);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
   ObjectSetString (0, arrowsBtnName, OBJPROP_TEXT,         "ARR");
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_SELECTABLE,   false);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_HIDDEN,       true);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
   ObjectSetInteger(0, arrowsBtnName, OBJPROP_ZORDER,       100);

   UpdateButtons();
}

void UpdateButtons()
{
   if(ObjectFind(0, histogramBtnName) >= 0)
   {
      ObjectSetInteger(0, histogramBtnName, OBJPROP_BGCOLOR, currentHistogramState ? clrGreen : clrRed);
      ObjectSetInteger(0, histogramBtnName, OBJPROP_STATE,   currentHistogramState);
      ObjectSetInteger(0, histogramBtnName, OBJPROP_ZORDER,  100);
   }

   if(ObjectFind(0, arrowsBtnName) >= 0)
   {
      ObjectSetInteger(0, arrowsBtnName, OBJPROP_BGCOLOR, currentArrowsState ? clrGreen : clrRed);
      ObjectSetInteger(0, arrowsBtnName, OBJPROP_STATE,   currentArrowsState);
      ObjectSetInteger(0, arrowsBtnName, OBJPROP_ZORDER,  100);
   }

   ChartRedraw(0);
}

void BringButtonsToFront()
{
   int total = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = 0; i < total; i++)
   {
      string name = ObjectName(0, i, -1, OBJ_BUTTON);
      if(name != "")
      {
         if(ObjectGetInteger(0, name, OBJPROP_ZORDER) < 100)
            ObjectSetInteger(0, name, OBJPROP_ZORDER, 100);
      }
   }
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Bind indicator buffers
   SetIndexBuffer(0, FTLMBuffer,      INDICATOR_DATA);
   SetIndexBuffer(1, FTLMColorBuffer, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, FTLMLineBuffer,  INDICATOR_DATA);
   SetIndexBuffer(3, FTLMSlopeBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(4, PricesBuffer,    INDICATOR_CALCULATIONS);

   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 44);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 44);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   //--- Set up unique IDs and global variables
   arrowsBtnName    = "FTLM_" + UniqueButtonID + "_ARR";
   histogramBtnName = "FTLM_" + UniqueButtonID + "_HISTO";
   gvArrows         = "FTLM_" + UniqueButtonID + "_" + IntegerToString(ChartID()) + "_ARR";
   gvHisto          = "FTLM_" + UniqueButtonID + "_" + IntegerToString(ChartID()) + "_HISTO";

   IndicatorName    = "[" + Symbol() + "] [" + GetStringTimeframe(_Period) + "] FTLM";
   IndicatorSetString(INDICATOR_SHORTNAME, IndicatorName);
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

   //--- Clean up phantom buttons
   int total_objs = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = total_objs - 1; i >= 0; i--)
   {
      string bName = ObjectName(0, i, -1, OBJ_BUTTON);
      if(StringFind(bName, UniqueButtonID) >= 0)
      {
         if(bName != arrowsBtnName && bName != histogramBtnName)
            ObjectDelete(0, bName);
      }
   }

   //--- If parameters dialog closed, reset persistent GV
   bool isParamChange = (UninitializeReason() == REASON_PARAMETERS);
   if(isParamChange)
   {
      if(gvArrows != "") GlobalVariableDel(gvArrows);
      if(gvHisto  != "") GlobalVariableDel(gvHisto);
   }

   LoadToggleState();

   if(currentHistogramState)
   {
      PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_HISTOGRAM);
      PlotIndexSetInteger(0, PLOT_SHOW_DATA, true);
      PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);

      if(ShowEdgeLine)
      {
         PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE);
         PlotIndexSetInteger(1, PLOT_SHOW_DATA, true);
         PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 2);
      }
      else
      {
         PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
         PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
      }
   }
   else
   {
      PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(0, PLOT_SHOW_DATA, false);

      PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
   }

   lastProcessedBar   = 0;
   forceRecalculation = true;
   CleanArrows();

   CreateToggleButtons();
   BringButtonsToFront();

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   CleanArrows();

   if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS)
   {
      ObjectDelete(0, arrowsBtnName);
      ObjectDelete(0, histogramBtnName);
      if(gvArrows != "") GlobalVariableDel(gvArrows);
      if(gvHisto  != "") GlobalVariableDel(gvHisto);
   }
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Custom indicator chart's event handler                           |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == arrowsBtnName)
      {
         ToggleArrows();
         return;
      }
      else if(sparam == histogramBtnName)
      {
         ToggleHistogram();
         return;
      }
   }

   if(id >= CHARTEVENT_CUSTOM)
      return;

   if(id == CHARTEVENT_CHART_CHANGE)
   {
      BringButtonsToFront();
   }
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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 < 45)
      return(0);

   if(prev_calculated <= 0)
   {
      CreateToggleButtons();
      BringButtonsToFront();
   }

   int start;
   if(prev_calculated == 0)
   {
      start = 0;
      if(MaxBarsToCalculate > 0 && rates_total > MaxBarsToCalculate)
         start = rates_total - MaxBarsToCalculate;
      if(start < 0) start = 0;

      ArrayInitialize(FTLMBuffer, EMPTY_VALUE);
      ArrayInitialize(FTLMColorBuffer, 2);
      ArrayInitialize(FTLMLineBuffer, EMPTY_VALUE);
      ArrayInitialize(FTLMSlopeBuffer, 0);
      ArrayInitialize(PricesBuffer, 0);

      CleanArrows();
      lastProcessedBar = 0;
   }
   else
   {
      start = prev_calculated - 1;
   }

   if(start >= rates_total)
      start = rates_total - 1;

   // 1. Calculate PricesBuffer using selected price type (33 prices supported)
   int price_start = (prev_calculated == 0) ? MathMax(0, start - 50) : start;
   for(int bar = price_start; bar < rates_total && !IsStopped(); bar++)
   {
      PricesBuffer[bar] = getPrice((int)Price, open, close, high, low, bar, rates_total);
   }

   // 2. Calculate FTLM and Slope directly via FIR Filter coefficients
   int ftlm_start = MathMax(start, 43);
   for(int bar = ftlm_start; bar < rates_total && !IsStopped(); bar++)
   {
      double value1 = 0.0;
      for(int k = 0; k < 39; k++)
         value1 += coeff1[k] * PricesBuffer[bar - k];

      double value2 = 0.0;
      for(int k = 0; k < 44; k++)
         value2 += coeff2[k] * PricesBuffer[bar - k];

      double ftlm = value1 - value2;
      FTLMBuffer[bar]     = ftlm;
      FTLMLineBuffer[bar] = ftlm;

      if(bar > 43)
      {
         if(FTLMBuffer[bar] > FTLMBuffer[bar - 1])
            FTLMSlopeBuffer[bar] = 1.0;
         else if(FTLMBuffer[bar] < FTLMBuffer[bar - 1])
            FTLMSlopeBuffer[bar] = -1.0;
         else
            FTLMSlopeBuffer[bar] = FTLMSlopeBuffer[bar - 1];
      }
      else
      {
         FTLMSlopeBuffer[bar] = 0.0;
      }

      // 5-Color Histogram Mapping:
      // 0 = clrRed         (FTLM < 0, sloping down / falling)
      // 1 = clrMagenta     (FTLM < 0, sloping up / rising)
      // 2 = clrGray        (FTLM == 0 / neutral)
      // 3 = clrTeal        (FTLM > 0, sloping down / falling)
      // 4 = clrSpringGreen (FTLM > 0, sloping up / rising)
      if(FTLMBuffer[bar] > 0)
      {
         FTLMColorBuffer[bar] = (FTLMSlopeBuffer[bar] == 1.0) ? 4.0 : 3.0;
      }
      else if(FTLMBuffer[bar] < 0)
      {
         FTLMColorBuffer[bar] = (FTLMSlopeBuffer[bar] == 1.0) ? 1.0 : 0.0;
      }
      else
      {
         FTLMColorBuffer[bar] = 2.0;
      }
   }

   // 3. Arrow Logic on Slope Changes
   if(currentArrowsState)
   {
      if(prev_calculated == 0 || forceRecalculation || lastProcessedBar == 0)
      {
         int arrow_start = MathMax(45, (MaxBarsToCalculate > 0 && rates_total > MaxBarsToCalculate) ? rates_total - MaxBarsToCalculate : 45);
         for(int bar = arrow_start; bar < rates_total - 1; bar++)
         {
            if(FTLMSlopeBuffer[bar] != FTLMSlopeBuffer[bar - 1])
            {
               if(FTLMSlopeBuffer[bar] == 1.0)
                  DrawArrow(bar, arrowsUpColor, arrowsUpCode, ArrowSize, true, high, low, time);
               else if(FTLMSlopeBuffer[bar] == -1.0)
                  DrawArrow(bar, arrowsDnColor, arrowsDnCode, ArrowSize, false, high, low, time);
            }
         }
         lastProcessedBar = time[rates_total - 2];
         forceRecalculation = false;
      }
      else
      {
         int checkBar = rates_total - 2;
         if(checkBar >= 45 && time[checkBar] != lastProcessedBar)
         {
            if(FTLMSlopeBuffer[checkBar] != FTLMSlopeBuffer[checkBar - 1])
            {
               if(FTLMSlopeBuffer[checkBar] == 1.0)
                  DrawArrow(checkBar, arrowsUpColor, arrowsUpCode, ArrowSize, true, high, low, time);
               else if(FTLMSlopeBuffer[checkBar] == -1.0)
                  DrawArrow(checkBar, arrowsDnColor, arrowsDnCode, ArrowSize, false, high, low, time);
            }
            lastProcessedBar = time[checkBar];
         }
      }
   }

   // 4. Alert Logic on Slope Changes
   if(alertsOn)
   {
      int whichBar = rates_total - 2;
      if(alertsOnCurrent) whichBar = rates_total - 1;

      if(whichBar >= 45 && FTLMSlopeBuffer[whichBar] != FTLMSlopeBuffer[whichBar - 1])
      {
         if(FTLMSlopeBuffer[whichBar] == 1.0)
            DoAlert("sloping up", time[whichBar]);
         else if(FTLMSlopeBuffer[whichBar] == -1.0)
            DoAlert("sloping down", time[whichBar]);
      }
   }

   return(rates_total);
}
//+------------------------------------------------------------------+