//+------------------------------------------------------------------+
//|                                     Sweep Reversal Map Herman.mq4|
//|                                  Copyright 2026, Herman Trading  |
//+------------------------------------------------------------------+
#property copyright "Herman Trading"
#property link      "https://mozilla.org/MPL/2.0/"
#property version   "1.01"
#property indicator_chart_window
#property indicator_buffers 0

// --- Constants ---
#define PREFIX "SRM_"

// --- Inputs ---
input string   Group_Detection        = "--- Detection ---"; 
input int      InpSwingLength        = 5;                   // Swing length
input int      InpAtrLength          = 14;                  // ATR length
input double   InpMinPenetration     = 0.0;                 // Minimum sweep penetration (ATR)

input string   Group_Confirmation     = "--- Confirmation ---"; 
input int      InpStructureLength    = 3;                   // Local structure length
input int      InpMaxConfirmBars     = 12;                  // Maximum confirmation bars
input double   InpMinDisplacement    = 0.2;                 // Minimum displacement body (ATR)

input string   Group_Style            = "--- Style ---";    
input bool     InpShowDeveloping     = true;                // Show developing reversals
input int      InpConfirmedExtension = 2;                   // Confirmed box extension
input int      InpMaxHistory         = 80;                  // Historical setups
input color    InpZoneColor          = C'255,159,67';       // Reversal zone
input color    InpLevelColor         = C'127,29,29';        // Sweep level and marker
input int      InpMaxBars            = 2000;                // Maximum historical bars

// --- History Structure ---
struct SetupObjects
{
   string boxName;
   string lineName;
   string labelName;
};

SetupObjects g_history[];
int g_setupCounter = 0;
datetime g_lastAlertTime = 0;

// --- Global State Variables (for persistent tracking) ---
double g_latestSwingHigh = 0.0, g_latestSwingLow = 0.0;
datetime g_latestSwingHighTime = 0, g_latestSwingLowTime = 0;
bool g_swingHighAvailable = false, g_swingLowAvailable = false;

bool g_bearishActive = false, g_bearishReclaimed = false;
int g_bearishStartBarIndex = 0;
datetime g_bearishStartBarTime = 0, g_bearishSwingTime = 0;
double g_bearishLevel = 0.0, g_bearishExtreme = 0.0, g_bearishConfirmationLevel = 0.0;
string g_curBearishBox = "", g_curBearishLine = "", g_curBearishLabel = "";

bool g_bullishActive = false, g_bullishReclaimed = false;
int g_bullishStartBarIndex = 0;
datetime g_bullishStartBarTime = 0, g_bullishSwingTime = 0;
double g_bullishLevel = 0.0, g_bullishExtreme = 0.0, g_bullishConfirmationLevel = 0.0;
string g_curBullishBox = "", g_curBullishLine = "", g_curBullishLabel = "";

//+------------------------------------------------------------------+
//| Initialization & Cleanup                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   DeleteObjectsByPrefix(PREFIX);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   DeleteObjectsByPrefix(PREFIX);
}

void ResetState()
{
   g_latestSwingHigh = 0.0; g_latestSwingLow = 0.0;
   g_latestSwingHighTime = 0; g_latestSwingLowTime = 0;
   g_swingHighAvailable = false; g_swingLowAvailable = false;

   g_bearishActive = false; g_bearishReclaimed = false;
   g_bearishStartBarIndex = 0; g_bearishStartBarTime = 0; g_bearishSwingTime = 0;
   g_bearishLevel = 0.0; g_bearishExtreme = 0.0; g_bearishConfirmationLevel = 0.0;
   g_curBearishBox = ""; g_curBearishLine = ""; g_curBearishLabel = "";

   g_bullishActive = false; g_bullishReclaimed = false;
   g_bullishStartBarIndex = 0; g_bullishStartBarTime = 0; g_bullishSwingTime = 0;
   g_bullishLevel = 0.0; g_bullishExtreme = 0.0; g_bullishConfirmationLevel = 0.0;
   g_curBullishBox = ""; g_curBullishLine = ""; g_curBullishLabel = "";

   g_setupCounter = 0;
   ArrayResize(g_history, 0);
}

//+------------------------------------------------------------------+
//| Helper Functions                                                 |
//+------------------------------------------------------------------+
void DeleteObjectsByPrefix(string prefix)
{
   for(int i = ObjectsTotal() - 1; i >= 0; i--)
   {
      string name = ObjectName(i);
      if(StringFind(name, prefix) == 0) ObjectDelete(name);
   }
}

datetime GetBarTime(int barIdx, const datetime &time[])
{
   int barsTotal = ArraySize(time);
   if(barIdx >= 0 && barIdx < barsTotal) return time[barIdx];
   if(barIdx < 0) return time[0] + MathAbs(barIdx) * PeriodSeconds();
   return time[barsTotal - 1];
}

void CreateBox(string name, datetime t1, double p1, datetime t2, double p2, color col, ENUM_LINE_STYLE style, bool fill = false)
{
   if(col == clrNONE) return;
   ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2);
   ObjectSetInteger(0, name, OBJPROP_COLOR, col);
   ObjectSetInteger(0, name, OBJPROP_STYLE, style);
   ObjectSetInteger(0, name, OBJPROP_BACK, fill); // false = transparent/hollow, true = solid/filled
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

void UpdateBox(string name, datetime t1, double p1, datetime t2, double p2, color col = clrNONE, ENUM_LINE_STYLE style = STYLE_SOLID, int fill = -1)
{
   if(ObjectFind(0, name) < 0) return;
   ObjectSetInteger(0, name, OBJPROP_TIME1, t1);
   ObjectSetDouble(0, name, OBJPROP_PRICE1, p1);
   ObjectSetInteger(0, name, OBJPROP_TIME2, t2);
   ObjectSetDouble(0, name, OBJPROP_PRICE2, p2);
   if(col != clrNONE) ObjectSetInteger(0, name, OBJPROP_COLOR, col);
   ObjectSetInteger(0, name, OBJPROP_STYLE, style);
   if(fill != -1) ObjectSetInteger(0, name, OBJPROP_BACK, (bool)fill);
}

void CreateLine(string name, datetime t1, double p1, datetime t2, double p2, color col, ENUM_LINE_STYLE style)
{
   if(col == clrNONE) return;
   ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2);
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
   ObjectSetInteger(0, name, OBJPROP_COLOR, col);
   ObjectSetInteger(0, name, OBJPROP_STYLE, style);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

void UpdateLine(string name, datetime t1, double p1, datetime t2, double p2, color col = clrNONE, ENUM_LINE_STYLE style = STYLE_SOLID)
{
   if(ObjectFind(0, name) < 0) return;
   ObjectSetInteger(0, name, OBJPROP_TIME1, t1);
   ObjectSetDouble(0, name, OBJPROP_PRICE1, p1);
   ObjectSetInteger(0, name, OBJPROP_TIME2, t2);
   ObjectSetDouble(0, name, OBJPROP_PRICE2, p2);
   if(col != clrNONE) ObjectSetInteger(0, name, OBJPROP_COLOR, col);
   ObjectSetInteger(0, name, OBJPROP_STYLE, style);
}

void CreateLabel(string name, datetime t, double p, string text, color col)
{
   if(col == clrNONE) return;
   ObjectCreate(0, name, OBJ_TEXT, 0, t, p);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, col);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

void UpdateLabel(string name, color col)
{
   if(ObjectFind(0, name) < 0) return;
   ObjectSetInteger(0, name, OBJPROP_COLOR, col);
}

void PushAndTrimHistory(string boxName, string lineName, string labelName, int maxHistory)
{
   int sz = ArraySize(g_history);
   ArrayResize(g_history, sz + 1);
   g_history[sz].boxName = boxName;
   g_history[sz].lineName = lineName;
   g_history[sz].labelName = labelName;

   while(ArraySize(g_history) > maxHistory)
   {
      ObjectDelete(g_history[0].boxName);
      ObjectDelete(g_history[0].lineName);
      ObjectDelete(g_history[0].labelName);

      int currentSz = ArraySize(g_history);
      for(int k = 0; k < currentSz - 1; k++) g_history[k] = g_history[k + 1];
      ArrayResize(g_history, currentSz - 1);
   }
}

bool IsPivotHigh(int i, int swingLen, const double &high[], int totalBars)
{
   int candidate = i + swingLen;
   if(candidate + swingLen >= totalBars || candidate < 0) return false;
   double candHigh = high[candidate];
   for(int k = 1; k <= swingLen; k++)
   {
      if(high[candidate + k] >= candHigh) return false;
      if(high[candidate - k] > candHigh) return false;
   }
   return true;
}

bool IsPivotLow(int i, int swingLen, const double &low[], int totalBars)
{
   int candidate = i + swingLen;
   if(candidate + swingLen >= totalBars || candidate < 0) return false;
   double candLow = low[candidate];
   for(int k = 1; k <= swingLen; k++)
   {
      if(low[candidate + k] <= candLow) return false;
      if(low[candidate - k] < candLow) return false;
   }
   return true;
}

//+------------------------------------------------------------------+
//| Main Iteration                                                   |
//+------------------------------------------------------------------+
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 <= InpSwingLength * 2 + InpStructureLength + 1) return(0);

   ArraySetAsSeries(high, true); ArraySetAsSeries(low, true);
   ArraySetAsSeries(open, true); ArraySetAsSeries(close, true);
   ArraySetAsSeries(time, true);

   int limit = rates_total - prev_calculated;

   // Reset and compute from max depth if initialized or history disrupted
   if(prev_calculated == 0 || limit > 1)
   {
      limit = MathMin(rates_total - 1 - (InpSwingLength * 2 + 1), InpMaxBars);
      DeleteObjectsByPrefix(PREFIX);
      ResetState();
   }

   bool alertBearish = false, alertBullish = false;

   // Process fully closed bars (simulating Pine's `if barstate.isconfirmed`)
   for(int i = limit; i >= 1; i--)
   {
      int barIndex = rates_total - 1 - i;
      color devBoxCol, devLineCol;
      datetime extTime;

      double atrValue = iATR(Symbol(), Period(), InpAtrLength, i);

      if(IsPivotHigh(i, InpSwingLength, high, rates_total)) {
         g_latestSwingHigh = high[i + InpSwingLength];
         g_latestSwingHighTime = time[i + InpSwingLength];
         g_swingHighAvailable = true;
      }
      if(IsPivotLow(i, InpSwingLength, low, rates_total)) {
         g_latestSwingLow = low[i + InpSwingLength];
         g_latestSwingLowTime = time[i + InpSwingLength];
         g_swingLowAvailable = true;
      }

      double priorStructureLow = low[i + 1];
      for(int k = 2; k <= InpStructureLength; k++) if(i + k < rates_total && low[i + k] < priorStructureLow) priorStructureLow = low[i + k];
      
      double priorStructureHigh = high[i + 1];
      for(int j = 2; j <= InpStructureLength; j++) if(i + j < rates_total && high[i + j] > priorStructureHigh) priorStructureHigh = high[i + j];

      double candleBody = MathAbs(close[i] - open[i]);
      bool displacementPass = (atrValue > 0) && (candleBody >= atrValue * InpMinDisplacement);
      double minimumPenetration = atrValue * InpMinPenetration;

      // --- Bearish State Logic ---
      bool bearishSweep = !g_bearishActive && g_swingHighAvailable && (g_latestSwingHigh > 0) && (priorStructureLow > 0) && (high[i] >= g_latestSwingHigh + minimumPenetration);

      if(bearishSweep)
      {
         g_bearishActive = true;
         g_bearishReclaimed = (close[i] < g_latestSwingHigh);
         g_bearishStartBarIndex = barIndex;
         g_bearishStartBarTime = time[i];
         g_bearishLevel = g_latestSwingHigh;
         g_bearishExtreme = high[i];
         g_bearishConfirmationLevel = priorStructureLow;
         g_bearishSwingTime = g_latestSwingHighTime;
         g_swingHighAvailable = false;

         g_setupCounter++;
         g_curBearishBox = PREFIX + "Box_" + IntegerToString(g_setupCounter);
         g_curBearishLine = PREFIX + "Line_" + IntegerToString(g_setupCounter);
         g_curBearishLabel = PREFIX + "Lbl_" + IntegerToString(g_setupCounter);

         devBoxCol = InpShowDeveloping ? InpZoneColor : clrNONE;
         devLineCol = InpShowDeveloping ? InpLevelColor : clrNONE;

         // create unconfirmed box as hollow (fill = false)
         CreateBox(g_curBearishBox, time[i], g_bearishExtreme, GetBarTime(i - 1, time), g_bearishLevel, devBoxCol, STYLE_SOLID, false);
         CreateLine(g_curBearishLine, g_bearishSwingTime, g_bearishLevel, time[i], g_bearishLevel, devLineCol, STYLE_DOT);
         CreateLabel(g_curBearishLabel, g_bearishSwingTime, g_bearishLevel, "x", devLineCol);
      }

      if(g_bearishActive)
      {
         g_bearishExtreme = MathMax(g_bearishExtreme, high[i]);
         g_bearishReclaimed = g_bearishReclaimed || (close[i] < g_bearishLevel);

         UpdateBox(g_curBearishBox, g_bearishStartBarTime, g_bearishExtreme, GetBarTime(i - 1, time), g_bearishLevel);
         UpdateLine(g_curBearishLine, g_bearishSwingTime, g_bearishLevel, time[i], g_bearishLevel);

         bool confirmBearish = g_bearishReclaimed && (close[i] < g_bearishConfirmationLevel) && displacementPass;
         bool expireBearish = (barIndex - g_bearishStartBarIndex) > InpMaxConfirmBars;

         if(confirmBearish)
         {
            if(i == 1) alertBearish = true;
            extTime = GetBarTime(i - InpConfirmedExtension, time);
            
            // update box as confirmed/solid (fill = true)
            UpdateBox(g_curBearishBox, g_bearishStartBarTime, g_bearishExtreme, extTime, g_bearishLevel, InpZoneColor, STYLE_SOLID, true);
            UpdateLine(g_curBearishLine, g_bearishSwingTime, g_bearishLevel, time[i], g_bearishLevel, InpLevelColor, STYLE_SOLID);
            UpdateLabel(g_curBearishLabel, InpLevelColor);

            PushAndTrimHistory(g_curBearishBox, g_curBearishLine, g_curBearishLabel, InpMaxHistory);

            g_bearishActive = false; g_curBearishBox = ""; g_curBearishLine = ""; g_curBearishLabel = "";
         }
         else if(expireBearish)
         {
            ObjectDelete(g_curBearishBox); ObjectDelete(g_curBearishLine); ObjectDelete(g_curBearishLabel);
            g_bearishActive = false; g_curBearishBox = ""; g_curBearishLine = ""; g_curBearishLabel = "";
         }
      }

      // --- Bullish State Logic ---
      bool bullishSweep = !g_bullishActive && g_swingLowAvailable && (g_latestSwingLow > 0) && (priorStructureHigh > 0) && (low[i] <= g_latestSwingLow - minimumPenetration);

      if(bullishSweep)
      {
         g_bullishActive = true;
         g_bullishReclaimed = (close[i] > g_latestSwingLow);
         g_bullishStartBarIndex = barIndex;
         g_bullishStartBarTime = time[i];
         g_bullishLevel = g_latestSwingLow;
         g_bullishExtreme = low[i];
         g_bullishConfirmationLevel = priorStructureHigh;
         g_bullishSwingTime = g_latestSwingLowTime;
         g_swingLowAvailable = false;

         g_setupCounter++;
         g_curBullishBox = PREFIX + "Box_" + IntegerToString(g_setupCounter);
         g_curBullishLine = PREFIX + "Line_" + IntegerToString(g_setupCounter);
         g_curBullishLabel = PREFIX + "Lbl_" + IntegerToString(g_setupCounter);

         devBoxCol = InpShowDeveloping ? InpZoneColor : clrNONE;
         devLineCol = InpShowDeveloping ? InpLevelColor : clrNONE;

         // create unconfirmed box as hollow (fill = false)
         CreateBox(g_curBullishBox, time[i], g_bullishLevel, GetBarTime(i - 1, time), g_bullishExtreme, devBoxCol, STYLE_SOLID, false);
         CreateLine(g_curBullishLine, g_bullishSwingTime, g_bullishLevel, time[i], g_bullishLevel, devLineCol, STYLE_DOT);
         CreateLabel(g_curBullishLabel, g_bullishSwingTime, g_bullishLevel, "x", devLineCol);
      }

      if(g_bullishActive)
      {
         g_bullishExtreme = MathMin(g_bullishExtreme, low[i]);
         g_bullishReclaimed = g_bullishReclaimed || (close[i] > g_bullishLevel);

         UpdateBox(g_curBullishBox, g_bullishStartBarTime, g_bullishLevel, GetBarTime(i - 1, time), g_bullishExtreme);
         UpdateLine(g_curBullishLine, g_bullishSwingTime, g_bullishLevel, time[i], g_bullishLevel);

         bool confirmBullish = g_bullishReclaimed && (close[i] > g_bullishConfirmationLevel) && displacementPass;
         bool expireBullish = (barIndex - g_bullishStartBarIndex) > InpMaxConfirmBars;

         if(confirmBullish)
         {
            if(i == 1) alertBullish = true;
            extTime = GetBarTime(i - InpConfirmedExtension, time);
            
            // update box as confirmed/solid (fill = true)
            UpdateBox(g_curBullishBox, g_bullishStartBarTime, g_bullishLevel, extTime, g_bullishExtreme, InpZoneColor, STYLE_SOLID, true);
            UpdateLine(g_curBullishLine, g_bullishSwingTime, g_bullishLevel, time[i], g_bullishLevel, InpLevelColor, STYLE_SOLID);
            UpdateLabel(g_curBullishLabel, InpLevelColor);

            PushAndTrimHistory(g_curBullishBox, g_curBullishLine, g_curBullishLabel, InpMaxHistory);

            g_bullishActive = false; g_curBullishBox = ""; g_curBullishLine = ""; g_curBullishLabel = "";
         }
         else if(expireBullish)
         {
            ObjectDelete(g_curBullishBox); ObjectDelete(g_curBullishLine); ObjectDelete(g_curBullishLabel);
            g_bullishActive = false; g_curBullishBox = ""; g_curBullishLine = ""; g_curBullishLabel = "";
         }
      }
   }

   // Optional visual shift for unclosed/developing boxes to follow the live current open bar
   if (g_bearishActive && g_curBearishBox != "") {
       ObjectSetInteger(0, g_curBearishBox, OBJPROP_TIME2, time[0]);
       ObjectSetInteger(0, g_curBearishLine, OBJPROP_TIME2, time[0]);
   }
   if (g_bullishActive && g_curBullishBox != "") {
       ObjectSetInteger(0, g_curBullishBox, OBJPROP_TIME2, time[0]);
       ObjectSetInteger(0, g_curBullishLine, OBJPROP_TIME2, time[0]);
   }

   if(time[1] != g_lastAlertTime)
   {
      if(alertBearish) { Alert(Symbol(), " - Bearish liquidity sweep reversal confirmed!"); g_lastAlertTime = time[1]; }
      else if(alertBullish) { Alert(Symbol(), " - Bullish liquidity sweep reversal confirmed!"); g_lastAlertTime = time[1]; }
   }

   return(rates_total);
}