//+------------------------------------------------------------------+
//|                                     RMI_Trend_Sniper.mq5         |
//|                          Copyright 2026, TZack88 ported          |
//|                                      https://www.mql5.com        |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026"
#property link      "https://www.mql5.com"
#property version   "1.27"
#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots   3

//--- Plot 1 Properties (Center)
#property indicator_type1   DRAW_COLOR_LINE
#property indicator_color1  clrDodgerBlue,clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2
#property indicator_label1  "RRTH Center"

//--- Plot 2 Properties (Top)
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_color2  clrDodgerBlue,clrRed
#property indicator_style2  STYLE_DASH
#property indicator_width2  1
#property indicator_label2  "RRTH Top"

//--- Plot 3 Properties (Bottom)
#property indicator_type3   DRAW_COLOR_LINE
#property indicator_color3  clrDodgerBlue,clrRed
#property indicator_style3  STYLE_DASH
#property indicator_width3  1
#property indicator_label3  "RRTH Bottom"

//--- Enums
enum ENUM_ALERT_MODE
{
   ALERT_CURRENT,    // On Current Bar
   ALERT_CLOSED,     // On Just-Closed Bar
   ALERT_NONE        // No Alerts
};

//--- Inputs
input group "Timeframe Settings"
input ENUM_TIMEFRAMES InpCustomTimeframe = PERIOD_CURRENT; // Calculation Timeframe (PERIOD_CURRENT for auto)

input group "RMI Settings"
input int         InpLength   = 14;      
input int         InpPmom     = 66;      
input int         InpNmom     = 30;      

input group "TDFI Filter Settings"
input bool        InpUseTDFI      = true;  // Joint TDFI Filter Active
input int         InpTDFILen      = 13;
input double      InpTDFILevel    = 0.3;   // Threshold as a multiple of recent N-bar StDev of price changes
input int         InpTDFINormWindow = 50;  // Self-normalization lookback (bars)

input group "Visuals"
input bool        InpShowMA     = true;  // Show RRTH Center
input bool        InpShowBands  = true;  // Show RRTH Top/Bottom

input group "Signals"
input bool        InpShowArrows     = true;         // Show BUY/SELL Arrows
input color       InpArrowBullColor = clrDodgerBlue;// Bull Arrow Color
input color       InpArrowBearColor = clrRed;       // Bear Arrow Color
input int         InpArrowSize      = 2;            // Arrow Size
input ENUM_ALERT_MODE InpAlertMode  = ALERT_CURRENT;// Alert Mode

#define OBJ_PREFIX "SSSSSS_SIG_"

//--- Buffers
double BufRwmaCenter[];
double BufRwmaCenterColors[];
double BufTopBand[];
double BufTopBandColors[];
double BufBottomBand[];
double BufBottomBandColors[];

int atrHandle;
int emaHandle;
int mfiHandle;
ENUM_TIMEFRAMES calcTf;
datetime g_lastAlertBarTime = 0;

//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, BufRwmaCenter, INDICATOR_DATA);
   SetIndexBuffer(1, BufRwmaCenterColors, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, BufTopBand, INDICATOR_DATA);
   SetIndexBuffer(3, BufTopBandColors, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4, BufBottomBand, INDICATOR_DATA);
   SetIndexBuffer(5, BufBottomBandColors, INDICATOR_COLOR_INDEX);

   ArraySetAsSeries(BufRwmaCenter, true);
   ArraySetAsSeries(BufRwmaCenterColors, true);
   ArraySetAsSeries(BufTopBand, true);
   ArraySetAsSeries(BufTopBandColors, true);
   ArraySetAsSeries(BufBottomBand, true);
   ArraySetAsSeries(BufBottomBandColors, true);

   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_LINE);
   PlotIndexSetInteger(0, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 70);

   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_COLOR_LINE);
   PlotIndexSetInteger(1, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 70);

   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_COLOR_LINE);
   PlotIndexSetInteger(2, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, 70);

   calcTf = (InpCustomTimeframe == PERIOD_CURRENT) ? _Period : InpCustomTimeframe;

   atrHandle  = iATR(_Symbol, calcTf, 30);
   emaHandle  = iMA(_Symbol, calcTf, 5, 0, MODE_EMA, PRICE_CLOSE);
   mfiHandle  = iMFI(_Symbol, calcTf, InpLength, VOLUME_TICK);

   if(atrHandle == INVALID_HANDLE || emaHandle == INVALID_HANDLE || mfiHandle == INVALID_HANDLE)
   {
      Print("Failed to create standard handles. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   int total = ObjectsTotal(0, -1, -1);
   for(int i = total - 1; i >= 0; i--)
   {
      string nm = ObjectName(0, i, -1, -1);
      if(StringFind(nm, OBJ_PREFIX) == 0) ObjectDelete(0, nm);
   }
}

//+------------------------------------------------------------------+
void CreateSignalMarker(string name, datetime t, double price, bool isBull)
{
   if(ObjectFind(0, name) >= 0) ObjectDelete(0, name);
   ObjectCreate(0, name, OBJ_ARROW, 0, t, price);
   ObjectSetInteger(0, name, OBJPROP_ARROWCODE, isBull ? 233 : 234);
   ObjectSetInteger(0, name, OBJPROP_COLOR,  isBull ? InpArrowBullColor : InpArrowBearColor);
   ObjectSetInteger(0, name, OBJPROP_WIDTH,  InpArrowSize);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, isBull ? ANCHOR_TOP : ANCHOR_BOTTOM);
   ObjectSetInteger(0, name, OBJPROP_BACK,   false);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
}

//+------------------------------------------------------------------+
double ClosePriceAt(int mappedIdx, const double &close[])
{
   if(calcTf == _Period) return close[mappedIdx];
   return iClose(_Symbol, calcTf, mappedIdx);
}

//+------------------------------------------------------------------+
bool BuildLookbackMap(int i, int count, int rates_total, const datetime &time[], int &mapIdx[])
{
   ArrayResize(mapIdx, count);
   for(int k = 0; k < count; k++)
   {
      int ci = i + k;
      if(ci >= rates_total) return false;

      if(calcTf == _Period) { mapIdx[k] = ci; continue; }

      int mi = iBarShift(_Symbol, calcTf, time[ci]);
      if(mi < 0) return false;
      mapIdx[k] = mi;
   }
   return true;
}

//+------------------------------------------------------------------+
// Raw TDFI force using Standard Deviation of price changes over InpTDFILen
//+------------------------------------------------------------------+
bool ComputeTdfiRaw(int baseIdx, int rates_total, const datetime &time[], const double &close[], double &outValue)
{
   outValue = 0.0;
   int tmpMap[];
   if(!BuildLookbackMap(baseIdx, InpTDFILen + 1, rates_total, time, tmpMap))
      return false;

   double changes[];
   ArrayResize(changes, InpTDFILen);
   double sumChanges = 0.0;

   for(int k = 0; k < InpTDFILen; k++)
   {
      double currentClose = ClosePriceAt(tmpMap[k], close);
      double previousClose = ClosePriceAt(tmpMap[k+1], close);
      changes[k] = currentClose - previousClose;
      sumChanges += changes[k];
   }

   double meanChange = (InpTDFILen > 0) ? sumChanges / InpTDFILen : 0.0;

   double sumSqDiff = 0.0;
   for(int k = 0; k < InpTDFILen; k++)
   {
      double diff = changes[k] - meanChange;
      sumSqDiff += diff * diff;
   }

   // Standard deviation calculation with direction retained from the latest price change sign
   double stdev = (InpTDFILen > 1) ? MathSqrt(sumSqDiff / (InpTDFILen - 1)) : 0.0;
   double latestDirection = (changes[0] >= 0.0) ? 1.0 : -1.0;

   outValue = stdev * latestDirection;
   return true;
}

//+------------------------------------------------------------------+
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 < 70) return(0);

   ArraySetAsSeries(close, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(time, true);

   double atrVal[], emaVal[], mfiVal[];
   ArraySetAsSeries(atrVal, true);
   ArraySetAsSeries(emaVal, true);
   ArraySetAsSeries(mfiVal, true);

   int copied = CopyBuffer(atrHandle, 0, 0, rates_total, atrVal);
   if(copied <= 0) return(0);
   CopyBuffer(emaHandle, 0, 0, rates_total, emaVal);
   CopyBuffer(mfiHandle, 0, 0, rates_total, mfiVal);

   int start = prev_calculated - 1;
   if(prev_calculated <= 70 || start < 1 || start >= rates_total)  
      start = rates_total - 2;

   int realStart = start;
   if(prev_calculated <= 70)
   {
      realStart = rates_total - 1;
   }

   bool statePositive = false;
   bool stateNegative = false;

   double tfRatio = 1.0;
   if(calcTf != _Period && calcTf != PERIOD_CURRENT)
   {
      double baseSec = PeriodSeconds(_Period);
      double calcSec = PeriodSeconds(calcTf);
      if(baseSec > 0) tfRatio = calcSec / baseSec;
   }
   
   int effectiveRwmaPeriod = 20;
   if(tfRatio < 1.0 && tfRatio > 0)
   {
      effectiveRwmaPeriod = (int)MathMin(50, MathMax(5, 20.0 / tfRatio));
   }

   for(int i = realStart; i >= 0 && !IsStopped(); i--)
   {
      if(i >= rates_total - InpLength - effectiveRwmaPeriod - 5 || (i + effectiveRwmaPeriod) >= rates_total)  
      {
         BufRwmaCenter[i]    = EMPTY_VALUE;
         BufTopBand[i]       = EMPTY_VALUE;
         BufBottomBand[i]    = EMPTY_VALUE;
         continue;
      }

      int mapIdx[];
      if(!BuildLookbackMap(i, InpLength + 2, rates_total, time, mapIdx))
         continue;
      int h_idx      = mapIdx[0];
      int h_idx_prev = mapIdx[1];

      double upSum = 0, downSum = 0;
      for(int k = 0; k < InpLength; k++)
      {
         double change = ClosePriceAt(mapIdx[k], close) - ClosePriceAt(mapIdx[k+1], close);
         if(change > 0) upSum += change;
         else downSum -= change;
      }

      double rsi = (downSum == 0) ? 100.0 : (upSum == 0) ? 0.0 : (100.0 - (100.0 / (1.0 + (upSum / downSum))));
      double mfi_val_current = (h_idx < ArraySize(mfiVal)) ? mfiVal[h_idx] : 50.0;
      double rsi_mfi = (rsi + mfi_val_current) / 2.0;

      double upSumPrev = 0, downSumPrev = 0;
      for(int k = 0; k < InpLength; k++)
      {
         double change = ClosePriceAt(mapIdx[k+1], close) - ClosePriceAt(mapIdx[k+2], close);
         if(change > 0) upSumPrev += change;
         else downSumPrev -= change;
      }
      double rsi_prev = (downSumPrev == 0) ? 100.0 : (upSumPrev == 0) ? 0.0 : (100.0 - (100.0 / (1.0 + (upSumPrev / downSumPrev))));

      double mfi_val_prev = (h_idx_prev >= 0 && h_idx_prev < ArraySize(mfiVal)) ? mfiVal[h_idx_prev] : 50.0;
      double rsi_mfi_prev = (rsi_prev + mfi_val_prev) / 2.0;

      double emaChange = 0;
      if(h_idx >= 0 && h_idx + 1 < ArraySize(emaVal))
         emaChange = emaVal[h_idx] - emaVal[h_idx+1];

      bool base_p_mom = (rsi_mfi_prev < InpPmom) && (rsi_mfi > InpPmom) && (rsi_mfi > InpNmom) && (emaChange > 0);
      bool base_n_mom = (rsi_mfi < InpNmom) && (emaChange < 0);

      // --- TDFI: self-normalization against recent StDev window ---
      double tdfiWindow[];
      ArrayResize(tdfiWindow, InpTDFINormWindow + 1);
      bool tdfiOk = true;
      for(int w = 0; w <= InpTDFINormWindow && tdfiOk; w++)
         tdfiOk = ComputeTdfiRaw(i + w, rates_total, time, close, tdfiWindow[w]);

      bool tdfi_pass_p = false;
      bool tdfi_pass_n = false;
      if(!InpUseTDFI)
      {
         tdfi_pass_p = true;
         tdfi_pass_n = true;
      }
      else if(tdfiOk)
      {
         double sumAbsCurrent = 0.0, sumAbsPrev = 0.0;
         for(int w = 0; w < InpTDFINormWindow; w++)
            sumAbsCurrent += MathAbs(tdfiWindow[w]);
         for(int w = 1; w <= InpTDFINormWindow; w++)
            sumAbsPrev += MathAbs(tdfiWindow[w]);

         double meanAbsCurrent = sumAbsCurrent / InpTDFINormWindow;
         double meanAbsPrev    = sumAbsPrev    / InpTDFINormWindow;

         double tdfi_norm      = (meanAbsCurrent > 0) ? tdfiWindow[0] / meanAbsCurrent : 0.0;
         double tdfi_prev_norm = (meanAbsPrev    > 0) ? tdfiWindow[1] / meanAbsPrev    : 0.0;

         tdfi_pass_p = (tdfi_norm >= InpTDFILevel && tdfi_norm >= tdfi_prev_norm);
         tdfi_pass_n = (tdfi_norm <= -InpTDFILevel && tdfi_norm <= tdfi_prev_norm);
      }

      bool p_mom = base_p_mom && (!InpUseTDFI || tdfi_pass_p);
      bool n_mom = base_n_mom && (!InpUseTDFI || tdfi_pass_n);

      bool wasPositive = statePositive;
      bool wasNegative = stateNegative;

      if(p_mom) { statePositive = true; stateNegative = false; }
      else if(n_mom) { statePositive = false; stateNegative = true; }

      bool newPositive = statePositive && !wasPositive;
      bool newNegative = stateNegative && !wasNegative;

      string sigName = OBJ_PREFIX + (string)time[i];
      if(newPositive || newNegative)
      {
         if(InpShowArrows)
         {
            double sigPrice = newPositive ? low[i] : high[i];
            CreateSignalMarker(sigName, time[i], sigPrice, newPositive);
         }

         bool alertBarMatch = (InpAlertMode == ALERT_CURRENT && i == 0) ||
                              (InpAlertMode == ALERT_CLOSED  && i == 1);
         if(alertBarMatch && time[i] != g_lastAlertBarTime)
         {
            string msg = _Symbol + " " + EnumToString((ENUM_TIMEFRAMES)_Period) +
                         " RMI Trend Sniper: " + (newPositive ? "BUY" : "SELL");
            Alert(msg);
            SendNotification(msg);
            g_lastAlertBarTime = time[i];
         }
      }
      else if(i == 0 && ObjectFind(0, sigName) >= 0)
      {
         ObjectDelete(0, sigName);
      }

      double atr_value = (h_idx < ArraySize(atrVal)) ? atrVal[h_idx] : 0;

      double band = MathMin(atr_value * 0.3, close[i] * (0.3 / 100.0)) * 4.0;
      if(calcTf == PERIOD_CURRENT)
      {
         band *= 1.0;
      }
      else
      {
         double adjustment = MathSqrt(MathMax(0.1, tfRatio));
         band *= adjustment;
      }

      double sumWeight = 0;
      double sumProd = 0;
      for(int p = 0; p < effectiveRwmaPeriod && (i+p) < rates_total; p++)
      {
         double r = high[i+p] - low[i+p];
         sumWeight += r;
      }
      
      double rwma = close[i];
      if(sumWeight > 0)
      {
         for(int p = 0; p < effectiveRwmaPeriod && (i+p) < rates_total; p++)
         {
            double r = high[i+p] - low[i+p];
            double weight = r / sumWeight;
            sumProd += close[i+p] * weight;
         }
         rwma = sumProd;
      }

      double finalRwma = rwma;
      if(statePositive) finalRwma = rwma - band;
      else if(stateNegative) finalRwma = rwma + band;

      int colorIdx = stateNegative ? 1 : 0;

      if(InpShowMA)
      {
         BufRwmaCenter[i]        = finalRwma;
         BufRwmaCenterColors[i]  = colorIdx;
      }
      else
      {
         BufRwmaCenter[i]        = EMPTY_VALUE;
      }

      if(InpShowBands)
      {
         BufTopBand[i]           = finalRwma + band;
         BufTopBandColors[i]     = colorIdx;

         BufBottomBand[i]        = finalRwma - band;
         BufBottomBandColors[i]  = colorIdx;
      }
      else
      {
         BufTopBand[i]           = EMPTY_VALUE;
         BufBottomBand[i]        = EMPTY_VALUE;
      }
   }

   return(rates_total);
}
//+------------------------------------------------------------------+