//+----------------------------------------------------------------------------------+
//|                                          AdaptiveSuperSmoother_Oscillator_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 Oscillator"
#property description "Article By: John F. Ehlers"

//--- Place in separate indicator subwindow
#property indicator_separate_window
#property indicator_buffers 15
#property indicator_plots   7

//--- Plot 1,2,3: 3D Oscillator Histogram
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_width1  5
#property indicator_label1  "Osc Hist Outer"

#property indicator_type2   DRAW_COLOR_HISTOGRAM
#property indicator_width2  3
#property indicator_label2  "Osc Hist Core"

#property indicator_type3   DRAW_COLOR_HISTOGRAM
#property indicator_width3  1
#property indicator_label3  "Osc Hist Highlight"

//--- Plot 4,5,6: 3D Oscillator Line
#property indicator_type4   DRAW_COLOR_LINE
#property indicator_width4  5
#property indicator_label4  "Osc Line Outer"

#property indicator_type5   DRAW_COLOR_LINE
#property indicator_width5  3
#property indicator_label5  "Osc Line Core"

#property indicator_type6   DRAW_COLOR_LINE
#property indicator_width6  1
#property indicator_label6  "Osc Line Highlight"

//--- Plot 7: Zero reference line
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrGold
#property indicator_width7  1
#property indicator_label7  "Zero"

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "--- Inputs ---"
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; // Source (Price field)
input int                InpBasePeriod   = 20;          // Base period (>= 2)
input int                InpRMSLength    = 81;          // RMS length (>= 2)

input group "--- 3D Histogram Widths ---"
input int                InpHistOuterWidth = 5;         // Histogram outer width (1-5)
input int                InpHistCoreWidth  = 3;         // Histogram core width (1-5)
input int                InpHistInnerWidth = 1;         // Histogram highlight width (1-5)

input group "--- 3D Line Widths ---"
input int                InpLineOuterWidth = 5;         // Line outer width (1-5)
input int                InpLineCoreWidth  = 3;         // Line core width (1-5)
input int                InpLineInnerWidth = 1;         // Line highlight width (1-5)

input group "--- FX Color Settings ---"
input bool               InpUse4Colors   = true;           // Use 4 colors (strong/weak)
input color              InpCandleUp     = C'96,170,204';  // Strong Bullish
input color              InpCandleUpWeak = C'35,100,130';  // Bullish Retracement
input color              InpCandleDn     = C'255,140,255'; // Strong Bearish
input color              InpCandleDnWeak = C'180,70,180';  // Bearish Retracement
input color              InpCandleWt     = C'34,34,34';    // Neutral

input group "--- Zero Line ---"
input color              InpZeroColor   = clrGold;      // Zero line color
input int                InpZeroWidth   = 1;            // Zero line 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;                  // Window for the button
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER;  // chart btn_corner for anchoring
input string             btn_text              = "A-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              = 20;                 // Horizontal location
input int                button_y              = 80;                 // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "ASS_OscBT";        // Unique Button ID
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

//+------------------------------------------------------------------+
//| Indicator Buffers                                                |
//+------------------------------------------------------------------+
double HistDarkBuffer[];   double HistDarkColors[];    // Plot 1
double HistBaseBuffer[];   double HistBaseColors[];    // Plot 2
double HistLightBuffer[];  double HistLightColors[];   // Plot 3

double LineDarkBuffer[];   double LineDarkColors[];    // Plot 4
double LineBaseBuffer[];   double LineBaseColors[];    // Plot 5
double LineLightBuffer[];  double LineLightColors[];   // Plot 6

double BufferZero[];                                  // Plot 7

double BufferAdaptive[];                              // Calculation: adaptive SS
double BufferBase[];                                  // Calculation: base SS

//+------------------------------------------------------------------+
//| 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;
int    g_btnWindow        = 0;
string g_buttonId;
string g_toggleButtonName;
string g_gvHidden;
string g_indicatorObjPrefix;

//+------------------------------------------------------------------+
//| Utility: Blend two colors by a given ratio (for 3D shading)      |
//+------------------------------------------------------------------+
color BlendColor(color c, color blendWith, double ratio)
{
   int r1 = (int)(c & 0x0000FF);
   int g1 = (int)((c & 0x00FF00) >> 8);
   int b1 = (int)((c & 0xFF0000) >> 16);
   int r2 = (int)(blendWith & 0x0000FF);
   int g2 = (int)((blendWith & 0x00FF00) >> 8);
   int b2 = (int)((blendWith & 0xFF0000) >> 16);

   int r = (int)MathRound(r1 + (r2 - r1) * ratio);
   int g = (int)MathRound(g1 + (g2 - g1) * ratio);
   int b = (int)MathRound(b1 + (b2 - b1) * ratio);
   return (color)(r | (g << 8) | (b << 16));
}

//+------------------------------------------------------------------+
//| Clamp a line width to the 1-5 range                              |
//+------------------------------------------------------------------+
int ClampWidth(const int w)
{
   return (int)MathMin(MathMax(w, 1), 5);
}

//+------------------------------------------------------------------+
//| Get Timeframe Name String                                        |
//+------------------------------------------------------------------+
string GetStringTimeframe(ENUM_TIMEFRAMES timeframe)
{
   return(StringSubstr(EnumToString(timeframe), 7));
}

//+------------------------------------------------------------------+
//| 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, g_btnWindow, 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("AdaptiveSuperSmoother_Oscillator: Base period must be >= 2. Received: %d", InpBasePeriod);
      return(INIT_PARAMETERS_INCORRECT);
   }
   if(InpRMSLength < 2)
   {
      PrintFormat("AdaptiveSuperSmoother_Oscillator: RMS length must be >= 2. Received: %d", InpRMSLength);
      return(INIT_PARAMETERS_INCORRECT);
   }

   //--- Bind indicator buffers (plot order is positional)
   SetIndexBuffer(0,  HistDarkBuffer,   INDICATOR_DATA);
   SetIndexBuffer(1,  HistDarkColors,   INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,  HistBaseBuffer,   INDICATOR_DATA);
   SetIndexBuffer(3,  HistBaseColors,   INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4,  HistLightBuffer,  INDICATOR_DATA);
   SetIndexBuffer(5,  HistLightColors,  INDICATOR_COLOR_INDEX);

   SetIndexBuffer(6,  LineDarkBuffer,   INDICATOR_DATA);
   SetIndexBuffer(7,  LineDarkColors,   INDICATOR_COLOR_INDEX);
   SetIndexBuffer(8,  LineBaseBuffer,   INDICATOR_DATA);
   SetIndexBuffer(9,  LineBaseColors,   INDICATOR_COLOR_INDEX);
   SetIndexBuffer(10, LineLightBuffer,  INDICATOR_DATA);
   SetIndexBuffer(11, LineLightColors,  INDICATOR_COLOR_INDEX);

   SetIndexBuffer(12, BufferZero,       INDICATOR_DATA);

   SetIndexBuffer(13, BufferAdaptive,   INDICATOR_CALCULATIONS);
   SetIndexBuffer(14, BufferBase,       INDICATOR_CALCULATIONS);

   //--- Standard chronological indexing (index 0 is oldest bar)
   ArraySetAsSeries(HistDarkBuffer,    false);
   ArraySetAsSeries(HistDarkColors,    false);
   ArraySetAsSeries(HistBaseBuffer,    false);
   ArraySetAsSeries(HistBaseColors,    false);
   ArraySetAsSeries(HistLightBuffer,   false);
   ArraySetAsSeries(HistLightColors,   false);
   ArraySetAsSeries(LineDarkBuffer,    false);
   ArraySetAsSeries(LineDarkColors,    false);
   ArraySetAsSeries(LineBaseBuffer,    false);
   ArraySetAsSeries(LineBaseColors,    false);
   ArraySetAsSeries(LineLightBuffer,   false);
   ArraySetAsSeries(LineLightColors,   false);
   ArraySetAsSeries(BufferZero,        false);
   ArraySetAsSeries(BufferAdaptive,    false);
   ArraySetAsSeries(BufferBase,        false);

   //--- FX palette: index 0 = strong bull, 1 = neutral, 2 = strong bear,
   //    3 = weak bull, 4 = weak bear
   color baseColors[5] = {InpCandleUp, InpCandleWt, InpCandleDn,
                          InpCandleUpWeak, InpCandleDnWeak};

   for(int p = 0; p < 6; p++)
      PlotIndexSetInteger(p, PLOT_COLOR_INDEXES, 5);

   for(int c = 0; c < 5; c++)
   {
      color base  = baseColors[c];
      color dark  = BlendColor(base, clrBlack, 0.4);  // 40% darker edge
      color light = BlendColor(base, clrWhite, 0.6);  // 60% lighter core

      //--- 3D histogram layers: outer (dark) -> core (base) -> highlight (light)
      PlotIndexSetInteger(0, PLOT_LINE_COLOR, c, dark);
      PlotIndexSetInteger(1, PLOT_LINE_COLOR, c, base);
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, c, light);

      //--- 3D oscillator line layers
      PlotIndexSetInteger(3, PLOT_LINE_COLOR, c, dark);
      PlotIndexSetInteger(4, PLOT_LINE_COLOR, c, base);
      PlotIndexSetInteger(5, PLOT_LINE_COLOR, c, light);
   }

   //--- Layer widths (outer -> highlight)
   PlotIndexSetInteger(0, PLOT_LINE_WIDTH, ClampWidth(InpHistOuterWidth));
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, ClampWidth(InpHistCoreWidth));
   PlotIndexSetInteger(2, PLOT_LINE_WIDTH, ClampWidth(InpHistInnerWidth));

   PlotIndexSetInteger(3, PLOT_LINE_WIDTH, ClampWidth(InpLineOuterWidth));
   PlotIndexSetInteger(4, PLOT_LINE_WIDTH, ClampWidth(InpLineCoreWidth));
   PlotIndexSetInteger(5, PLOT_LINE_WIDTH, ClampWidth(InpLineInnerWidth));

   //--- Zero reference
   PlotIndexSetInteger(6, PLOT_LINE_COLOR, 0, InpZeroColor);
   PlotIndexSetInteger(6, PLOT_LINE_WIDTH, ClampWidth(InpZeroWidth));

   //--- Visual configuration
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   string short_name = StringFormat("[" + Symbol() + "] [" + GetStringTimeframe(_Period) + "] Adaptive SuperSmoother Oscillator (%d, %d)", InpBasePeriod, InpRMSLength);
   IndicatorSetString(INDICATOR_SHORTNAME, short_name);

   //--- Precalculate fixed filter coefficients for base SuperSmoother
   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: window, unique IDs and saved state
   g_btnWindow = (btn_Subwindow < 0) ? ChartWindowFind() : btn_Subwindow;
   if(g_btnWindow < 0)
      g_btnWindow = 0;

   g_indicatorObjPrefix = "__ASSOSCBT__";
   g_buttonId           = g_indicatorObjPrefix + UniqueButtonID;
   g_toggleButtonName   = g_buttonId;
   g_gvHidden           = "ASS_Osc_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);

      //--- 1. Base SuperSmoother Calculation
      if(i < 4)
      {
         BufferBase[i] = src;
      }
      else
      {
         double src_prev = GetAppliedPrice(i - 1, open, high, low, close);
         BufferBase[i] = g_a0_base * (src + src_prev)
                       + g_c1_base * BufferBase[i - 1]
                       - g_c2_base * BufferBase[i - 2];
      }

      //--- 2. Calculate Adaptive Period via Rate of Change (ROC) and RMS
      int aPeriod = InpBasePeriod;
      if(i >= InpRMSLength - 1)
      {
         // Calculate Root Mean Square (RMS) of 1-bar ROC over InpRMSLength bars
         double sum = 0.0;
         for(int k = 0; k < InpRMSLength; k++)
         {
            int idx = i - k;
            double r = (idx > 0) ? (BufferBase[idx] - BufferBase[idx - 1]) : 0.0;
            sum += r * r;
         }
         double rocRMS = MathSqrt(sum / (double)InpRMSLength);
         double roc1   = BufferBase[i] - BufferBase[i - 1];
         double roc    = 0.0;

         if(rocRMS > 0.0 && MathIsValidNumber(rocRMS))
         {
            double rVal = MathAbs(roc1 / rocRMS);
            if(MathIsValidNumber(rVal))
               roc = MathMin(rVal, 2.0);
         }

         double factor = (1.0 - 0.5 * roc) * (1.0 - 0.5 * roc);
         aPeriod = (int)MathMax((int)(InpBasePeriod * factor), 2);
      }

      //--- 3. Adaptive SuperSmoother Calculation
      double w_a  = 1.414 * M_PI / (double)aPeriod;
      double q_a  = MathExp(-w_a);
      double c1_a = 2.0 * q_a * MathCos(w_a);
      double c2_a = q_a * q_a;
      double a0_a = (1.0 - c1_a + c2_a) / 2.0;

      if(i < 4)
      {
         BufferAdaptive[i] = src;
      }
      else
      {
         double src_prev = GetAppliedPrice(i - 1, open, high, low, close);
         BufferAdaptive[i] = a0_a * (src + src_prev)
                           + c1_a * BufferAdaptive[i - 1]
                           - c2_a * BufferAdaptive[i - 2];
      }

      //--- 4. Oscillator (ssAdaptive - ssBase) and zero baseline
      double osc = BufferAdaptive[i] - BufferBase[i];

      if(g_indicatorHidden)
      {
         HistDarkBuffer[i]   = EMPTY_VALUE;  HistDarkColors[i]   = 0.0;
         HistBaseBuffer[i]   = EMPTY_VALUE;  HistBaseColors[i]   = 0.0;
         HistLightBuffer[i]  = EMPTY_VALUE;  HistLightColors[i]  = 0.0;
         LineDarkBuffer[i]   = EMPTY_VALUE;  LineDarkColors[i]   = 0.0;
         LineBaseBuffer[i]   = EMPTY_VALUE;  LineBaseColors[i]   = 0.0;
         LineLightBuffer[i]  = EMPTY_VALUE;  LineLightColors[i]  = 0.0;
         BufferZero[i]       = EMPTY_VALUE;
         continue;
      }

      //--- 5. FX color mapping from oscillator sign and slope
      double oscPrev = (i > 0) ? (BufferAdaptive[i - 1] - BufferBase[i - 1]) : osc;
      bool rising    = (osc > oscPrev);
      bool falling   = (osc < oscPrev);

      int colorIdx = 1;                          // neutral at zero
      if(osc > 0.0)
         colorIdx = (InpUse4Colors && falling) ? 3 : 0;
      else if(osc < 0.0)
         colorIdx = (InpUse4Colors && rising) ? 4 : 2;

      //--- 6. Populate 3D histogram layers
      HistDarkBuffer[i]  = osc;  HistDarkColors[i]  = colorIdx;
      HistBaseBuffer[i]  = osc;  HistBaseColors[i]  = colorIdx;
      HistLightBuffer[i] = osc;  HistLightColors[i] = colorIdx;

      //--- 7. Populate 3D oscillator line layers
      LineDarkBuffer[i]  = osc;  LineDarkColors[i]  = colorIdx;
      LineBaseBuffer[i]  = osc;  LineBaseColors[i]  = colorIdx;
      LineLightBuffer[i] = osc;  LineLightColors[i] = colorIdx;

      //--- 8. Zero baseline
      BufferZero[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(BufferBase) < n)
      n = ArraySize(BufferBase);
   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(HistDarkBuffer,   EMPTY_VALUE);
      ArrayInitialize(HistDarkColors,   0.0);
      ArrayInitialize(HistBaseBuffer,   EMPTY_VALUE);
      ArrayInitialize(HistBaseColors,   0.0);
      ArrayInitialize(HistLightBuffer,  EMPTY_VALUE);
      ArrayInitialize(HistLightColors,  0.0);

      ArrayInitialize(LineDarkBuffer,   EMPTY_VALUE);
      ArrayInitialize(LineDarkColors,   0.0);
      ArrayInitialize(LineBaseBuffer,   EMPTY_VALUE);
      ArrayInitialize(LineBaseColors,   0.0);
      ArrayInitialize(LineLightBuffer,  EMPTY_VALUE);
      ArrayInitialize(LineLightColors,  0.0);

      ArrayInitialize(BufferZero,       EMPTY_VALUE);
   }
   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);
}
//+------------------------------------------------------------------+
