//+----------------------------------------------------------------------------------+
//|                                                             SuperSmoother_BT.mq5 |
//|                                                   Copyright 2026, John F. Ehlers |
//| https://www.tradingview.com/script/FnlMn99W-TASC-2026-09-Adaptive-SuperSmoother/ |
//+----------------------------------------------------------------------------------+
#property copyright   "Copyright 2026, John F. Ehlers / PineCoders"
#property link        "https://forex-station.com/post1295586787.html#p1295586787"
#property version     "1.00"
#property description "TASC Issue: September 2026"
#property description "Article: Improved Filter Performance - Adaptive SuperSmoother"
#property description "Article By: John F. Ehlers"
#property description "SuperSmoother (base) moving average with on/off toggle button"

//--- Place on main price chart
#property indicator_chart_window
#property indicator_buffers 8
#property indicator_plots   2

//--- Plot 1: Candles colored by slope
#property indicator_label1  "SuperSmoother Candles"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrLime,clrRed
#property indicator_width1  2

//--- Plot 2: SuperSmoother, two colors by slope
#property indicator_label2  "SuperSmoother"
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_color2  clrLime,clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "--- Inputs ---"
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; // Source (Price field)
input int                InpBasePeriod   = 20;          // Base period (>= 2)

input group "--- Colors ---"
input color              InpUpColor      = clrLime;      // Rising slope color
input color              InpDownColor    = clrRed;       // Falling slope color

input group "--- Display ---"
input bool               InpShowMA        = true;         // Show Moving Average
input int                InpMAWidth       = 2;            // MA Width (1-5)

input group "--- Candles ---"
input bool               InpColorCandles = true;         // Color Candles By Slope
input int                InpCandleWidth  = 2;            // Candle Width (1-5)

//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              = "SS: 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              = 100;                // Horizontal location
input int                button_y              = 120;                // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "SuperSmootherBT";  // Unique Button ID
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

//+------------------------------------------------------------------+
//| Indicator Buffers                                                |
//+------------------------------------------------------------------+
double CandleO[];       // Candles: open
double CandleH[];       // Candles: high
double CandleL[];       // Candles: low
double CandleC[];       // Candles: close
double CandleCol[];     // Candles color index: 0 = rising, 1 = falling
double BufferBase[];    // Plotted: SuperSmoother
double BufferBaseCol[]; // Color index: 0 = rising, 1 = falling
double BufferMACalc[];  // Calculation buffer: SuperSmoother values

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
double g_w_base;
double g_q_base;
double g_c1_base;
double g_c2_base;
double g_a0_base;

//--- toggle button state
bool   g_indicatorEnabled = true;
bool   g_indicatorHidden  = false;
string g_buttonId;
string g_toggleButtonName;
string g_gvHidden;
string g_indicatorObjPrefix;

//+------------------------------------------------------------------+
//| 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, g_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 == g_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, g_buttonId, OBJ_BUTTON, btn_Subwindow, 0, 0);

      ObjectSetInteger(0, g_buttonId, OBJPROP_CORNER,       btn_corner);
      ObjectSetInteger(0, g_buttonId, OBJPROP_XDISTANCE,    button_x);
      ObjectSetInteger(0, g_buttonId, OBJPROP_YDISTANCE,    y_pos);
      ObjectSetInteger(0, g_buttonId, OBJPROP_XSIZE,        btn_Width);
      ObjectSetInteger(0, g_buttonId, OBJPROP_YSIZE,        btn_Height);
      ObjectSetString (0, g_buttonId, OBJPROP_FONT,         btn_Font);
      ObjectSetInteger(0, g_buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
      ObjectSetInteger(0, g_buttonId, OBJPROP_BGCOLOR,      btn_background_color);
      ObjectSetInteger(0, g_buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
      ObjectSetInteger(0, g_buttonId, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
      ObjectSetInteger(0, g_buttonId, OBJPROP_SELECTABLE,   false);
      ObjectSetInteger(0, g_buttonId, OBJPROP_HIDDEN,       true);
      ObjectSetInteger(0, g_buttonId, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
      ObjectSetInteger(0, g_buttonId, OBJPROP_ZORDER,       100);
   }

   UpdateToggleButton();
}

//+------------------------------------------------------------------+
//| Update Toggle Button Appearance                                  |
//+------------------------------------------------------------------+
void UpdateToggleButton()
{
   if(ObjectFind(0, g_buttonId) < 0) return;

   g_indicatorEnabled = !g_indicatorHidden;

   string labelText  = GetButtonText(g_indicatorEnabled);
   color  textColor  = g_indicatorEnabled ? btn_text_ON_color : btn_text_OFF_color;

   ObjectSetString (0, g_buttonId, OBJPROP_TEXT,         labelText);
   ObjectSetString (0, g_buttonId, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, g_buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, g_buttonId, OBJPROP_COLOR,        textColor);
   ObjectSetInteger(0, g_buttonId, OBJPROP_BGCOLOR,      btn_background_color);
   ObjectSetInteger(0, g_buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, g_buttonId, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, g_buttonId, OBJPROP_XDISTANCE,    button_x);
   ObjectSetInteger(0, g_buttonId, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, g_buttonId, OBJPROP_YSIZE,        btn_Height);
   ObjectSetInteger(0, g_buttonId, OBJPROP_STATE,        g_indicatorEnabled);
   ObjectSetInteger(0, g_buttonId, OBJPROP_ZORDER,       100);

   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()
{
   //--- Validate input parameters
   if(InpBasePeriod < 2)
   {
      PrintFormat("SuperSmoother: Base period must be >= 2. Received: %d", InpBasePeriod);
      return(INIT_PARAMETERS_INCORRECT);
   }

   //--- Bind indicator buffers (plot 1 = candles, plot 2 = line)
   SetIndexBuffer(0, CandleO,       INDICATOR_DATA);
   SetIndexBuffer(1, CandleH,       INDICATOR_DATA);
   SetIndexBuffer(2, CandleL,       INDICATOR_DATA);
   SetIndexBuffer(3, CandleC,       INDICATOR_DATA);
   SetIndexBuffer(4, CandleCol,     INDICATOR_COLOR_INDEX);
   SetIndexBuffer(5, BufferBase,    INDICATOR_DATA);
   SetIndexBuffer(6, BufferBaseCol, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(7, BufferMACalc,  INDICATOR_CALCULATIONS);

   //--- Standard chronological indexing (index 0 is oldest bar)
   ArraySetAsSeries(CandleO,       false);
   ArraySetAsSeries(CandleH,       false);
   ArraySetAsSeries(CandleL,       false);
   ArraySetAsSeries(CandleC,       false);
   ArraySetAsSeries(CandleCol,     false);
   ArraySetAsSeries(BufferBase,    false);
   ArraySetAsSeries(BufferBaseCol, false);
   ArraySetAsSeries(BufferMACalc,  false);

   //--- Candles colored by the line's slope
   //    (draw type stays DRAW_COLOR_CANDLES; when disabled the buffers are
   //     filled with EMPTY_VALUE so the plot stays hidden without remapping)
   PlotIndexSetInteger(0, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, InpUpColor);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, InpDownColor);
   PlotIndexSetInteger(0, PLOT_LINE_WIDTH, MathMin(MathMax(InpCandleWidth, 1), 5));
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   //--- Two colors selected by the line's slope
   PlotIndexSetInteger(1, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, InpUpColor);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 1, InpDownColor);
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, MathMin(MathMax(InpMAWidth, 1), 5));
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   //--- Visual configuration
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("SuperSmoother(%d)", InpBasePeriod));

   //--- Precalculate fixed filter coefficients
   g_w_base  = 1.414 * M_PI / (double)InpBasePeriod;
   g_q_base  = MathExp(-g_w_base);
   g_c1_base = 2.0 * g_q_base * MathCos(g_w_base);
   g_c2_base = g_q_base * g_q_base;
   g_a0_base = (1.0 - g_c1_base + g_c2_base) / 2.0;

   //--- Toggle button: unique IDs and saved state
   g_indicatorObjPrefix = "__SSBT__";
   g_buttonId           = g_indicatorObjPrefix + UniqueButtonID;
   g_toggleButtonName   = g_buttonId;
   g_gvHidden           = "SuperSmoother_BT_" + UniqueButtonID + "_" +
                          IntegerToString((long)ChartID()) + "_Hidden";

   //--- restore the toggle state (kept across input changes, TF/symbol changes and reloads)
   bool hasSavedState = false;
   bool savedHidden   = false;

   if(ObjectFind(0, g_buttonId) >= 0)
   {
      string btnTxt = ObjectGetString(0, g_buttonId, OBJPROP_TEXT);
      long   btnSt  = ObjectGetInteger(0, g_buttonId, OBJPROP_STATE);

      if(StringFind(btnTxt, "OFF") >= 0 || StringFind(btnTxt, "Off") >= 0)
         savedHidden = true;
      else if(StringFind(btnTxt, "ON") >= 0 || StringFind(btnTxt, "On") >= 0)
         savedHidden = false;
      else
         savedHidden = (btnSt == 0);

      hasSavedState = true;
   }

   if(!hasSavedState && GlobalVariableCheck(g_gvHidden))
   {
      savedHidden   = (GlobalVariableGet(g_gvHidden) > 0.5);
      hasSavedState = true;
   }

   g_indicatorHidden  = (hasSavedState ? savedHidden : false);
   g_indicatorEnabled = !g_indicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, g_indicatorHidden ? 1.0 : 0.0);

   CreateToggleButton();
   BringButtonsToFront();

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   //--- keep the ON/OFF state across an input change; drop it when the indicator goes away
   if(reason == REASON_PARAMETERS)
   {
      if(g_gvHidden != "")
         GlobalVariableSet(g_gvHidden, g_indicatorHidden ? 1.0 : 0.0);
   }
   else if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE)
   {
      if(g_gvHidden != "")
         GlobalVariableDel(g_gvHidden);
   }

   if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS)
   {
      ObjectDelete(0, g_buttonId);
      ObjectDelete(0, g_toggleButtonName);
   }

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Helper: Extract selected applied price                           |
//+------------------------------------------------------------------+
inline double GetAppliedPrice(const int index,
                              const double &open[],
                              const double &high[],
                              const double &low[],
                              const double &close[])
{
   switch(InpAppliedPrice)
   {
      case PRICE_OPEN:     return open[index];
      case PRICE_HIGH:     return high[index];
      case PRICE_LOW:      return low[index];
      case PRICE_MEDIAN:   return (high[index] + low[index]) * 0.5;
      case PRICE_TYPICAL:  return (high[index] + low[index] + close[index]) / 3.0;
      case PRICE_WEIGHTED: return (high[index] + low[index] + 2.0 * close[index]) * 0.25;
      case PRICE_CLOSE:
      default:             return close[index];
   }
}

//+------------------------------------------------------------------+
//| Calculation for a range of bars                                  |
//+------------------------------------------------------------------+
void CalcRange(const int from, const int rates_total,
               const double &open[], const double &high[],
               const double &low[], const double &close[])
{
   for(int i = from; i < rates_total; i++)
   {
      double src = GetAppliedPrice(i, open, high, low, close);

      //--- SuperSmoother Calculation (recursion uses the calculation buffer)
      double ma;
      if(i < 4)
      {
         ma = src;
      }
      else
      {
         double src_prev = GetAppliedPrice(i - 1, open, high, low, close);
         ma = g_a0_base * (src + src_prev)
            + g_c1_base * BufferMACalc[i - 1]
            - g_c2_base * BufferMACalc[i - 2];
      }
      BufferMACalc[i] = ma;

      //--- Slope color: falling = 1, rising or flat = 0
      double slope = (i > 0 && ma < BufferMACalc[i - 1]) ? 1.0 : 0.0;

      if(g_indicatorHidden)
      {
         BufferBase[i]    = EMPTY_VALUE;
         BufferBaseCol[i] = 0.0;
         CandleO[i]       = EMPTY_VALUE;
         CandleH[i]       = EMPTY_VALUE;
         CandleL[i]       = EMPTY_VALUE;
         CandleC[i]       = EMPTY_VALUE;
         CandleCol[i]     = 0.0;
         continue;
      }

      //--- Line (EMPTY_VALUE hides it when disabled in inputs)
      BufferBaseCol[i] = slope;
      BufferBase[i]    = (InpShowMA ? ma : EMPTY_VALUE);

      //--- Candles colored by slope (EMPTY_VALUE hides them when disabled)
      if(InpColorCandles)
      {
         CandleO[i]   = open[i];
         CandleH[i]   = high[i];
         CandleL[i]   = low[i];
         CandleC[i]   = close[i];
         CandleCol[i] = slope;
      }
      else
      {
         CandleO[i]   = EMPTY_VALUE;
         CandleH[i]   = EMPTY_VALUE;
         CandleL[i]   = EMPTY_VALUE;
         CandleC[i]   = EMPTY_VALUE;
         CandleCol[i] = 0.0;
      }
   }
}

//+------------------------------------------------------------------+
//| Full visual rebuild from chart history (works when market closed)|
//+------------------------------------------------------------------+
void ForceFullRedraw()
{
   int total = Bars(_Symbol, _Period);
   if(total <= 0)
      return;

   double o[], h[], l[], c[];

   int nO = CopyOpen(_Symbol, _Period, 0, total, o);
   int nH = CopyHigh(_Symbol, _Period, 0, total, h);
   int nL = CopyLow(_Symbol, _Period, 0, total, l);
   int nC = CopyClose(_Symbol, _Period, 0, total, c);
   if(nO <= 0 || nH <= 0 || nL <= 0 || nC <= 0)
      return;

   int n = (int)MathMin(nO, MathMin(nH, MathMin(nL, nC)));
   if(ArraySize(BufferMACalc) < n)
      n = ArraySize(BufferMACalc);
   if(n <= 0 || ArraySize(o) < n || ArraySize(h) < n || ArraySize(l) < n || ArraySize(c) < n)
      return;

   CalcRange(0, n, o, h, l, c);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Toggle Indicator Visibility                                      |
//+------------------------------------------------------------------+
void ToggleIndicatorVisibility()
{
   g_indicatorHidden  = !g_indicatorHidden;
   g_indicatorEnabled = !g_indicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, g_indicatorHidden ? 1.0 : 0.0);

   if(g_indicatorHidden)
   {
      ArrayInitialize(BufferBase,    EMPTY_VALUE);
      ArrayInitialize(BufferBaseCol, 0.0);
      ArrayInitialize(CandleO,       EMPTY_VALUE);
      ArrayInitialize(CandleH,       EMPTY_VALUE);
      ArrayInitialize(CandleL,       EMPTY_VALUE);
      ArrayInitialize(CandleC,       EMPTY_VALUE);
      ArrayInitialize(CandleCol,     0.0);
   }
   else
   {
      ForceFullRedraw();
   }

   UpdateToggleButton();
   BringButtonsToFront();
   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 == g_buttonId || sparam == g_toggleButtonName)
      {
         ToggleIndicatorVisibility();
         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(prev_calculated <= 0)
   {
      CreateToggleButton();
      BringButtonsToFront();
   }

   if(rates_total < 5)
      return(0);

   int start = prev_calculated - 1;
   if(start < 0)
      start = 0;

   CalcRange(start, rates_total, open, high, low, close);

   return(rates_total);
}
//+------------------------------------------------------------------+
