//+------------------------------------------------------------------+
//|                        stochastic-rsi-reversal-ea.mq5            |
//|  A Stochastic RSI reversal EA: trades the %K/%D crossover while  |
//|  the oscillator is still inside its oversold or overbought zone  |
//|  (custom calculated — no built-in Stochastic RSI function),      |
//|  with ATR-based Stop Loss/Take Profit and risk-based position    |
//|  sizing. One position at a time.                                  |
//|  EDUCATIONAL — test on a demo account first. Past performance     |
//|  does not guarantee future results. This is not financial advice.|
//|  Source: web-forex (educational, free to use and modify)          |
//+------------------------------------------------------------------+
#property copyright "web-forex"
#include <Trade\Trade.mqh>

CTrade trade;

input string       ___Strategy___       = "--- Stochastic RSI Reversal Strategy ---";
input int          RSIPeriod            = 14;
input int          StochPeriod          = 14;
input int          SlowingPeriod        = 3;
input int          SignalPeriod         = 3;
input double       OversoldLevel        = 20.0;
input double       OverboughtLevel      = 80.0;

input string       ___RiskManagement___ = "--- Risk Management ---";
input int          ATRPeriod            = 14;
input double       ATRMultiplier        = 2.0;
input double       RiskRewardRatio      = 2.0;
input bool         UseFixedLot          = false;
input double       FixedLotSize         = 0.01;
input double       RiskPercent          = 1.0;

input string       ___Filters___        = "--- Filters ---";
input int          MaxSpreadPoints      = 30;

input string       ___General___        = "--- General ---";
input ulong        MagicNumber          = 20260911;
input bool         EnableTrading        = true;

int atrHandle = INVALID_HANDLE;
int rsiHandle = INVALID_HANDLE;
datetime lastBarTime = 0;

double rsiArr[];

int OnInit()
{
   trade.SetExpertMagicNumber(MagicNumber);

   //--- MQL5 indicator functions return handles; values come from CopyBuffer
   atrHandle = iATR(_Symbol, _Period, ATRPeriod);
   rsiHandle = iRSI(_Symbol, _Period, RSIPeriod, PRICE_CLOSE);
   if (atrHandle == INVALID_HANDLE || rsiHandle == INVALID_HANDLE)
      return(INIT_FAILED);

   ArraySetAsSeries(rsiArr, true);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   IndicatorRelease(atrHandle);
   IndicatorRelease(rsiHandle);
}

void OnTick()
{
   if (!EnableTrading)
      return;

   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if (currentBarTime == lastBarTime)
      return; // only evaluate once per new bar
   lastBarTime = currentBarTime;

   if (CountOpenPositions() > 0)
      return; // one position at a time

   long spreadPoints = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   if (spreadPoints > MaxSpreadPoints)
      return; // spread too wide right now, skip this bar

   int needed = StochPeriod + SlowingPeriod + SignalPeriod + 6;
   if (CopyBuffer(rsiHandle, 0, 0, needed, rsiArr) < needed)
      return;

   double kLast = StochRsiK(1), kPrev = StochRsiK(2);
   double dLast = StochRsiD(1), dPrev = StochRsiD(2);

   double atrArr[];
   ArraySetAsSeries(atrArr, true);
   if (CopyBuffer(atrHandle, 0, 0, 2, atrArr) < 2) return;
   double atr = atrArr[1];

   bool bullCross = (kPrev <= dPrev && kLast > dLast && kPrev < OversoldLevel);
   bool bearCross = (kPrev >= dPrev && kLast < dLast && kPrev > OverboughtLevel);

   if (bullCross)
      OpenTrade(ORDER_TYPE_BUY, atr);
   else if (bearCross)
      OpenTrade(ORDER_TYPE_SELL, atr);
}

//--- raw Stochastic RSI: where the current RSI sits inside its own StochPeriod range
double RawStochRsi(int shift)
{
   double rsiNow = rsiArr[shift];
   double highest = rsiNow;
   double lowest  = rsiNow;

   for (int j = 1; j < StochPeriod; j++)
   {
      double v = rsiArr[shift + j];
      if (v > highest) highest = v;
      if (v < lowest)  lowest  = v;
   }

   double range = highest - lowest;
   if (range <= 0.0)
      return(50.0); // RSI was flat across the whole window — call it neutral
   return(100.0 * (rsiNow - lowest) / range);
}

//--- %K is the raw line smoothed over SlowingPeriod bars
double StochRsiK(int shift)
{
   double sum = 0.0;
   for (int j = 0; j < SlowingPeriod; j++)
      sum += RawStochRsi(shift + j);
   return(sum / SlowingPeriod);
}

//--- %D is the moving average of %K
double StochRsiD(int shift)
{
   double sum = 0.0;
   for (int j = 0; j < SignalPeriod; j++)
      sum += StochRsiK(shift + j);
   return(sum / SignalPeriod);
}

int CountOpenPositions()
{
   int count = 0;
   for (int i = 0; i < PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if (ticket > 0 && PositionSelectByTicket(ticket))
      {
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == (long)MagicNumber)
            count++;
      }
   }
   return(count);
}

void OpenTrade(ENUM_ORDER_TYPE type, double atr)
{
   double slDistance = atr * ATRMultiplier;
   double tpDistance = slDistance * RiskRewardRatio;
   double lots       = CalculateLotSize(slDistance);
   double price, sl, tp;

   if (type == ORDER_TYPE_BUY)
   {
      price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      sl = price - slDistance;
      tp = price + tpDistance;
      trade.Buy(lots, _Symbol, price, sl, tp, "Stochastic RSI Reversal EA");
   }
   else
   {
      price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      sl = price + slDistance;
      tp = price - tpDistance;
      trade.Sell(lots, _Symbol, price, sl, tp, "Stochastic RSI Reversal EA");
   }
}

double CalculateLotSize(double slDistance)
{
   if (UseFixedLot || slDistance <= 0)
      return(NormalizeLotSize(FixedLotSize));

   double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
   double tickValue   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if (tickSize <= 0 || tickValue <= 0)
      return(NormalizeLotSize(FixedLotSize));

   double slTicks = slDistance / tickSize;
   double lots    = riskAmount / (slTicks * tickValue);

   return(NormalizeLotSize(lots));
}

double NormalizeLotSize(double lots)
{
   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   if (lotStep <= 0)
      return(minLot);

   lots = MathFloor(lots / lotStep) * lotStep;
   if (lots < minLot)
      lots = minLot;
   if (lots > maxLot)
      lots = maxLot;
   return(lots);
}
