//+------------------------------------------------------------------+
//|                               Stoch (Signals and Trends) BT.mq5  |
//|                                  Copyright © 2026, Forex-Station |
//|                                    https://www.forex-station.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright © 2026, Forex-Station."
#property link        "https://forex-station.com/post1295586885.html#p1295586885"
#property description "Stoch (Signals and Trends) with on/off toggle button"
#property version     "1.01"
//--------------------------------------------------------------------
#property indicator_separate_window
#property indicator_buffers 8
#property indicator_plots   8

//--- Plot 1: Stoch 1 Main
#property indicator_label1  "Stoch1 Main"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrLightSeaGreen
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- Plot 2: Stoch 1 Signal
#property indicator_label2  "Stoch1 Signal"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//--- Plot 3: Stoch 2 Main
#property indicator_label3  "Stoch2 Main"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrBlue
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1

//--- Plot 4: Stoch 2 Signal
#property indicator_label4  "Stoch2 Signal"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrOrange
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1

//--- Plot 5: Cross Up Dot
#property indicator_label5  "Cross Up Dot"
#property indicator_type5   DRAW_ARROW
#property indicator_color5  clrLime
#property indicator_style5  STYLE_SOLID
#property indicator_width5  2

//--- Plot 6: Cross Down Dot
#property indicator_label6  "Cross Down Dot"
#property indicator_type6   DRAW_ARROW
#property indicator_color6  clrRed
#property indicator_style6  STYLE_SOLID
#property indicator_width5  2

//--- Plot 7: Stoch1 Cross Up (Flag buffer)
#property indicator_label7  "Stoch1 Cross Up"
#property indicator_type7   DRAW_NONE
#property indicator_color7  clrLime
#property indicator_style7  STYLE_SOLID
#property indicator_width7  1

//--- Plot 8: Stoch1 Cross Down (Flag buffer)
#property indicator_label8  "Stoch1 Cross Down"
#property indicator_type8   DRAW_NONE
#property indicator_color8  clrRed
#property indicator_style8  STYLE_SOLID
#property indicator_width8  1

//--- Subwindow levels & range
#property indicator_level1  20.0
#property indicator_level2  80.0
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT
#property indicator_minimum 0.0
#property indicator_maximum 100.0

//--- input parameters for Stoch 1
input group "=== Stochastic 1 (Fast) ==="
input string          Note1          = "--- Stochastic 1 (Fast) ---";
input int             KPeriod1       = 5;
input int             DPeriod1       = 3;
input int             Slowing1       = 3;
input ENUM_MA_METHOD  MAMethod1      = MODE_SMA;
input ENUM_STO_PRICE  PriceField1    = STO_LOWHIGH; // 0=Low/High, 1=Close/Close

//--- input parameters for Stoch 2
input group "=== Stochastic 2 (Slow) ==="
input string          Note2          = "--- Stochastic 2 (Slow) ---";
input int             KPeriod2       = 89;
input int             DPeriod2       = 34;
input int             Slowing2       = 55;
input ENUM_MA_METHOD  MAMethod2      = MODE_SMA;
input ENUM_STO_PRICE  PriceField2    = STO_LOWHIGH;

//--- arrow colors
input group "=== Arrow & Signal Counter Settings ==="
input int             UpArrowCode    = 159;
input int             DownArrowCode  = 167;
input color           UpCounterColor = clrOlive;
input color           DnCounterColor = clrHotPink;
input int             MaxBars        = 1000;       // Maximum bars to calculate (0 = all bars)

//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              = "STOCH: 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              = 40;                 // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "StochBT";          // Unique Button ID                      
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

//--- indicator buffers
double         MainBuffer1[];
double         SignalBuffer1[];
double         MainBuffer2[];
double         SignalBuffer2[];
double         UpDotBuffer[];
double         DnDotBuffer[];
double         UpArrowBuffer[];
double         DnArrowBuffer[]; 

string         shortName;
int            hStoch1 = INVALID_HANDLE;
int            hStoch2 = INVALID_HANDLE;

//--- Button State Tracking Variables
bool           IndicatorEnabled = true;
bool           IndicatorHidden  = false;
bool           g_forceRecalc    = false;
string         ToggleButtonName;
string         g_gvHidden;
string         g_prefix;
string         IndicatorObjPrefix, buttonId;

//+------------------------------------------------------------------------------------------------------------------+
//| Helper to convert timeframe enum to string                                                                       |
//+------------------------------------------------------------------------------------------------------------------+
string timeFrameToString(ENUM_TIMEFRAMES tf)
{
   string s = EnumToString(tf);
   if(StringFind(s, "PERIOD_") == 0)
      return StringSubstr(s, 7);
   return s;
}

//+------------------------------------------------------------------------------------------------------------------+
//| Helper to get unique Day-Of-Year integer (year * 1000 + doy)                                                     |
//+------------------------------------------------------------------------------------------------------------------+
int GetDayKey(datetime t)
{
   MqlDateTime dt;
   TimeToStruct(t, dt);
   return (dt.year * 1000 + dt.day_of_year);
}

//+------------------------------------------------------------------------------------------------------------------+
//| Delete all signal arrow objects                                                                                  |
//+------------------------------------------------------------------------------------------------------------------+
void DeleteAllArrowObjects()
{
   ObjectsDeleteAll(0, "StochArrowDn_");
   ObjectsDeleteAll(0, "StochArrowUp_");
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| 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;
}

//+------------------------------------------------------------------+
//| Save current plot widths & styles from chart to GlobalVariables  |
//+------------------------------------------------------------------+
void SavePlotStyles()
{
   if(g_prefix == "") return;
   for(int k = 0; k < 6; 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 < 6; 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))
      {
         int s = (int)GlobalVariableGet(gvS);
         if(s >= 0) PlotIndexSetInteger(k, PLOT_LINE_STYLE, s);
      }
   }
}

//+------------------------------------------------------------------+
//| Apply Plot Styles based on Button Toggle State                   |
//+------------------------------------------------------------------+
void ApplyPlotsStyle()
{
   bool show = !IndicatorHidden;

   // Plot 0: Stoch1 Main
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE,  show ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, show ? clrLightSeaGreen : clrNONE);
   PlotIndexSetString (0, PLOT_LABEL,      show ? "Stoch1 Main" : "");
   PlotIndexSetInteger(0, PLOT_SHOW_DATA,  show);

   // Plot 1: Stoch1 Signal
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE,  show ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, show ? clrRed : clrNONE);
   PlotIndexSetString (1, PLOT_LABEL,      show ? "Stoch1 Signal" : "");
   PlotIndexSetInteger(1, PLOT_SHOW_DATA,  show);

   // Plot 2: Stoch2 Main
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE,  show ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, show ? clrBlue : clrNONE);
   PlotIndexSetString (2, PLOT_LABEL,      show ? "Stoch2 Main" : "");
   PlotIndexSetInteger(2, PLOT_SHOW_DATA,  show);

   // Plot 3: Stoch2 Signal
   PlotIndexSetInteger(3, PLOT_DRAW_TYPE,  show ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetInteger(3, PLOT_LINE_COLOR, show ? clrOrange : clrNONE);
   PlotIndexSetString (3, PLOT_LABEL,      show ? "Stoch2 Signal" : "");
   PlotIndexSetInteger(3, PLOT_SHOW_DATA,  show);

   // Plot 4: Cross Up Dot
   PlotIndexSetInteger(4, PLOT_DRAW_TYPE,  show ? DRAW_ARROW : DRAW_NONE);
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, show ? clrLime : clrNONE);
   PlotIndexSetInteger(4, PLOT_ARROW,      UpArrowCode);
   PlotIndexSetString (4, PLOT_LABEL,      show ? "Cross Up Dot" : "");
   PlotIndexSetInteger(4, PLOT_SHOW_DATA,  show);

   // Plot 5: Cross Down Dot
   PlotIndexSetInteger(5, PLOT_DRAW_TYPE,  show ? DRAW_ARROW : DRAW_NONE);
   PlotIndexSetInteger(5, PLOT_LINE_COLOR, show ? clrRed : clrNONE);
   PlotIndexSetInteger(5, PLOT_ARROW,      DownArrowCode);
   PlotIndexSetString (5, PLOT_LABEL,      show ? "Cross Down Dot" : "");
   PlotIndexSetInteger(5, PLOT_SHOW_DATA,  show);

   // Plot 6 & 7: Flags (hidden plots)
   PlotIndexSetInteger(6, PLOT_DRAW_TYPE,  DRAW_NONE);
   PlotIndexSetInteger(6, PLOT_LINE_COLOR, clrNONE);
   PlotIndexSetString (6, PLOT_LABEL,      show ? "Stoch1 Cross Up" : "");
   PlotIndexSetInteger(6, PLOT_SHOW_DATA,  show);

   PlotIndexSetInteger(7, PLOT_DRAW_TYPE,  DRAW_NONE);
   PlotIndexSetInteger(7, PLOT_LINE_COLOR, clrNONE);
   PlotIndexSetString (7, PLOT_LABEL,      show ? "Stoch1 Cross Down" : "");
   PlotIndexSetInteger(7, PLOT_SHOW_DATA,  show);

   // Subwindow levels
   IndicatorSetInteger(INDICATOR_LEVELS, show ? 2 : 0);
   if(show)
   {
      IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 20.0);
      IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 80.0);
   }
}

//+------------------------------------------------------------------+
//| Create Toggle Button                                             |
//+------------------------------------------------------------------+
void CreateToggleButton()
{
   int target_window = btn_Subwindow;
   if(target_window < 0)
   {
      target_window = ChartWindowFind(0, shortName);
      if(target_window < 0) target_window = 0;
   }

   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, target_window, 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);
}

//+------------------------------------------------------------------+
//| 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);
      }
   }
}

//+------------------------------------------------------------------+
//| 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();
   BringButtonsToFront();

   if(IndicatorHidden)
   {
      DeleteAllArrowObjects();
   }
   else
   {
      g_forceRecalc = true;
   }

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   DeleteAllArrowObjects();

   IndicatorObjPrefix = "__" + btn_text + "__";
   buttonId           = IndicatorObjPrefix + UniqueButtonID;
   ToggleButtonName   = buttonId;
   g_prefix           = "Stoch_BT_" + UniqueButtonID + "_";
   g_gvHidden         = g_prefix + IntegerToString(ChartID()) + "_Hidden";

   // Restore previous toggle state across timeframe/symbol switches
   if(GlobalVariableCheck(g_gvHidden))
      IndicatorHidden = (GlobalVariableGet(g_gvHidden) > 0.5);
   else
      IndicatorHidden = false;

   IndicatorEnabled = !IndicatorHidden;

   SetIndexBuffer(0, MainBuffer1, INDICATOR_DATA);
   SetIndexBuffer(1, SignalBuffer1, INDICATOR_DATA);
   SetIndexBuffer(2, MainBuffer2, INDICATOR_DATA);
   SetIndexBuffer(3, SignalBuffer2, INDICATOR_DATA);
   SetIndexBuffer(4, UpDotBuffer, INDICATOR_DATA);
   SetIndexBuffer(5, DnDotBuffer, INDICATOR_DATA);
   SetIndexBuffer(6, UpArrowBuffer, INDICATOR_DATA);
   SetIndexBuffer(7, DnArrowBuffer, INDICATOR_DATA);

   ArraySetAsSeries(MainBuffer1, true);
   ArraySetAsSeries(SignalBuffer1, true);
   ArraySetAsSeries(MainBuffer2, true);
   ArraySetAsSeries(SignalBuffer2, true);
   ArraySetAsSeries(UpDotBuffer, true);
   ArraySetAsSeries(DnDotBuffer, true);
   ArraySetAsSeries(UpArrowBuffer, true);
   ArraySetAsSeries(DnArrowBuffer, true);

   PlotIndexSetInteger(4, PLOT_ARROW, UpArrowCode); 
   PlotIndexSetInteger(5, PLOT_ARROW, DownArrowCode);

   PlotIndexSetDouble(4, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(5, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(6, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(7, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   IndicatorSetInteger(INDICATOR_DIGITS, 2);

   shortName = "[" + _Symbol + "] [" + timeFrameToString(_Period) + "] Stoch (" + IntegerToString(KPeriod1) + "," + IntegerToString(DPeriod1) + "," + IntegerToString(Slowing1) + ") & Stoch (" + IntegerToString(KPeriod2) + "," + IntegerToString(DPeriod2) + "," + IntegerToString(Slowing2) + ")";
   IndicatorSetString(INDICATOR_SHORTNAME, shortName);

   if(hStoch1 != INVALID_HANDLE) { IndicatorRelease(hStoch1); hStoch1 = INVALID_HANDLE; }
   if(hStoch2 != INVALID_HANDLE) { IndicatorRelease(hStoch2); hStoch2 = INVALID_HANDLE; }

   hStoch1 = iStochastic(_Symbol, _Period, KPeriod1, DPeriod1, Slowing1, MAMethod1, PriceField1);
   if(hStoch1 == INVALID_HANDLE)
   {
      Print("Failed to create Stochastic 1 handle. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   hStoch2 = iStochastic(_Symbol, _Period, KPeriod2, DPeriod2, Slowing2, MAMethod2, PriceField2);
   if(hStoch2 == INVALID_HANDLE)
   {
      Print("Failed to create Stochastic 2 handle. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   ApplyPlotsStyle();
   if(!IndicatorHidden)
      RestorePlotStyles();
   SavePlotStyles();
   CreateToggleButton();
   BringButtonsToFront();

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   DeleteAllArrowObjects();

   if(reason == REASON_CHARTCHANGE)
   {
      if(!IndicatorHidden)
         SavePlotStyles();
   }

   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 < 6; k++)
         {
            GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k));
            GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k));
         }
      }
   }

   if(hStoch1 != INVALID_HANDLE)
   {
      IndicatorRelease(hStoch1);
      hStoch1 = INVALID_HANDLE;
   }
   if(hStoch2 != INVALID_HANDLE)
   {
      IndicatorRelease(hStoch2);
      hStoch2 = INVALID_HANDLE;
   }

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| 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)
   {
      if(!IndicatorHidden)
         SavePlotStyles();
      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 < 2) return(0);

   if(prev_calculated <= 0)
   {
      CreateToggleButton();
      ApplyPlotsStyle();
      if(!IndicatorHidden)
         RestorePlotStyles();
      BringButtonsToFront();
      DeleteAllArrowObjects();
   }

   int bars_to_process = (MaxBars > 0 && rates_total > MaxBars) ? MaxBars : rates_total;

   int calc1 = BarsCalculated(hStoch1);
   int calc2 = BarsCalculated(hStoch2);
   if(calc1 < bars_to_process || calc2 < bars_to_process)
      return(0);

   ArraySetAsSeries(time, true);

   bool do_full_recalc = (prev_calculated <= 0 || g_forceRecalc);
   if(g_forceRecalc)
   {
      DeleteAllArrowObjects();
      g_forceRecalc = false;
   }

   int calc_prev = do_full_recalc ? 0 : prev_calculated;

   int limit;
   if(calc_prev == 0)
      limit = bars_to_process;
   else
   {
      limit = rates_total - calc_prev + 1;
      if(limit > bars_to_process)
         limit = bars_to_process;
   }

   int to_copy = (calc_prev == 0) ? bars_to_process : (limit + 2);
   if(to_copy > bars_to_process) 
      to_copy = bars_to_process;
   if(to_copy > calc1) 
      to_copy = calc1;
   if(to_copy > calc2) 
      to_copy = calc2;

   if(to_copy <= 0)
      return(0);

   double m1[], s1[], m2[], s2[];
   ArraySetAsSeries(m1, true);
   ArraySetAsSeries(s1, true);
   ArraySetAsSeries(m2, true);
   ArraySetAsSeries(s2, true);

   if(CopyBuffer(hStoch1, 0, 0, to_copy, m1) < to_copy) return(0);
   if(CopyBuffer(hStoch1, 1, 0, to_copy, s1) < to_copy) return(0);
   if(CopyBuffer(hStoch2, 0, 0, to_copy, m2) < to_copy) return(0);
   if(CopyBuffer(hStoch2, 1, 0, to_copy, s2) < to_copy) return(0);

   int process_limit = (calc_prev == 0) ? bars_to_process : (limit + 1);
   if(process_limit > to_copy) 
      process_limit = to_copy;
   if(process_limit > bars_to_process) 
      process_limit = bars_to_process;

   // If initial/reset, clear old values beyond bars_to_process
   if(calc_prev == 0 && rates_total > bars_to_process)
   {
      for(int b = bars_to_process; b < rates_total; b++)
      {
         MainBuffer1[b]   = EMPTY_VALUE;
         SignalBuffer1[b] = EMPTY_VALUE;
         MainBuffer2[b]   = EMPTY_VALUE;
         SignalBuffer2[b] = EMPTY_VALUE;
         UpDotBuffer[b]   = EMPTY_VALUE;
         DnDotBuffer[b]   = EMPTY_VALUE;
         UpArrowBuffer[b] = EMPTY_VALUE;
         DnArrowBuffer[b] = EMPTY_VALUE;
      }
   }

   // --- Pass 1: Stochastics and Cross Signals ---
   for(int i = process_limit - 1; i >= 0; i--)
   {
      MainBuffer1[i]   = m1[i];
      SignalBuffer1[i] = s1[i];
      MainBuffer2[i]   = m2[i];
      SignalBuffer2[i] = s2[i];
      
      UpDotBuffer[i]   = EMPTY_VALUE;
      DnDotBuffer[i]   = EMPTY_VALUE;
      UpArrowBuffer[i] = EMPTY_VALUE;
      DnArrowBuffer[i] = EMPTY_VALUE;
      
      if(i < bars_to_process - 1)
      {
         bool s1_CrossUp = (MainBuffer1[i] > SignalBuffer1[i]) && (MainBuffer1[i+1] <= SignalBuffer1[i+1]);
         bool s1_CrossDn = (MainBuffer1[i] < SignalBuffer1[i]) && (MainBuffer1[i+1] >= SignalBuffer1[i+1]);
         bool s2_TrendUp = (MainBuffer2[i] > SignalBuffer2[i]);
         bool s2_TrendDn = (MainBuffer2[i] < SignalBuffer2[i]);
         
         if(s1_CrossUp && s2_TrendUp)
         {
            UpDotBuffer[i]   = MainBuffer1[i]; 
            UpArrowBuffer[i] = 5.0;          
         }
         
         if(s1_CrossDn && s2_TrendDn)
         {
            DnDotBuffer[i]   = MainBuffer1[i]; 
            DnArrowBuffer[i] = 95.0; // Use this buffer to flag existence
         }
      }
   }
   
   // --- Pass 2: Object Management & Daily Recount ---
   if(!IndicatorHidden)
   {
      int winIdx = ChartWindowFind(0, shortName);
      if(winIdx < 0) 
         winIdx = ChartWindowFind();
      if(winIdx < 0) 
         return(0); // Window not ready yet

      if(calc_prev == 0)
      {
         // Full recount for the entire bars_to_process window
         int currentDayKey = -1;
         int dayCountDn = 0;
         int dayCountUp = 0;

         for(int i = bars_to_process - 1; i >= 0; i--)
         {
            int barDayKey = GetDayKey(time[i]);
            if(barDayKey != currentDayKey)
            {
               currentDayKey = barDayKey;
               dayCountDn = 0;
               dayCountUp = 0;
            }

            if(DnArrowBuffer[i] != EMPTY_VALUE && DnArrowBuffer[i] != 0.0)
            {
               dayCountDn++;
               CreateDnArrowObject(time[i], dayCountDn, winIdx);
            }

            if(UpArrowBuffer[i] != EMPTY_VALUE && UpArrowBuffer[i] != 0.0)
            {
               dayCountUp++;
               CreateUpArrowObject(time[i], dayCountUp, winIdx);
            }
         }
      }
      else
      {
         // Intraday update: recount today's signals forward from midnight
         int startScanIdx = 0;
         int todayKey = GetDayKey(time[0]);
         while(startScanIdx + 1 < bars_to_process && GetDayKey(time[startScanIdx + 1]) == todayKey)
         {
            startScanIdx++;
         }

         int dayCountDn = 0;
         int dayCountUp = 0;

         for(int i = startScanIdx; i >= 0; i--)
         {
            if(DnArrowBuffer[i] != EMPTY_VALUE && DnArrowBuffer[i] != 0.0)
            {
               dayCountDn++;
               CreateDnArrowObject(time[i], dayCountDn, winIdx);
            }

            if(UpArrowBuffer[i] != EMPTY_VALUE && UpArrowBuffer[i] != 0.0)
            {
               dayCountUp++;
               CreateUpArrowObject(time[i], dayCountUp, winIdx);
            }
         }

         // Clean up bar 0 arrow objects if signal unpainted on tick
         if(DnArrowBuffer[0] == EMPTY_VALUE || DnArrowBuffer[0] == 0.0)
            ObjectDelete(0, "StochArrowDn_" + IntegerToString((long)time[0]));
         if(UpArrowBuffer[0] == EMPTY_VALUE || UpArrowBuffer[0] == 0.0)
            ObjectDelete(0, "StochArrowUp_" + IntegerToString((long)time[0]));
      }
   }
   else
   {
      DeleteAllArrowObjects();
   }
   
   // Force redraw to show objects immediately
   if(calc_prev == 0 || limit > 0) 
      ChartRedraw(0);
   
   return(rates_total);
}

//+------------------------------------------------------------------+
//| Create Down Arrow Object                                         |
//+------------------------------------------------------------------+
void CreateDnArrowObject(datetime t, int count, int winIdx)
{
   string name = "StochArrowDn_" + IntegerToString((long)t);
   int code = 140 + ((count - 1) % 10); 
   
   if(ObjectFind(0, name) < 0) 
   {
      if(!ObjectCreate(0, name, OBJ_ARROW, winIdx, t, 95.0)) 
      {
         Print("Failed to create object: ", name, " Error: ", GetLastError());
         return;
      }
   }
   ObjectSetInteger(0, name, OBJPROP_ARROWCODE, code);
   ObjectSetInteger(0, name, OBJPROP_COLOR, DnCounterColor);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP); // Arrow points down
   ObjectSetDouble(0, name, OBJPROP_PRICE, 95.0);
   ObjectSetInteger(0, name, OBJPROP_TIME, t); 
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

//+------------------------------------------------------------------+
//| Create Up Arrow Object                                           |
//+------------------------------------------------------------------+
void CreateUpArrowObject(datetime t, int count, int winIdx)
{
   string name = "StochArrowUp_" + IntegerToString((long)t);
   int code = 140 + ((count - 1) % 10); 
   
   if(ObjectFind(0, name) < 0) 
   {
      if(!ObjectCreate(0, name, OBJ_ARROW, winIdx, t, 5.0)) 
      {
         Print("Failed to create object: ", name, " Error: ", GetLastError());
         return;
      }
   }
   ObjectSetInteger(0, name, OBJPROP_ARROWCODE, code);
   ObjectSetInteger(0, name, OBJPROP_COLOR, UpCounterColor);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_BOTTOM); // Arrow points up
   ObjectSetDouble(0, name, OBJPROP_PRICE, 5.0);
   ObjectSetInteger(0, name, OBJPROP_TIME, t); 
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
