//+------------------------------------------------------------------+
//|                                      Pivot Trend Detector BT.mq5 |
//|                         Copyright © 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright   "Copyright © 2025, Allan Munene Mutiiria"
#property link        "https://www.mql5.com/en/articles/20610"
#property version     "1.00"
#property description "Pivot Trend Detector with On/Off Toggle Button"
#property strict

#property indicator_chart_window
#property indicator_buffers 8
#property indicator_plots   4

#property indicator_label1 "PTD slow line up"
#property indicator_type1  DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2

#property indicator_label2 "PTD slow line down"
#property indicator_type2  DRAW_LINE
#property indicator_color2 clrCrimson
#property indicator_width2 2

#property indicator_label3 "PTD fast line"
#property indicator_type3  DRAW_COLOR_LINE
#property indicator_color3 clrDodgerBlue,clrCrimson
#property indicator_style3 STYLE_DOT

#property indicator_label4 "PTD trend start"
#property indicator_type4  DRAW_COLOR_ARROW
#property indicator_color4 clrDodgerBlue,clrCrimson
#property indicator_width4 2

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "=== Pivot Trend Detector Settings ==="
input int    fastPeriod       = 5;                                // Fast period
input int    slowPeriod       = 10;                               // Slow period
input color  upColor          = clrDodgerBlue;                    // Up trend color
input color  downColor        = clrCrimson;                       // Down trend color
input int    fillOpacity      = 128;                              // Fill opacity (0-255)
input int    arrowCode        = 77;                               // Arrow code for trend start
input bool   showExtensions   = true;                             // Show line extensions
input bool   enableFilling    = true;                             // Enable canvas fill (disable for speed)
input int    extendBars       = 1;                                // Extension bars to protrude lines/fill

//Forex-Station copy & paste code; Button code start 11
input group "=== Toggle Button Settings ==="
input string             button_note1          = "------------------------------"; // ------------------------------
input int                btn_Subwindow         = 0;                  // What window to put the button on
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER;  // chart btn_corner for anchoring
input string             btn_text              = "Trend: ON";        // Display label
input string             btn_Font              = "Arial";            // button font name
input int                btn_FontSize          = 9;                  // btn__font size
input color              btn_text_ON_color     = clrLime;            // ON color when the button is turned on
input color              btn_text_OFF_color    = clrRed;             // OFF color when the button is turned off
input color              btn_background_color  = clrDimGray;         // background color of the button
input color              btn_border_color      = clrBlack;           // border color the button
input int                button_x              = 20;                 // Horizontal location
input int                button_y              = 20;                 // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "PivotTrend_BT";    // Unique Button ID                      
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas obj_Canvas;                                                   //--- Canvas object

//--- indicator buffers
double slowLineUpBuffer[];
double slowLineDownBuffer[];
double fastLineBuffer[];
double fastLineColorBuffer[];
double trendArrowBuffer[];
double trendArrowColorBuffer[];
double trendBuffer[];
double slowLineBuffer[];

//--- chart properties
int    currentChartWidth    = 0;                                      //--- Current chart width
int    currentChartHeight   = 0;                                      //--- Current chart height
int    currentChartScale    = 0;                                      //--- Current chart scale
int    firstVisibleBarIndex = 0;                                      //--- First visible bar index
int    visibleBarsCount     = 0;                                      //--- Visible bars count
double minPrice             = 0.0;                                    //--- Minimum price
double maxPrice             = 0.0;                                    //--- Maximum price

//--- optimization flags
static datetime lastRedrawTime = 0;                                   //--- Last redraw time
static double   previousTrend  = -1;                                  //--- Previous trend
string objectPrefix = "PTD_";                                         //--- Object prefix
string canvasName   = "";                                             //--- Canvas object name

//+------------------------------------------------------------------+
//| Button State Tracking Variables                                  |
//+------------------------------------------------------------------+
bool    IndicatorEnabled = true;
bool    IndicatorHidden  = false; // True when indicator is turned OFF via toggle button
string  ToggleButtonName;
string  g_gvHidden;
string  g_prefix;
string  IndicatorName, IndicatorObjPrefix, buttonId;

//+------------------------------------------------------------------+
//| Forward Declarations                                             |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled);
void   CreateToggleButton();
void   UpdateToggleButton();
void   SavePlotStyles();
void   RestorePlotStyles();
void   ApplyPlotsStyle();
void   ToggleIndicatorVisibility();
void   BringButtonsToFront();
void   Redraw(void);
int    BarWidth(int chartScale);
int    ShiftToX(int barShift);
int    PriceToY(double price);
bool   drawRightPrice(string objName, datetime lineTime, double linePrice, color lineColor, ENUM_LINE_STYLE lineStyle = STYLE_SOLID);
void   DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0);

//+------------------------------------------------------------------+
//| Save current plot widths & styles from chart to GlobalVariables  |
//+------------------------------------------------------------------+
void SavePlotStyles()
{
   if(g_prefix == "") return;
   for(int k = 0; k < 4; k++)
   {
      int w = (int)PlotIndexGetInteger(k, PLOT_LINE_WIDTH);
      int s = (int)PlotIndexGetInteger(k, PLOT_LINE_STYLE);
      if(w > 0)
         GlobalVariableSet(g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k), (double)w);
      if(s >= 0)
         GlobalVariableSet(g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k), (double)s);
   }
}

//+------------------------------------------------------------------+
//| Restore plot widths & styles after timeframe switch              |
//+------------------------------------------------------------------+
void RestorePlotStyles()
{
   if(g_prefix == "") return;
   for(int k = 0; k < 4; k++)
   {
      string gvW = g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k);
      string gvS = g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k);

      if(GlobalVariableCheck(gvW))
      {
         int w = (int)GlobalVariableGet(gvW);
         if(w > 0) PlotIndexSetInteger(k, PLOT_LINE_WIDTH, w);
      }
      if(GlobalVariableCheck(gvS) && k < 3)
      {
         int s = (int)GlobalVariableGet(gvS);
         if(s >= 0) PlotIndexSetInteger(k, PLOT_LINE_STYLE, s);
      }
   }
}

//+------------------------------------------------------------------+
//| Helper to resolve button text based on state                     |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled)
{
   string t = btn_text;
   if(isEnabled)
   {
      if(StringFind(t, "OFF") >= 0) StringReplace(t, "OFF", "ON");
      else if(StringFind(t, "Off") >= 0) StringReplace(t, "Off", "On");
      else if(StringFind(t, "off") >= 0) StringReplace(t, "off", "on");
   }
   else
   {
      if(StringFind(t, "ON") >= 0) StringReplace(t, "ON", "OFF");
      else if(StringFind(t, "On") >= 0) StringReplace(t, "On", "Off");
      else if(StringFind(t, "on") >= 0) StringReplace(t, "on", "off");
   }
   return t;
}

//+------------------------------------------------------------------+
//| Create Toggle Button                                             |
//+------------------------------------------------------------------+
void CreateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0)
   {
      int y_pos = button_y;
      int obj_total = ObjectsTotal(0, -1, OBJ_BUTTON);
      bool collision = true;
      while(collision)
      {
         collision = false;
         for(int i = 0; i < obj_total; i++)
         {
            string name = ObjectName(0, i, -1, OBJ_BUTTON);
            if(name == buttonId) continue;
            if((ENUM_BASE_CORNER)ObjectGetInteger(0, name, OBJPROP_CORNER) != btn_corner) continue;
            if(ObjectGetInteger(0, name, OBJPROP_XDISTANCE) == button_x &&
               ObjectGetInteger(0, name, OBJPROP_YDISTANCE) == y_pos)
            {
               y_pos += btn_Height + 4;
               collision = true;
               break;
            }
         }
      }

      ObjectCreate(0, buttonId, OBJ_BUTTON, btn_Subwindow, 0, 0);

      ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
      ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
      ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE,    y_pos);
      ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
      ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
      ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
      ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
      ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
      ObjectSetInteger(0, buttonId, OBJPROP_SELECTABLE,   false);
      ObjectSetInteger(0, buttonId, OBJPROP_HIDDEN,       true);
      ObjectSetInteger(0, buttonId, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
      ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);
   }

   UpdateToggleButton();
}

//+------------------------------------------------------------------+
//| Update Toggle Button Appearance                                  |
//+------------------------------------------------------------------+
void UpdateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0) return;

   IndicatorEnabled = !IndicatorHidden;

   string labelText  = GetButtonText(IndicatorEnabled);
   color  textColor  = IndicatorEnabled ? btn_text_ON_color : btn_text_OFF_color;

   ObjectSetString (0, buttonId, OBJPROP_TEXT,         labelText);
   ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, buttonId, OBJPROP_COLOR,        textColor);
   ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
   ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
   ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
   ObjectSetInteger(0, buttonId, OBJPROP_STATE,        IndicatorEnabled);
   ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Apply Plot Styles based on Button Toggle State                   |
//+------------------------------------------------------------------+
void ApplyPlotsStyle()
{
   bool showIndicator = !IndicatorHidden;
   int shift = showExtensions ? extendBars : 0;

   // Plot 0: PTD slow line up
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, showIndicator ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetString (0, PLOT_LABEL, showIndicator ? "PTD slow line up" : "");
   PlotIndexSetInteger(0, PLOT_SHOW_DATA, showIndicator);
   PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, slowPeriod);
   PlotIndexSetInteger(0, PLOT_SHIFT, shift);
   if(showIndicator)
      PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, upColor);
   else
      PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, clrNONE);

   // Plot 1: PTD slow line down
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, showIndicator ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetString (1, PLOT_LABEL, showIndicator ? "PTD slow line down" : "");
   PlotIndexSetInteger(1, PLOT_SHOW_DATA, showIndicator);
   PlotIndexSetDouble (1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, slowPeriod);
   PlotIndexSetInteger(1, PLOT_SHIFT, shift);
   if(showIndicator)
      PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, downColor);
   else
      PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, clrNONE);

   // Plot 2: PTD fast line
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, showIndicator ? DRAW_COLOR_LINE : DRAW_NONE);
   PlotIndexSetString (2, PLOT_LABEL, showIndicator ? "PTD fast line" : "");
   PlotIndexSetInteger(2, PLOT_SHOW_DATA, showIndicator);
   PlotIndexSetDouble (2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, fastPeriod);
   PlotIndexSetInteger(2, PLOT_SHIFT, shift);
   if(showIndicator)
   {
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, upColor);
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, downColor);
      PlotIndexSetInteger(2, PLOT_LINE_STYLE, STYLE_DOT);
   }
   else
   {
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, clrNONE);
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, clrNONE);
   }

   // Plot 3: PTD trend start
   PlotIndexSetInteger(3, PLOT_DRAW_TYPE, showIndicator ? DRAW_COLOR_ARROW : DRAW_NONE);
   PlotIndexSetInteger(3, PLOT_ARROW, arrowCode);
   PlotIndexSetString (3, PLOT_LABEL, showIndicator ? "PTD trend start" : "");
   PlotIndexSetInteger(3, PLOT_SHOW_DATA, showIndicator);
   PlotIndexSetDouble (3, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, slowPeriod);
   PlotIndexSetInteger(3, PLOT_SHIFT, 0);
   if(showIndicator)
   {
      PlotIndexSetInteger(3, PLOT_LINE_COLOR, 0, upColor);
      PlotIndexSetInteger(3, PLOT_LINE_COLOR, 1, downColor);
   }
   else
   {
      PlotIndexSetInteger(3, PLOT_LINE_COLOR, 0, clrNONE);
      PlotIndexSetInteger(3, PLOT_LINE_COLOR, 1, clrNONE);
   }
}

//+------------------------------------------------------------------+
//| Toggle Indicator Visibility                                      |
//+------------------------------------------------------------------+
void ToggleIndicatorVisibility()
{
   if(!IndicatorHidden)
      SavePlotStyles();

   IndicatorHidden  = !IndicatorHidden;
   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   ApplyPlotsStyle();
   if(!IndicatorHidden)
      RestorePlotStyles();

   UpdateToggleButton();

   if(IndicatorHidden)
   {
      // Clean up chart extension objects
      ObjectsDeleteAll(0, objectPrefix, 0, OBJ_ARROW_RIGHT_PRICE);
      // Clear canvas fill
      if(enableFilling)
      {
         obj_Canvas.Erase(0);
         obj_Canvas.Update();
      }
   }
   else
   {
      // Redraw canvas fill
      if(enableFilling)
      {
         Redraw();
      }
      // Redraw line extensions
      if(showExtensions)
      {
         int total = ArraySize(slowLineBuffer);
         if(total > 0 && ArraySize(fastLineBuffer) > 0 && ArraySize(trendBuffer) > 0)
         {
            int latestBarIndex = total - 1;
            double slowLineValue = slowLineBuffer[latestBarIndex];
            double fastLineValue = fastLineBuffer[latestBarIndex];
            double currentTrend  = trendBuffer[latestBarIndex];
            color lineColor = (currentTrend == 0.0) ? upColor : downColor;
            datetime currentBarTime = iTime(_Symbol, _Period, 0);
            long timeOffset = (long)extendBars * PeriodSeconds(_Period);
            datetime extensionTime = currentBarTime + (datetime)timeOffset;
            drawRightPrice(objectPrefix + "SLOW", extensionTime, slowLineValue, lineColor, STYLE_SOLID);
            drawRightPrice(objectPrefix + "FAST", extensionTime, fastLineValue, lineColor, STYLE_DOT);
         }
      }
   }

   BringButtonsToFront();
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Ensure all buttons on chart have higher ZORDER                   |
//+------------------------------------------------------------------+
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()
{
   //--- Set up unique IDs and global variables for state tracking
   IndicatorObjPrefix = "__" + btn_text + "__";
   buttonId           = IndicatorObjPrefix + UniqueButtonID;
   ToggleButtonName   = buttonId;
   IndicatorName      = "Pivot Trend Detector BT [" + UniqueButtonID + "]";
   g_prefix           = "PTD_BT_" + UniqueButtonID + "_";
   g_gvHidden         = g_prefix + IntegerToString(ChartID()) + "_Hidden";
   objectPrefix       = "PTD_" + UniqueButtonID + "_";

   // Clean up any old duplicate or 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 || StringFind(bName, "PivotTrend_BT") >= 0 || StringFind(bName, "PTD_BT") >= 0)
      {
         if(bName != buttonId)
            ObjectDelete(0, bName);
      }
   }

   // When parameters are changed by user in properties dialog, delete old global variables and respect inputs strictly
   bool isParamChange = (UninitializeReason() == REASON_PARAMETERS);
   if(isParamChange && g_gvHidden != "")
   {
      GlobalVariableDel(g_gvHidden);
      for(int k = 0; k < 4; k++)
      {
         GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k));
         GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k));
      }
   }

   // Restore toggle state across timeframe/symbol changes
   bool hasSavedState = false;
   bool savedHidden   = false;

   if(!isParamChange)
   {
      if(ObjectFind(0, buttonId) >= 0)
      {
         string btnTxt = ObjectGetString(0, buttonId, OBJPROP_TEXT);
         long   btnSt  = ObjectGetInteger(0, buttonId, OBJPROP_STATE);

         if(StringFind(btnTxt, "OFF") >= 0 || StringFind(btnTxt, "Off") >= 0 || btnSt == 0)
            savedHidden = true;
         else if(StringFind(btnTxt, "ON") >= 0 || StringFind(btnTxt, "On") >= 0 || btnSt != 0)
            savedHidden = false;
         hasSavedState = true;
      }

      if(!hasSavedState && GlobalVariableCheck(g_gvHidden))
      {
         savedHidden   = (GlobalVariableGet(g_gvHidden) > 0.5);
         hasSavedState = true;
      }
   }

   if(hasSavedState)
      IndicatorHidden = savedHidden;
   else
      IndicatorHidden = false;

   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   // Set chart properties
   currentChartWidth    = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   currentChartHeight   = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   currentChartScale    = (int)ChartGetInteger(0, CHART_SCALE);
   firstVisibleBarIndex = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   visibleBarsCount     = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   minPrice             = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   maxPrice             = ChartGetDouble(0, CHART_PRICE_MAX, 0);

   // Indicator buffers
   SetIndexBuffer(0, slowLineUpBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, slowLineDownBuffer, INDICATOR_DATA);
   SetIndexBuffer(2, fastLineBuffer, INDICATOR_DATA);
   SetIndexBuffer(3, fastLineColorBuffer, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4, trendArrowBuffer, INDICATOR_DATA);
   SetIndexBuffer(5, trendArrowColorBuffer, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(6, trendBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(7, slowLineBuffer, INDICATOR_CALCULATIONS);

   ApplyPlotsStyle();
   if(!isParamChange)
      RestorePlotStyles();
   SavePlotStyles();
   CreateToggleButton();
   BringButtonsToFront();

   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   IndicatorSetString(INDICATOR_SHORTNAME, IndicatorName);

   // Create canvas only if enabled
   if(enableFilling)
   {
      canvasName = objectPrefix + "Canvas";
      ObjectDelete(0, canvasName);
      if(!obj_Canvas.CreateBitmapLabel(0, 0, canvasName, 0, 0, currentChartWidth, currentChartHeight, COLOR_FORMAT_ARGB_NORMALIZE))
      {
         Print("Failed to create canvas");
         return(INIT_FAILED);
      }
      ObjectSetInteger(0, canvasName, OBJPROP_BACK, true);
      ObjectSetInteger(0, canvasName, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, canvasName, OBJPROP_ZORDER, 0);

      if(IndicatorHidden)
      {
         obj_Canvas.Erase(0);
         obj_Canvas.Update();
      }
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(reason == REASON_CHARTCHANGE)
   {
      SavePlotStyles();
   }

   if(enableFilling)
   {
      obj_Canvas.Destroy();
      if(canvasName != "")
         ObjectDelete(0, canvasName);
   }

   ObjectsDeleteAll(0, objectPrefix, 0, OBJ_ARROW_RIGHT_PRICE);

   if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS)
   {
      ObjectDelete(0, buttonId);
      ObjectDelete(0, ToggleButtonName);
      if(g_gvHidden != "")
         GlobalVariableDel(g_gvHidden);

      if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE)
      {
         for(int k = 0; k < 4; k++)
         {
            GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k));
            GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k));
         }
      }
   }
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| 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 < MathMax(fastPeriod, slowPeriod))
      return(0);

   if(prev_calculated <= 0)
   {
      CreateToggleButton();
      ApplyPlotsStyle();
      if(!IndicatorHidden)
         RestorePlotStyles();
   }

   // Always calculate buffers
   int startBar = prev_calculated - 1;
   if(startBar < 0) startBar = 0;
   for(int barIndex = startBar; barIndex < rates_total && !_StopFlag; barIndex++)
   {
      int fastStartBar = barIndex - fastPeriod + 1;
      if(fastStartBar < 0) fastStartBar = 0;
      int slowStartBar = barIndex - slowPeriod + 1;
      if(slowStartBar < 0) slowStartBar = 0;
      int slowCount = barIndex - slowStartBar + 1;
      int fastCount = barIndex - fastStartBar + 1;
      double slowHigh = high[ArrayMaximum(high, slowStartBar, slowCount)];
      double slowLow  = low[ArrayMinimum(low, slowStartBar, slowCount)];
      double fastHigh = high[ArrayMaximum(high, fastStartBar, fastCount)];
      double fastLow  = low[ArrayMinimum(low, fastStartBar, fastCount)];
      if(barIndex > 0)
      {
         slowLineBuffer[barIndex] = (close[barIndex] > slowLineBuffer[barIndex-1]) ? slowLow : slowHigh;
         fastLineBuffer[barIndex] = (close[barIndex] > fastLineBuffer[barIndex-1]) ? fastLow : fastHigh;
         trendBuffer[barIndex] = trendBuffer[barIndex-1];
         if(close[barIndex] < slowLineBuffer[barIndex] && close[barIndex] < fastLineBuffer[barIndex]) trendBuffer[barIndex] = 1;
         if(close[barIndex] > slowLineBuffer[barIndex] && close[barIndex] > fastLineBuffer[barIndex]) trendBuffer[barIndex] = 0;
         trendArrowBuffer[barIndex] = (trendBuffer[barIndex] != trendBuffer[barIndex-1]) ? slowLineBuffer[barIndex] : EMPTY_VALUE;
         slowLineUpBuffer[barIndex] = (trendBuffer[barIndex] == 0) ? slowLineBuffer[barIndex] : EMPTY_VALUE;
         slowLineDownBuffer[barIndex] = (trendBuffer[barIndex] == 1) ? slowLineBuffer[barIndex] : EMPTY_VALUE;
      }
      else
      {
         trendArrowBuffer[barIndex] = slowLineUpBuffer[barIndex] = slowLineDownBuffer[barIndex] = EMPTY_VALUE;
         trendBuffer[barIndex] = fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = 0;
         fastLineBuffer[barIndex] = slowLineBuffer[barIndex] = close[barIndex];
      }
      fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = trendBuffer[barIndex];
   }

   // Draw line extensions if enabled and visible
   if(showExtensions && rates_total > 0)
   {
      if(!IndicatorHidden)
      {
         int latestBarIndex = rates_total - 1;
         double slowLineValue = slowLineBuffer[latestBarIndex];
         double fastLineValue = fastLineBuffer[latestBarIndex];
         double currentTrend = trendBuffer[latestBarIndex];
         color lineColor = (currentTrend == 0.0) ? upColor : downColor;
         datetime currentBarTime = iTime(_Symbol, _Period, 0);
         long timeOffset = (long)extendBars * PeriodSeconds(_Period);
         datetime extensionTime = currentBarTime + (datetime)timeOffset;
         drawRightPrice(objectPrefix + "SLOW", extensionTime, slowLineValue, lineColor, STYLE_SOLID);
         drawRightPrice(objectPrefix + "FAST", extensionTime, fastLineValue, lineColor, STYLE_DOT);
      }
      else
      {
         ObjectsDeleteAll(0, objectPrefix, 0, OBJ_ARROW_RIGHT_PRICE);
      }
   }

   if(!enableFilling) return(rates_total);

   if(IndicatorHidden)
   {
      return(rates_total);
   }

   // Canvas logic only if enabled and visible
   bool isNewBar = (rates_total > prev_calculated);
   bool hasTrendChanged = false;
   if(rates_total > 0 && trendBuffer[rates_total-1] != previousTrend)
   {
      hasTrendChanged = true;
      previousTrend = trendBuffer[rates_total-1];
   }

   // Update chart properties (only if changed)
   bool hasChartChanged = false;
   int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   int newChartScale = (int)ChartGetInteger(0, CHART_SCALE);
   int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);
   if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight)
   {
      obj_Canvas.Resize(newChartWidth, newChartHeight);
      currentChartWidth = newChartWidth;
      currentChartHeight = newChartHeight;
      hasChartChanged = true;
   }
   if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
      newMinPrice != minPrice || newMaxPrice != maxPrice)
   {
      currentChartScale = newChartScale;
      firstVisibleBarIndex = newFirstVisibleBar;
      visibleBarsCount = newVisibleBars;
      minPrice = newMinPrice;
      maxPrice = newMaxPrice;
      hasChartChanged = true;
   }

   // Redraw only on: new bar, trend change, or chart resize/scroll. Debounce to 1x/sec max.
   datetime currentTime = TimeCurrent();
   if((isNewBar || hasTrendChanged || hasChartChanged) && (currentTime - lastRedrawTime >= 1))
   {
      Redraw();
      lastRedrawTime = currentTime;
   }
   return(rates_total);
}

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      string altId = "_" + UniqueButtonID + IndicatorObjPrefix + "_BT_";
      if(sparam == buttonId || sparam == ToggleButtonName || sparam == altId)
      {
         ToggleIndicatorVisibility();
         return;
      }
   }

   if(id == CHARTEVENT_CHART_CHANGE)
   {
      SavePlotStyles();
      BringButtonsToFront();

      if(enableFilling)
      {
         int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
         int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
         if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight)
         {
            obj_Canvas.Resize(newChartWidth, newChartHeight);
            currentChartWidth = newChartWidth;
            currentChartHeight = newChartHeight;
            if(!IndicatorHidden)
               Redraw();
            else
            {
               obj_Canvas.Erase(0);
               obj_Canvas.Update();
            }
            return;
         }
         int newChartScale = (int)ChartGetInteger(0, CHART_SCALE);
         int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
         int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
         double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0);
         double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0);
         if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
            newMinPrice != minPrice || newMaxPrice != maxPrice)
         {
            currentChartScale = newChartScale;
            firstVisibleBarIndex = newFirstVisibleBar;
            visibleBarsCount = newVisibleBars;
            minPrice = newMinPrice;
            maxPrice = newMaxPrice;
            if(!IndicatorHidden)
               Redraw();
            else
            {
               obj_Canvas.Erase(0);
               obj_Canvas.Update();
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Convert chart scale to bar width                                 |
//+------------------------------------------------------------------+
int BarWidth(int chartScale)
{
   return (int)MathPow(2.0, chartScale);
}

//+------------------------------------------------------------------+
//| Convert bar shift to x pixel                                     |
//+------------------------------------------------------------------+
int ShiftToX(int barShift)
{
   return (int)((firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1);
}

//+------------------------------------------------------------------+
//| Convert price to y pixel                                         |
//+------------------------------------------------------------------+
int PriceToY(double price)
{
   if(maxPrice - minPrice == 0.0) return 0;
   return (int)MathRound(currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1);
}

//+------------------------------------------------------------------+
//| Draw right price extension line/label                            |
//+------------------------------------------------------------------+
bool drawRightPrice(string objName, datetime lineTime, double linePrice, color lineColor, ENUM_LINE_STYLE lineStyle = STYLE_SOLID)
{
   if(IndicatorHidden) return false;
   bool objectExists = (ObjectFind(0, objName) >= 0);
   if(!objectExists)
   {
      if(!ObjectCreate(0, objName, OBJ_ARROW_RIGHT_PRICE, 0, lineTime, linePrice))
      {
         Print("Failed to create ", objName);
         return false;
      }
   }
   else
   {
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, lineTime);
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, linePrice);
   }
   long currentScale = ChartGetInteger(0, CHART_SCALE);
   int lineWidth = 1;
   if(currentScale <= 1) lineWidth = 1;
   else if(currentScale <= 3) lineWidth = 2;
   else lineWidth = 3;
   ObjectSetInteger(0, objName, OBJPROP_COLOR, lineColor);
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, lineWidth);
   ObjectSetInteger(0, objName, OBJPROP_STYLE, lineStyle);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
   ChartRedraw(0);
   return true;
}

//+------------------------------------------------------------------+
//| Fill area between two lines using gradient alpha                 |
//+------------------------------------------------------------------+
void DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0)
{
   if(IndicatorHidden) return;
   int firstVisibleBar = firstVisibleBarIndex;
   int totalBarsToDraw = visibleBarsCount + extendShift;
   int bufferSize = (int)ArraySize(slowLineValues);
   if(bufferSize == 0 || bufferSize != ArraySize(fastLineValues) || bufferSize != ArraySize(trendValues)) return;
   int previousX = -1;
   int previousY1 = -1;
   int previousY2 = -1;
   for(int offset = 0; offset < totalBarsToDraw; offset++)
   {
      int barPosition = firstVisibleBar - offset;
      int x = ShiftToX(barPosition);
      if(x >= currentChartWidth) break;
      int dataBarShift = firstVisibleBar - offset + extendShift;
      int bufferBarIndex = bufferSize - 1 - dataBarShift;
      if(bufferBarIndex < 0 || bufferBarIndex >= bufferSize)
      {
         previousX = -1;
         continue;
      }
      double value1 = slowLineValues[bufferBarIndex];
      double value2 = fastLineValues[bufferBarIndex];
      if(value1 == EMPTY_VALUE || value2 == EMPTY_VALUE)
      {
         previousX = -1;
         continue;
      }
      int y1 = PriceToY(value1);
      int y2 = PriceToY(value2);
      double currentTrend = trendValues[bufferBarIndex];
      uint baseColorRGB = (currentTrend == 0.0) ? (ColorToARGB(fillUpColor, 255) & 0x00FFFFFF) : (ColorToARGB(fillDownColor, 255) & 0x00FFFFFF);
      if(previousX != -1 && x > previousX)
      {
         double deltaX = x - previousX;
         int endColumn = MathMin(x, currentChartWidth - 1);
         for(int column = previousX; column <= endColumn; column++)
         {
            double t = (column - previousX) / deltaX;
            double interpolatedY1 = previousY1 + t * (y1 - previousY1);
            double interpolatedY2 = previousY2 + t * (y2 - previousY2);
            int upperY = (int)MathRound(MathMin(interpolatedY1, interpolatedY2));
            int lowerY = (int)MathRound(MathMax(interpolatedY1, interpolatedY2));
            if(upperY > lowerY) continue;
            double slowLineY = interpolatedY1;
            double height = MathAbs(interpolatedY1 - interpolatedY2);
            if(height == 0.0) continue;
            // Fill per row with gradient from slow (opaque) to fast (transparent)
            for(int row = upperY; row <= lowerY; row++)
            {
               double distanceFromSlow = MathAbs(row - slowLineY);
               double gradientFraction = distanceFromSlow / height;
               uchar alphaValue = (uchar)(fillAlpha * (1.0 - gradientFraction));
               if(alphaValue > fillAlpha) alphaValue = fillAlpha;
               uint pixelColor = ((uint)alphaValue << 24) | baseColorRGB;
               obj_Canvas.FillRectangle(column, row, column, row, pixelColor);
            }
         }
      }
      previousX = x;
      previousY1 = y1;
      previousY2 = y2;
   }
}

//+------------------------------------------------------------------+
//| Redraw the canvas                                                |
//+------------------------------------------------------------------+
void Redraw(void)
{
   if(!enableFilling) return;
   if(currentChartWidth <= 0 || currentChartHeight <= 0) return;
   if(IndicatorHidden)
   {
      obj_Canvas.Erase(0);
      obj_Canvas.Update();
      return;
   }
   uint defaultColor = 0;
   obj_Canvas.Erase(defaultColor);
   DrawFilling(slowLineBuffer, fastLineBuffer, trendBuffer, upColor, downColor, (uchar)fillOpacity, extendBars);
   obj_Canvas.Update();
}
//+------------------------------------------------------------------+
