//+------------------------------------------------------------------+
//|                                            FXP_StochRsiZones.mq5 |
//|                                                     FXPIPS ONLINE |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/stochastic-rsi-zones/"
#property version   "1.00"
#property description "Stochastic RSI %K and %D with overbought/oversold zones."
#property description "Arrows mark confirmed exits from the extreme zones."
/*
   License: MIT. Copyright (c) 2026 FXPIPS ONLINE.
   Permission is hereby granted, free of charge, to any person obtaining a copy of this
   software, to use, copy, modify, merge, publish and distribute it, provided this notice
   is included. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.

   Educational tool - not financial advice. Trading involves substantial risk of loss.
*/
#property indicator_separate_window
#property indicator_minimum -5
#property indicator_maximum 105
#property indicator_buffers 8
#property indicator_plots   4

#property indicator_label1  "%K"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2

#property indicator_label2  "%D"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrDarkOrange
#property indicator_width2  1

#property indicator_label3  "Oversold exit"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrLimeGreen
#property indicator_width3  2

#property indicator_label4  "Overbought exit"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrRed
#property indicator_width4  2

input group "Stochastic RSI"
input int    InpRsiLen    = 14;   // RSI length
input int    InpStochLen  = 14;   // Stochastic lookback on RSI
input int    InpKSmooth   = 3;    // %K smoothing
input int    InpDSmooth   = 3;    // %D smoothing
input group "Zones"
input double InpUpper     = 80.0; // Overbought level
input double InpLower     = 20.0; // Oversold level
input bool   InpUseD      = false;// Require %K above/below %D for the exit arrow
input group "Alerts"
input bool   InpAlerts    = false; // Pop-up alert on new zone exit
input bool   InpPush      = false; // Push notification on new zone exit

double g_k[], g_d[], g_buy[], g_sell[], g_rsi[], g_raw[], g_gain[], g_loss[];
int g_rLen, g_sLen, g_kLen, g_dLen, g_rawBegin, g_kBegin, g_dBegin;
datetime g_lastAlert = 0;

//+------------------------------------------------------------------+
int OnInit()
{
   g_rLen = MathMax(2, InpRsiLen);
   g_sLen = MathMax(2, InpStochLen);
   g_kLen = MathMax(1, InpKSmooth);
   g_dLen = MathMax(1, InpDSmooth);
   g_rawBegin = g_rLen + g_sLen - 1;
   g_kBegin = g_rawBegin + g_kLen - 1;
   g_dBegin = g_kBegin + g_dLen - 1;

   SetIndexBuffer(0, g_k,    INDICATOR_DATA);
   SetIndexBuffer(1, g_d,    INDICATOR_DATA);
   SetIndexBuffer(2, g_buy,  INDICATOR_DATA);
   SetIndexBuffer(3, g_sell, INDICATOR_DATA);
   SetIndexBuffer(4, g_rsi,  INDICATOR_CALCULATIONS);
   SetIndexBuffer(5, g_raw,  INDICATOR_CALCULATIONS);
   SetIndexBuffer(6, g_gain, INDICATOR_CALCULATIONS);
   SetIndexBuffer(7, g_loss, INDICATOR_CALCULATIONS);

   ArraySetAsSeries(g_k, false);    ArraySetAsSeries(g_d, false);
   ArraySetAsSeries(g_buy, false);  ArraySetAsSeries(g_sell, false);
   ArraySetAsSeries(g_rsi, false);  ArraySetAsSeries(g_raw, false);
   ArraySetAsSeries(g_gain, false); ArraySetAsSeries(g_loss, false);

   for(int p = 0; p < 4; p++)
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, g_kBegin);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, g_dBegin);
   PlotIndexSetInteger(2, PLOT_ARROW, 233); PlotIndexSetInteger(2, PLOT_ARROW_SHIFT, 12);
   PlotIndexSetInteger(3, PLOT_ARROW, 234); PlotIndexSetInteger(3, PLOT_ARROW_SHIFT, -12);

   IndicatorSetInteger(INDICATOR_LEVELS, 3);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpUpper);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 50.0);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, InpLower);
   IndicatorSetInteger(INDICATOR_DIGITS, 2);
   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("FXP Stochastic RSI Zones (%d, %d, %d, %d)", g_rLen, g_sLen, g_kLen, g_dLen));
   return(INIT_SUCCEEDED);
}

void Notify(const datetime barTime, const string msg)
{
   if(barTime == g_lastAlert) return;
   g_lastAlert = barTime;
   string text = StringFormat("%s %s: %s", _Symbol, EnumToString((ENUM_TIMEFRAMES)_Period), msg);
   if(InpAlerts) Alert(text);
   if(InpPush)   SendNotification(text);
}

double Sma(const double &src[], const int i, const int len)
{
   double s = 0.0;
   for(int k = 0; k < len; k++) s += src[i - k];
   return(s / len);
}

//+------------------------------------------------------------------+
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 <= g_dBegin + 2) return(0);

   int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;

   for(int i = start; i < rates_total; i++)
   {
      g_buy[i] = EMPTY_VALUE; g_sell[i] = EMPTY_VALUE;

      // Wilder RSI
      if(i == 0) { g_gain[i] = 0.0; g_loss[i] = 0.0; g_rsi[i] = 50.0; }
      else
      {
         double ch = close[i] - close[i - 1];
         double up = MathMax(ch, 0.0), dn = MathMax(-ch, 0.0);
         if(i < g_rLen)
         {
            g_gain[i] = g_gain[i - 1] + up; g_loss[i] = g_loss[i - 1] + dn;   // running sums
            g_rsi[i] = 50.0;
         }
         else
         {
            if(i == g_rLen)
            {
               g_gain[i] = (g_gain[i - 1] + up) / g_rLen;
               g_loss[i] = (g_loss[i - 1] + dn) / g_rLen;
            }
            else
            {
               g_gain[i] = (g_gain[i - 1] * (g_rLen - 1) + up) / g_rLen;
               g_loss[i] = (g_loss[i - 1] * (g_rLen - 1) + dn) / g_rLen;
            }
            g_rsi[i] = (g_loss[i] == 0.0) ? 100.0 : 100.0 - 100.0 / (1.0 + g_gain[i] / g_loss[i]);
         }
      }

      // Stochastic of RSI
      if(i >= g_rawBegin)
      {
         double hi = g_rsi[i], lo = g_rsi[i];
         for(int k = 1; k < g_sLen; k++) { hi = MathMax(hi, g_rsi[i - k]); lo = MathMin(lo, g_rsi[i - k]); }
         g_raw[i] = (hi > lo) ? 100.0 * (g_rsi[i] - lo) / (hi - lo) : (i > 0 ? g_raw[i - 1] : 50.0);
      }
      else g_raw[i] = 50.0;

      g_k[i] = (i >= g_kBegin) ? Sma(g_raw, i, g_kLen) : EMPTY_VALUE;
      g_d[i] = (i >= g_dBegin) ? Sma(g_k, i, g_dLen) : EMPTY_VALUE;

      // Zone exits on closed bars
      if(i > g_dBegin && i < rates_total - 1)
      {
         if(g_k[i] > InpLower && g_k[i - 1] <= InpLower && (!InpUseD || g_k[i] > g_d[i]))
            g_buy[i] = g_k[i];
         if(g_k[i] < InpUpper && g_k[i - 1] >= InpUpper && (!InpUseD || g_k[i] < g_d[i]))
            g_sell[i] = g_k[i];
      }
   }

   if(prev_calculated > 0 && (InpAlerts || InpPush))
   {
      int b = rates_total - 2;
      if(g_buy[b] != EMPTY_VALUE)  Notify(time[b], "Stochastic RSI left the oversold zone");
      if(g_sell[b] != EMPTY_VALUE) Notify(time[b], "Stochastic RSI left the overbought zone");
   }
   return(rates_total);
}

void OnDeinit(const int reason)
{
   // No handles or chart objects are created by this indicator.
}
//+------------------------------------------------------------------+
