//+------------------------------------------------------------------+
//|                                              BystraStrategy.mq5   |
//|                                      Copyright 2025, Bystra Dev   |
//|                                             https://www.mql5.com  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Bystra Dev"
#property link      "https://www.mql5.com"
#property version   "1.20" // Implements separate Fresh/Unfresh S/R pair limits

#include <Arrays\ArrayObj.mqh>
#include <Object.mqh>

//+------------------------------------------------------------------+
//| Input parameters                                                  |
//+------------------------------------------------------------------+

input group "==== Strategy Enable/Disable ===="
input bool Enable_Bystra_Strategy = true;      // Enable Bystra Strategy (Engulfing + Marubozu)
input bool Enable_Malaysian_SNR = true;        // Enable Malaysian SNR Strategy

input group "==== Historical-Scan Settings ===="
input int  LookbackBars = 300;      // how many past bars to scan for ENG / DM

input group "==== Time Frame Settings ===="
input string   TF_List           = "H4,H1,M15";    // Time frames (comma-separated, max 3)

input group "==== Zone Management ===="
input int      MaxZones          = 100;            // Maximum zones to keep on chart
input double   BodyRatio         = 0.50;           // Min body ratio for Double-Marubozu (0.01-1.0)
input int      FadePercent       = 70;             // Fade percentage after break (0-100)
input bool Use_DM_D2_Logic = true;   // false = old DMD-1, true = DMD-2

input group "==== Broken-Zone Visibility ===="
input bool      ShowBrokenZones = true;   // true = keep faded zone, false = hide it

input group "==== Style Settings ===="
input ENUM_LINE_STYLE Active_LineStyle  = STYLE_SOLID;  // Active zone border style
input ENUM_LINE_STYLE Broken_LineStyle  = STYLE_DOT;    // Broken zone border style
input color    Broken_BorderColour = clrGray;           // Broken zone border color

input group "==== Bullish Engulfing Colors ===="
input color    BullEng_FillColor   = C'32,255,32';      // Fill color (will be made transparent)
input color    BullEng_BorderColor = C'0,128,0';        // Border color

input group "==== Bearish Engulfing Colors ===="
input color    BearEng_FillColor   = C'255,64,64';      // Fill color (will be made transparent)
input color    BearEng_BorderColor = C'176,0,0';        // Border color

input group "==== Bullish Double-Marubozu Colors ===="
input color    BullDM_FillColor    = C'64,160,255';     // Fill color (will be made transparent)
input color    BullDM_BorderColor  = C'0,96,255';       // Border color

input group "==== Bearish Double-Marubozu Colors ===="
input color    BearDM_FillColor    = C'255,176,96';     // Fill color (will be made transparent)
input color    BearDM_BorderColor  = C'182,92,0';       // Border color

input group "==== Malaysian SNR Settings (S/R Pair Limits) ===="
input string SNR_TF_List = "H4,W1,D1";   // SNR Timeframes (comma-separated, max 7)
input string SNR_Fresh_MaxPairs_List = "5,3,3";   // Max FRESH S/R pairs per TF (e.g., 5 means 5 S & 5 R)
input string SNR_Unfresh_MaxPairs_List = "2,2,2"; // Max UNFESH S/R pairs per TF (e.g., 2 means 2 S & 2 R)
input int Max_SNR_Lines = 100;                 // Maximum total SNR lines (overall safety limit)
input double Pattern2_WickPercent = 25.0;      // Wick percentage for Pattern 2 (0-100)

input group "==== Malaysian SNR Colors ===="
input color Fresh_Support_Color = clrBlue;     // Fresh support line color
input color Fresh_Resistance_Color = clrRed;   // Fresh resistance line color
input color Unfresh_SNR_Color = clrGray;       // Unfresh SNR line color
input ENUM_LINE_STYLE Fresh_LineStyle = STYLE_SOLID;    // Fresh line style
input ENUM_LINE_STYLE Unfresh_LineStyle = STYLE_DOT;    // Unfresh line style
input int SNR_LineWidth = 2;                   // SNR line width



//--- true if two price intervals intersect --------------------------
bool PriceRangesOverlap(double a_low, double a_high,
                        double b_low, double b_high)
{
   return (a_low <= b_high) && (a_high >= b_low);
}

//+------------------------------------------------------------------+
//| Zone class definition                                             |
//+------------------------------------------------------------------+
class CZone : public CObject
{
private:
   string            m_name;           // Rectangle object name
   string            m_text_name;      // Text label object name
   datetime          m_time_start;     // Zone start time
   datetime          m_time_end;       // Zone end time (for broken zones)
   double            m_price_high;     // Zone high price
   double            m_price_low;      // Zone low price
   bool              m_is_bullish;     // Direction
   string            m_pattern_type;   // "ENG" or "DM"
   string            m_timeframe;      // Timeframe code
   ENUM_TIMEFRAMES   m_timeframe_enum; // Timeframe enum value
   bool              m_is_broken;      // Break status
   color             m_fill_color;     // Original fill color
   color             m_border_color;   // Original border color
   static int        s_uid_counter;    // Static UID counter
   
public:
                     CZone(void);
                    ~CZone(void);
   bool              Create(datetime time, double high, double low, bool bullish, 
                           string pattern, string tf, color fill, color border,
                           ENUM_TIMEFRAMES tf_enum);
   void              Update(datetime current_time);
   bool              CheckBreak(double open, double high, double low, double close, datetime bar_time);
   void              SetBroken(datetime break_time);
   void              Delete(void);
   datetime          GetTime(void) const { return m_time_start; }
   bool              IsBroken(void) const { return m_is_broken; }
   bool              IsDM() const { return (m_pattern_type == "DM"); }
   ENUM_TIMEFRAMES   GetTimeframe(void) const { return m_timeframe_enum; }
   
};

int CZone::s_uid_counter = 0;

//+------------------------------------------------------------------+
//| Zone constructor                                                  |
//+------------------------------------------------------------------+
CZone::CZone(void) : m_is_broken(false), m_time_end(0)
{
}

//+------------------------------------------------------------------+
//| Zone destructor                                                   |
//+------------------------------------------------------------------+
CZone::~CZone(void)
{
   Delete();
}

//+------------------------------------------------------------------+
//| Create zone objects                                               |
//+------------------------------------------------------------------+
bool CZone::Create(datetime time, double high, double low, bool bullish, 
                   string pattern, string tf, color fill, color border,
                   ENUM_TIMEFRAMES tf_enum)
{
   m_time_start = time;
   m_time_end = time;  // Initially same as start
   m_price_high = high;
   m_price_low = low;
   m_is_bullish = bullish;
   m_pattern_type = pattern;
   m_timeframe = tf;
   m_timeframe_enum = tf_enum;
   m_fill_color = fill;
   m_border_color = border;
   
   // Generate unique names
   s_uid_counter++;
   m_name = "BZ#" + IntegerToString(s_uid_counter);
   m_text_name = "BZtxt#" + IntegerToString(s_uid_counter);
   
   // Create rectangle
   if(!ObjectCreate(0, m_name, OBJ_RECTANGLE, 0, time, high, time, low))
      return false;
      
   // Set rectangle properties with transparency
   ObjectSetInteger(0, m_name, OBJPROP_COLOR, border);
   ObjectSetInteger(0, m_name, OBJPROP_STYLE, Active_LineStyle);
   ObjectSetInteger(0, m_name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, m_name, OBJPROP_FILL, true);
   ObjectSetInteger(0, m_name, OBJPROP_BACK, true);
   
   // Apply transparency to fill color (128 = 50% transparency)
   uint argb_fill = ColorToARGB(fill, 128);
   ObjectSetInteger(0, m_name, OBJPROP_BGCOLOR, argb_fill);
   
   // Create text label
   string label = tf + "-" + pattern + (bullish ? "↑" : "↓");
   double label_price = low - (high - low) * 0.1;
   
   if(!ObjectCreate(0, m_text_name, OBJ_TEXT, 0, time, label_price))
      return false;
      
   ObjectSetString(0, m_text_name, OBJPROP_TEXT, label);
   ObjectSetInteger(0, m_text_name, OBJPROP_COLOR, border);
   ObjectSetInteger(0, m_text_name, OBJPROP_FONTSIZE, 8);
   ObjectSetString(0, m_text_name, OBJPROP_FONT, "Arial");
   ObjectSetInteger(0, m_text_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
   
   return true;
}

//+------------------------------------------------------------------+
//| Update zone (extend right edge only if not broken)                |
//+------------------------------------------------------------------+
void CZone::Update(datetime current_time)
{
   // Only update if zone is not broken
   if(!m_is_broken && current_time > m_time_end)
   {
      m_time_end = current_time;
      ObjectSetInteger(0, m_name, OBJPROP_TIME, 1, current_time);
   }
}

//+------------------------------------------------------------------+
//| Check if zone is broken                                           |
//+------------------------------------------------------------------+
bool CZone::CheckBreak(double open, double high, double low, double close, datetime bar_time)
{
   if(m_is_broken)
      return false;

   bool broken = false;

   // ---------------------------------------------------------------+
   //  Double-Marubozu zones                                         |
   // ---------------------------------------------------------------+
   if(m_pattern_type == "DM")
   {
      if(Use_DM_D2_Logic)              // ----- DMD-2 (wick-only rule)
      {
         // Calculate body range
         double body_high = MathMax(open, close);
         double body_low  = MathMin(open, close);
         
         // Check if there's an upper wick that touches the zone
         bool upper_wick_touches = false;
         if(high > body_high)  // Upper wick exists
         {
            // Upper wick range is [body_high, high]
            upper_wick_touches = PriceRangesOverlap(body_high, high, 
                                                   m_price_low, m_price_high);
         }
         
         // Check if there's a lower wick that touches the zone
         bool lower_wick_touches = false;
         if(low < body_low)  // Lower wick exists
         {
            // Lower wick range is [low, body_low]
            lower_wick_touches = PriceRangesOverlap(low, body_low,
                                                   m_price_low, m_price_high);
         }
         
         // Zone is broken only if a wick touches it
         broken = upper_wick_touches || lower_wick_touches;
      }
      else                              // ----- DMD-1 (old edge rule)
      {
         broken = (m_is_bullish ? low  <= m_price_low   // bullish DM
                                : high >= m_price_high); // bearish DM
      }
   }
   // ---------------------------------------------------------------+
   //  Engulfing zones – freeze when touched by any part of candle   |
   // ---------------------------------------------------------------+
   else if(m_pattern_type == "ENG")
   {
      // Check if candle (body or wick) overlaps with the zone
      // Candle range is [low, high]
      // Zone range is [m_price_low, m_price_high]
      broken = PriceRangesOverlap(low, high, m_price_low, m_price_high);
   }
   // ---------------------------------------------------------------+
   //  Any other future zone types – keep old single-edge logic      |
   // ---------------------------------------------------------------+
   else
   {
      broken = (m_is_bullish ? low  <= m_price_low
                             : high >= m_price_high);
   }

   // ---------------------------------------------------------------+
   //  If broken → freeze & style-change                             |
   // ---------------------------------------------------------------+
   if(broken)
   {
      SetBroken(bar_time);
      return true;
   }

   return false;
}

//+------------------------------------------------------------------+
//| Set zone as broken (fade OR hide, depending on input)            |
//+------------------------------------------------------------------+
void CZone::SetBroken(datetime break_time)
{
   m_is_broken = true;

   //---------------------------------------------------------------
   // 1.  HIDE completely if the user does not want broken zones
   //---------------------------------------------------------------
   if(!ShowBrokenZones)
   {
      // Hide rectangle by removing it from all timeframes
      ObjectSetInteger(0, m_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
      // Hide label by removing it from all timeframes
      ObjectSetInteger(0, m_text_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
      return;                   // finished – no fading / styling needed
   }

   //---------------------------------------------------------------
   // 2.  Otherwise keep old "fade & freeze" behaviour
   //---------------------------------------------------------------
   m_time_end = break_time;
   ObjectSetInteger(0, m_name, OBJPROP_TIME , 1, break_time);

   ObjectSetInteger(0, m_name, OBJPROP_STYLE, Broken_LineStyle);
   ObjectSetInteger(0, m_name, OBJPROP_COLOR, Broken_BorderColour);

   uchar new_alpha = (uchar)(128 * (100 - FadePercent) / 100);
   uint  new_argb  = ColorToARGB(m_fill_color, new_alpha);
   ObjectSetInteger(0, m_name, OBJPROP_BGCOLOR, new_argb);

   ObjectSetInteger(0, m_text_name, OBJPROP_COLOR, Broken_BorderColour);
}

//+------------------------------------------------------------------+
//| Delete zone objects                                               |
//+------------------------------------------------------------------+
void CZone::Delete(void)
{
   ObjectDelete(0, m_name);
   ObjectDelete(0, m_text_name);
}

//+------------------------------------------------------------------+
//| SNR Line class definition                                         |
//+------------------------------------------------------------------+
class CSNRLine : public CObject
{
private:
   string            m_name;           // Line object name
   string            m_text_name;      // Text label object name
   double            m_price;          // SNR price level
   datetime          m_time_start;     // Line start time
   bool              m_is_support;     // true = support, false = resistance
   bool              m_is_fresh;       // Fresh/Unfresh status
   string            m_timeframe;      // Timeframe code
   ENUM_TIMEFRAMES   m_timeframe_enum; // Timeframe enum value
   color             m_fresh_color;    // Original fresh color
   static int        s_uid_counter;    // Static UID counter
   
public:
                     CSNRLine(void);
                    ~CSNRLine(void);
   bool              Create(datetime time, double price, bool is_support, 
                           string tf, ENUM_TIMEFRAMES tf_enum);
   void              Update(datetime current_time);
   bool              CheckFreshness(double open, double high, double low, double close);
   void              SetFresh(bool fresh);
   void              UpdateRole(double current_price);
   void              Delete(void);
   double            GetPrice(void) const { return m_price; }
   bool              IsSupport(void) const { return m_is_support; }
   bool              IsFresh(void) const { return m_is_fresh; }
   ENUM_TIMEFRAMES   GetTimeframe(void) const { return m_timeframe_enum; }
   datetime          GetStartTime(void) const { return m_time_start; }
};

int CSNRLine::s_uid_counter = 0;

//+------------------------------------------------------------------+
//| SNR Line constructor                                              |
//+------------------------------------------------------------------+
CSNRLine::CSNRLine(void) : m_is_fresh(true)
{
}

//+------------------------------------------------------------------+
//| SNR Line destructor                                               |
//+------------------------------------------------------------------+
CSNRLine::~CSNRLine(void)
{
   Delete();
}

//+------------------------------------------------------------------+
//| Create SNR line objects                                           |
//+------------------------------------------------------------------+
bool CSNRLine::Create(datetime time, double price, bool is_support, 
                      string tf, ENUM_TIMEFRAMES tf_enum)
{
   m_time_start = time;
   m_price = price;
   m_is_support = is_support;
   m_is_fresh = true;
   m_timeframe = tf;
   m_timeframe_enum = tf_enum;
   m_fresh_color = is_support ? Fresh_Support_Color : Fresh_Resistance_Color;
   
   // Generate unique names
   s_uid_counter++;
   m_name = "SNR#" + IntegerToString(s_uid_counter);
   m_text_name = "SNRtxt#" + IntegerToString(s_uid_counter);
   
   // Create trend line instead of horizontal line
   // Get a far future time for the end point
   datetime end_time = time + PeriodSeconds(PERIOD_D1) * 365; // 1 year ahead
   
   if(!ObjectCreate(0, m_name, OBJ_TREND, 0, time, price, end_time, price))
      return false;
      
   // Set line properties
   ObjectSetInteger(0, m_name, OBJPROP_COLOR, m_fresh_color);
   ObjectSetInteger(0, m_name, OBJPROP_STYLE, Fresh_LineStyle);
   ObjectSetInteger(0, m_name, OBJPROP_WIDTH, SNR_LineWidth);
   ObjectSetInteger(0, m_name, OBJPROP_BACK, true);
   ObjectSetInteger(0, m_name, OBJPROP_RAY_RIGHT, true);  // Extend to the right
   ObjectSetInteger(0, m_name, OBJPROP_RAY_LEFT, false);  // Don't extend to the left
   
   // Create text label
   string label = tf + "-" + (is_support ? "S" : "R");
   
   if(!ObjectCreate(0, m_text_name, OBJ_TEXT, 0, time, price))
      return false;
      
   ObjectSetString(0, m_text_name, OBJPROP_TEXT, label);
   ObjectSetInteger(0, m_text_name, OBJPROP_COLOR, m_fresh_color);
   ObjectSetInteger(0, m_text_name, OBJPROP_FONTSIZE, 8);
   ObjectSetString(0, m_text_name, OBJPROP_FONT, "Arial");
   ObjectSetInteger(0, m_text_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
   
   return true;
}

//+------------------------------------------------------------------+
//| Update SNR line (move text label to current time)                |
//+------------------------------------------------------------------+
void CSNRLine::Update(datetime current_time)
{
   ObjectSetInteger(0, m_text_name, OBJPROP_TIME, 0, current_time);
}

//+------------------------------------------------------------------+
//| Check if line crossed by body or wick - MODIFIED VERSION         |
//+------------------------------------------------------------------+
bool CSNRLine::CheckFreshness(double open, double high, double low, double close)
{
   // Check if candle crosses the line
   if(low <= m_price && high >= m_price)
   {
      // Calculate body range
      double body_high = MathMax(open, close);
      double body_low = MathMin(open, close);
      
      // Check if body crosses the line
      bool crossed_by_body = (body_low <= m_price && body_high >= m_price);
      
      if(crossed_by_body)
      {
         // Body crossed the line - always significant
         if(m_is_fresh)
         {
            // Fresh line crossed by body - stays fresh but check role
            UpdateRole(close);
         }
         else
         {
            // Unfresh line crossed by body - becomes fresh and update role
            SetFresh(true);
            UpdateRole(close);
            Print("Reactivated by body cross at ", DoubleToString(m_price, _Digits));
         }
      }
      else if(m_is_fresh)
      {
         // Only wick touched fresh line - make unfresh
         SetFresh(false);
      }
      // If unfresh and only wick - stays unfresh
      
      return true; // Line was touched
   }
   
   return false; // Line was not touched
}

//+------------------------------------------------------------------+
//| Update role based on current price position - NEW FUNCTION       |
//+------------------------------------------------------------------+
void CSNRLine::UpdateRole(double current_price)
{
   bool should_be_support = (m_price < current_price);
   
   if(should_be_support != m_is_support)
   {
      // Role needs to change
      m_is_support = should_be_support;
      
      // Update colors and appearance
      m_fresh_color = m_is_support ? Fresh_Support_Color : Fresh_Resistance_Color;
      ObjectSetInteger(0, m_name, OBJPROP_COLOR, m_fresh_color);
      
      // Update label
      string label = m_timeframe + "-" + (m_is_support ? "S" : "R");
      ObjectSetString(0, m_text_name, OBJPROP_TEXT, label);
      ObjectSetInteger(0, m_text_name, OBJPROP_COLOR, m_fresh_color);
      
      Print("Role updated: ", m_timeframe, " ", 
            (!m_is_support ? "Support→Resistance" : "Resistance→Support"),
            " at ", DoubleToString(m_price, _Digits));
   }
}

//+------------------------------------------------------------------+
//| Set line fresh/unfresh status                                    |
//+------------------------------------------------------------------+
void CSNRLine::SetFresh(bool fresh)
{
   if(m_is_fresh == fresh)
      return; // No change needed
      
   m_is_fresh = fresh;
   
   if(fresh)
   {
      // Restore to fresh appearance
      ObjectSetInteger(0, m_name, OBJPROP_COLOR, m_fresh_color);
      ObjectSetInteger(0, m_name, OBJPROP_STYLE, Fresh_LineStyle);
      ObjectSetInteger(0, m_text_name, OBJPROP_COLOR, m_fresh_color);
   }
   else
   {
      // Change to unfresh appearance
      ObjectSetInteger(0, m_name, OBJPROP_COLOR, Unfresh_SNR_Color);
      ObjectSetInteger(0, m_name, OBJPROP_STYLE, Unfresh_LineStyle);
      ObjectSetInteger(0, m_text_name, OBJPROP_COLOR, Unfresh_SNR_Color);
   }
}

//+------------------------------------------------------------------+
//| Delete SNR line objects                                           |
//+------------------------------------------------------------------+
void CSNRLine::Delete(void)
{
   ObjectDelete(0, m_name);
   ObjectDelete(0, m_text_name);
}

//+------------------------------------------------------------------+
//| Global variables                                                  |
//+------------------------------------------------------------------+
CArrayObj g_zones;                    // Array of zones
ENUM_TIMEFRAMES g_timeframes[3];      // Parsed timeframes
int g_tf_count = 0;                   // Number of timeframes
datetime g_last_bar_time[3];          // Last processed bar time for each TF

CArrayObj g_snr_lines;                // Array of SNR lines
ENUM_TIMEFRAMES g_snr_timeframes[7];  // Parsed SNR timeframes
int g_snr_tf_count = 0;               // Number of SNR timeframes
datetime g_snr_last_bar_time[7];      // Last processed bar time for each SNR TF

// Global variables for separate fresh/unfresh and S/R pair counts
int g_snr_max_fresh_pairs[7];         // Maximum FRESH S/R pairs per timeframe
int g_snr_max_unfresh_pairs[7];       // Maximum UNFESH S/R pairs per timeframe
int g_snr_fresh_support_count[7];     // Current FRESH support line count
int g_snr_fresh_resistance_count[7];  // Current FRESH resistance line count
int g_snr_unfresh_support_count[7];   // Current UNFESH support line count
int g_snr_unfresh_resistance_count[7];// Current UNFESH resistance line count


//+------------------------------------------------------------------+
//| Expert initialization function                                    |
//+------------------------------------------------------------------+
int OnInit()
{
   // Clear any existing zones and lines
   g_zones.Clear();
   g_snr_lines.Clear();
   
   // Parse Bystra timeframes
   if(Enable_Bystra_Strategy)
   {
      if(!ParseTimeframes())
      {
         Alert("Invalid timeframe list. Please use format like: H4,H1,M15");
         return INIT_FAILED;
      }
      
      // Validate body ratio
      if(BodyRatio < 0.01 || BodyRatio > 1.0)
      {
         Alert("BodyRatio must be between 0.01 and 1.0");
         return INIT_FAILED;
      }
   }
   
   // Parse SNR timeframes if enabled
   if(Enable_Malaysian_SNR)
   {
      if(!ParseSNRTimeframes())
      {
         Alert("Invalid SNR timeframe list. Please use format like: H4,H1,M30,M15");
         return INIT_FAILED;
      }
      
      // Parse both fresh and unfresh max pair lists
      if(!ParseSNRMaxFreshPairs())
      {
         return INIT_FAILED;
      }
      if(!ParseSNRMaxUnfreshPairs())
      {
         return INIT_FAILED;
      }
      
      // Validate Pattern2 wick percentage
      if(Pattern2_WickPercent < 0 || Pattern2_WickPercent > 100)
      {
         Alert("Pattern2_WickPercent must be between 0 and 100");
         return INIT_FAILED;
      }
   }
   
   // Initialize last bar times for Bystra
   for(int i = 0; i < g_tf_count; i++)
   {
      g_last_bar_time[i] = 0;
   }
   
   // Initialize last bar times for SNR
   for(int i = 0; i < g_snr_tf_count; i++)
   {
      g_snr_last_bar_time[i] = 0;
   }
   
   // Process historical bars for Bystra
   if(Enable_Bystra_Strategy)
   {
      ProcessHistoricalBars();
   }
   
   // Process historical bars for Malaysian SNR
   if(Enable_Malaysian_SNR)
   {
      ProcessHistoricalSNRBars();
   }
   
   // Print initialization summary
   Print("=== EA Initialized Successfully ===");
   
   if(Enable_Bystra_Strategy)
   {
      Print("Bystra Strategy: ENABLED");
      Print("  - Timeframes: ", TF_List);
      Print("  - Max Zones: ", MaxZones);
      Print("  - DM Logic: ", (Use_DM_D2_Logic ? "DMD-2 (wick-only)" : "DMD-1 (edge)"));
   }
   else
   {
      Print("Bystra Strategy: DISABLED");
   }
   
   if(Enable_Malaysian_SNR)
   {
      Print("Malaysian SNR Strategy: ENABLED");
      Print("  - Timeframes: ", SNR_TF_List);
      Print("  - Max Fresh S/R Pairs: ", SNR_Fresh_MaxPairs_List);
      Print("  - Max Unfresh S/R Pairs: ", SNR_Unfresh_MaxPairs_List);
      Print("  - Total Max Lines: ", Max_SNR_Lines);
      Print("  - Pattern2 Wick %: ", Pattern2_WickPercent);
   }
   else
   {
      Print("Malaysian SNR Strategy: DISABLED");
   }
   
   Print("===================================");
   
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Clean up all zones and lines
   g_zones.Clear();
   g_snr_lines.Clear();
}

//+------------------------------------------------------------------+
//| Expert tick function                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   //-----------------------------------------------------------------
   // 1) Check Bystra Strategy timeframes
   //-----------------------------------------------------------------
   if(Enable_Bystra_Strategy)
   {
      for(int i = 0; i < g_tf_count; i++)
      {
         datetime bar_time = iTime(_Symbol, g_timeframes[i], 0);
         if(bar_time > g_last_bar_time[i])
         {
            CheckPatterns(g_timeframes[i], TimeframeToString(g_timeframes[i]));
            g_last_bar_time[i] = bar_time;
         }
      }
   }
   
   //-----------------------------------------------------------------
   // 2) Check Malaysian SNR timeframes
   //-----------------------------------------------------------------
   if(Enable_Malaysian_SNR)
   {
      for(int i = 0; i < g_snr_tf_count; i++)
      {
         datetime bar_time = iTime(_Symbol, g_snr_timeframes[i], 0);
         if(bar_time > g_snr_last_bar_time[i])
         {
            CheckMalaysianSNR(g_snr_timeframes[i], 
                            TimeframeToString(g_snr_timeframes[i]));
            g_snr_last_bar_time[i] = bar_time;
         }
      }
   }

   //-----------------------------------------------------------------
   // 3) Update Bystra zones
   //-----------------------------------------------------------------
   if(Enable_Bystra_Strategy)
   {
      for(int i = 0; i < g_zones.Total(); i++)
      {
         CZone *zone = g_zones.At(i);
         if(zone == NULL || zone.IsBroken())
            continue;

         bool zone_broken = false;
         ENUM_TIMEFRAMES zone_tf = zone.GetTimeframe();

         // a) LIVE-candle check – applies ONLY to Double-Marubozu zones
         if(zone.IsDM())
         {
            double live_open  = iOpen(_Symbol, zone_tf, 0);
            double live_high  = iHigh(_Symbol, zone_tf, 0);
            double live_low   = iLow(_Symbol, zone_tf, 0);
            double live_close = iClose(_Symbol, zone_tf, 0);
            datetime current_time = iTime(_Symbol, zone_tf, 0);
            
            if(zone.CheckBreak(live_open, live_high, live_low, live_close, current_time))
               zone_broken = true;
         }

         // b) Closed-candle check
         if(!zone_broken)
         {
            int start_bar = iBarShift(_Symbol, zone_tf, zone.GetTime());
            for(int bar = start_bar - 1; bar >= 1; bar--)
            {
               double bar_open  = iOpen(_Symbol, zone_tf, bar);
               double bar_high  = iHigh(_Symbol, zone_tf, bar);
               double bar_low   = iLow(_Symbol, zone_tf, bar);
               double bar_close = iClose(_Symbol, zone_tf, bar);
               datetime bar_time = iTime(_Symbol, zone_tf, bar);

               if(zone.CheckBreak(bar_open, bar_high, bar_low, bar_close, bar_time))
               {
                  zone_broken = true;
                  break;
               }
            }
         }

         // c) Extend zone to the right
         if(!zone_broken)
            zone.Update(iTime(_Symbol, PERIOD_CURRENT, 0));
      }
   }
   
   //-----------------------------------------------------------------
   // 4) Update SNR lines freshness
   //-----------------------------------------------------------------
   if(Enable_Malaysian_SNR)
   {
      datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
      
      for(int i = 0; i < g_snr_lines.Total(); i++)
      {
         CSNRLine *line = g_snr_lines.At(i);
         if(line == NULL)
            continue;
            
         ENUM_TIMEFRAMES line_tf = line.GetTimeframe();
         
         // Check current candle on line's timeframe
         double curr_open  = iOpen(_Symbol, line_tf, 0);
         double curr_high  = iHigh(_Symbol, line_tf, 0);
         double curr_low   = iLow(_Symbol, line_tf, 0);
         double curr_close = iClose(_Symbol, line_tf, 0);
         
         line.CheckFreshness(curr_open, curr_high, curr_low, curr_close);
         
         // Update line label position
         line.Update(current_time);
      }
   }

   //-----------------------------------------------------------------
   // 5) Keep array sizes within limits
   //-----------------------------------------------------------------
   while(g_zones.Total() > MaxZones)
      g_zones.Delete(0);
   
   // Enforce unfresh line limits after state changes, for S/R pairs
   if(Enable_Malaysian_SNR)
   {
      CountSNRLinesPerTimeframe();
      for(int i = 0; i < g_snr_tf_count; i++)
      {
         // Check if we are over the unfresh SUPPORT limit for this timeframe
         while(g_snr_unfresh_support_count[i] > g_snr_max_unfresh_pairs[i] && g_snr_max_unfresh_pairs[i] >= 0)
         {
            RemoveOldestSNRLine(g_snr_timeframes[i], false, true); // Remove an unfresh support line
            CountSNRLinesPerTimeframe(); // Recount after removal
         }
         
         // Check if we are over the unfresh RESISTANCE limit for this timeframe
         while(g_snr_unfresh_resistance_count[i] > g_snr_max_unfresh_pairs[i] && g_snr_max_unfresh_pairs[i] >= 0)
         {
            RemoveOldestSNRLine(g_snr_timeframes[i], false, false); // Remove an unfresh resistance line
            CountSNRLinesPerTimeframe(); // Recount after removal
         }
      }
   }
      
   while(g_snr_lines.Total() > Max_SNR_Lines)
      g_snr_lines.Delete(0);
}


//+------------------------------------------------------------------+
//| Parse timeframe string                                            |
//+------------------------------------------------------------------+
bool ParseTimeframes(void)
{
   g_tf_count = 0;
   
   // Split the string by comma
   string tf_array[];
   int count = StringSplit(TF_List, ',', tf_array);
   
   if(count == 0 || count > 3)
      return false;
      
   for(int i = 0; i < count; i++)
   {
      // Trim spaces
      StringTrimLeft(tf_array[i]);
      StringTrimRight(tf_array[i]);
      
      ENUM_TIMEFRAMES tf = StringToTimeframe(tf_array[i]);
      if(tf == 0)
         return false;
         
      g_timeframes[g_tf_count++] = tf;
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| Parse SNR max FRESH S/R pairs per timeframe                       |
//+------------------------------------------------------------------+
bool ParseSNRMaxFreshPairs(void)
{
   // Initialize all to 0
   for(int i = 0; i < 7; i++)
   {
      g_snr_max_fresh_pairs[i] = 0;
      g_snr_fresh_support_count[i] = 0;
      g_snr_fresh_resistance_count[i] = 0;
   }
   
   // Split the string by comma
   string lines_array[];
   int count = StringSplit(SNR_Fresh_MaxPairs_List, ',', lines_array);
   
   // Must match the number of timeframes
   if(count != g_snr_tf_count)
   {
      Alert("Number of FRESH max S/R pairs must match number of SNR timeframes!");
      return false;
   }
   
   for(int i = 0; i < count; i++)
   {
      // Trim spaces
      StringTrimLeft(lines_array[i]);
      StringTrimRight(lines_array[i]);
      
      // Convert to integer
      g_snr_max_fresh_pairs[i] = (int)StringToInteger(lines_array[i]);
      
      if(g_snr_max_fresh_pairs[i] < 0)
      {
         Alert("Max fresh S/R pairs cannot be negative!");
         return false;
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| Parse SNR max UNFESH S/R pairs per timeframe                      |
//+------------------------------------------------------------------+
bool ParseSNRMaxUnfreshPairs(void)
{
   // Initialize all to 0
   for(int i = 0; i < 7; i++)
   {
      g_snr_max_unfresh_pairs[i] = 0;
      g_snr_unfresh_support_count[i] = 0;
      g_snr_unfresh_resistance_count[i] = 0;
   }

   // Split the string by comma
   string lines_array[];
   int count = StringSplit(SNR_Unfresh_MaxPairs_List, ',', lines_array);

   // Must match the number of timeframes
   if(count != g_snr_tf_count)
   {
      Alert("Number of UNFESH max S/R pairs must match number of SNR timeframes!");
      return false;
   }

   for(int i = 0; i < count; i++)
   {
      // Trim spaces
      StringTrimLeft(lines_array[i]);
      StringTrimRight(lines_array[i]);

      // Convert to integer
      g_snr_max_unfresh_pairs[i] = (int)StringToInteger(lines_array[i]);

      if(g_snr_max_unfresh_pairs[i] < 0)
      {
         Alert("Max unfresh S/R pairs cannot be negative!");
         return false;
      }
   }

   return true;
}


//+------------------------------------------------------------------+
//| Parse SNR timeframes                                              |
//+------------------------------------------------------------------+
bool ParseSNRTimeframes(void)
{
   g_snr_tf_count = 0;
   
   // Split the string by comma
   string tf_array[];
   int count = StringSplit(SNR_TF_List, ',', tf_array);
   
   if(count == 0 || count > 7)
      return false;
      
   for(int i = 0; i < count; i++)
   {
      // Trim spaces
      StringTrimLeft(tf_array[i]);
      StringTrimRight(tf_array[i]);
      
      ENUM_TIMEFRAMES tf = StringToTimeframe(tf_array[i]);
      if(tf == 0)
         return false;
         
      g_snr_timeframes[g_snr_tf_count++] = tf;
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| Convert string to timeframe                                       |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES StringToTimeframe(string tf_str)
{
   StringToUpper(tf_str);
   
   if(tf_str == "M1")  return PERIOD_M1;
   if(tf_str == "M5")  return PERIOD_M5;
   if(tf_str == "M15") return PERIOD_M15;
   if(tf_str == "M30") return PERIOD_M30;
   if(tf_str == "H1")  return PERIOD_H1;
   if(tf_str == "H4")  return PERIOD_H4;
   if(tf_str == "D1")  return PERIOD_D1;
   if(tf_str == "W1")  return PERIOD_W1;
   if(tf_str == "MN1") return PERIOD_MN1;
   
   return 0;
}

//+------------------------------------------------------------------+
//| Convert timeframe to string                                       |
//+------------------------------------------------------------------+
string TimeframeToString(ENUM_TIMEFRAMES tf)
{
   switch(tf)
   {
      case PERIOD_M1:  return "M1";
      case PERIOD_M5:  return "M5";
      case PERIOD_M15: return "M15";
      case PERIOD_M30: return "M30";
      case PERIOD_H1:  return "H1";
      case PERIOD_H4:  return "H4";
      case PERIOD_D1:  return "D1";
      case PERIOD_W1:  return "W1";
      case PERIOD_MN1: return "MN1";
      default:         return "";
   }
}

//+------------------------------------------------------------------+
//| Process historical bars on initialization                         |
//+------------------------------------------------------------------+
void ProcessHistoricalBars(void)
{
   // First create all zones
   for(int i = 0; i < g_tf_count; i++)
   {
      int bars_to_check = MathMin(LookbackBars, iBars(_Symbol, g_timeframes[i]));

      for(int j = bars_to_check - 1; j >= 2; j--)
      {
         CheckPatternsAtBar(g_timeframes[i], TimeframeToString(g_timeframes[i]), j);
      }

      g_last_bar_time[i] = iTime(_Symbol, g_timeframes[i], 0);
   }
   
   // Then check for historical breaks
   for(int i = 0; i < g_zones.Total(); i++)
   {
      CZone *zone = g_zones.At(i);
      if(zone != NULL && !zone.IsBroken())
      {
         ENUM_TIMEFRAMES zone_tf = zone.GetTimeframe();
         int start_bar = iBarShift(_Symbol, zone_tf, zone.GetTime());
         
         // Check all bars from zone creation
         for(int bar = start_bar - 1; bar >= 0; bar--)
         {
            double bar_open = iOpen(_Symbol, zone_tf, bar);
            double bar_high = iHigh(_Symbol, zone_tf, bar);
            double bar_low = iLow(_Symbol, zone_tf, bar);
            double bar_close = iClose(_Symbol, zone_tf, bar);
            datetime bar_time = iTime(_Symbol, zone_tf, bar);
            
            if(zone.CheckBreak(bar_open, bar_high, bar_low, bar_close, bar_time))
            {
               break; // Zone frozen at break point
            }
         }
         
         // If zone is not broken, extend it to current time
         if(!zone.IsBroken())
         {
            zone.Update(iTime(_Symbol, PERIOD_CURRENT, 0));
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Process historical bars for SNR (CORRECTED with 2-Pass Logic)     |
//+------------------------------------------------------------------+
void ProcessHistoricalSNRBars(void)
{
   if(!Enable_Malaysian_SNR)
      return;
      
   // --- PASS 1: Find all the best SUPPORT lines first ---
   Print("SNR Scan: Starting Pass 1 to find all Support lines...");
   for(int i = 0; i < g_snr_tf_count; i++)
   {
      int bars_to_check = MathMin(LookbackBars, iBars(_Symbol, g_snr_timeframes[i]));

      for(int j = bars_to_check - 1; j >= 4; j--)
      {
         // In this pass, ONLY check for Bullish 'V' patterns (Supports)
         CheckBullishV(g_snr_timeframes[i], 
                       TimeframeToString(g_snr_timeframes[i]), j);
      }
   }
   Print("SNR Scan: Pass 1 (Support) complete.");

   // --- PASS 2: Find all the best RESISTANCE lines second ---
   Print("SNR Scan: Starting Pass 2 to find all Resistance lines...");
   for(int i = 0; i < g_snr_tf_count; i++)
   {
      int bars_to_check = MathMin(LookbackBars, iBars(_Symbol, g_snr_timeframes[i]));

      for(int j = bars_to_check - 1; j >= 4; j--)
      {
         // In this pass, ONLY check for Bearish 'Λ' patterns (Resistances)
         CheckBearishLambda(g_snr_timeframes[i], 
                            TimeframeToString(g_snr_timeframes[i]), j);
      }
   }
   Print("SNR Scan: Pass 2 (Resistance) complete.");
   
   // --- FINAL STEP: Set last bar time and check historical freshness ---
   for(int i=0; i < g_snr_tf_count; i++)
   {
       g_snr_last_bar_time[i] = iTime(_Symbol, g_snr_timeframes[i], 0);
   }

   // Now that we have the final set of S/R lines, check their freshness history
   for(int i = 0; i < g_snr_lines.Total(); i++)
   {
      CSNRLine *line = g_snr_lines.At(i);
      if(line != NULL)
      {
         ENUM_TIMEFRAMES line_tf = line.GetTimeframe();
         int start_bar = iBarShift(_Symbol, line_tf, line.GetStartTime());
         
         // Check all bars from line creation to the present
         for(int bar = start_bar - 1; bar >= 0; bar--)
         {
            double bar_open = iOpen(_Symbol, line_tf, bar);
            double bar_high = iHigh(_Symbol, line_tf, bar);
            double bar_low = iLow(_Symbol, line_tf, bar);
            double bar_close = iClose(_Symbol, line_tf, bar);
            
            line.CheckFreshness(bar_open, bar_high, bar_low, bar_close);
         }
         
         // Update line label position
         line.Update(iTime(_Symbol, PERIOD_CURRENT, 0));
      }
   }
   
   // Enforce unfresh limits after historical scan for S/R pairs
   CountSNRLinesPerTimeframe();
   for(int i = 0; i < g_snr_tf_count; i++)
   {
      // Unfresh Support
      while(g_snr_unfresh_support_count[i] > g_snr_max_unfresh_pairs[i] && g_snr_max_unfresh_pairs[i] >= 0)
      {
         RemoveOldestSNRLine(g_snr_timeframes[i], false, true);
         CountSNRLinesPerTimeframe();
      }
      // Unfresh Resistance
      while(g_snr_unfresh_resistance_count[i] > g_snr_max_unfresh_pairs[i] && g_snr_max_unfresh_pairs[i] >= 0)
      {
         RemoveOldestSNRLine(g_snr_timeframes[i], false, false);
         CountSNRLinesPerTimeframe();
      }
   }
}

//+------------------------------------------------------------------+
//| Count SNR lines for a specific timeframe                          |
//+------------------------------------------------------------------+
void CountSNRLinesPerTimeframe(void)
{
   // Reset counts
   for(int i = 0; i < g_snr_tf_count; i++)
   {
      g_snr_fresh_support_count[i] = 0;
      g_snr_fresh_resistance_count[i] = 0;
      g_snr_unfresh_support_count[i] = 0;
      g_snr_unfresh_resistance_count[i] = 0;
   }
   
   // Count lines for each timeframe, separating all four categories
   for(int i = 0; i < g_snr_lines.Total(); i++)
   {
      CSNRLine *line = g_snr_lines.At(i);
      if(line != NULL)
      {
         ENUM_TIMEFRAMES line_tf = line.GetTimeframe();
         
         // Find which timeframe index this belongs to
         for(int j = 0; j < g_snr_tf_count; j++)
         {
            if(g_snr_timeframes[j] == line_tf)
            {
               if(line.IsFresh())
               {
                  if(line.IsSupport())
                     g_snr_fresh_support_count[j]++;
                  else
                     g_snr_fresh_resistance_count[j]++;
               }
               else // Is Unfresh
               {
                  if(line.IsSupport())
                     g_snr_unfresh_support_count[j]++;
                  else
                     g_snr_unfresh_resistance_count[j]++;
               }
               break;
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Remove oldest SNR lines for a specific timeframe                  |
//+------------------------------------------------------------------+
void RemoveOldestSNRLine(ENUM_TIMEFRAMES tf, bool is_fresh_line, bool is_support_line)
{
   datetime oldest_time = D'2099.12.31';
   int oldest_index = -1;
   
   // Find the oldest line for this timeframe and specific category
   for(int i = 0; i < g_snr_lines.Total(); i++)
   {
      CSNRLine *line = g_snr_lines.At(i);
      if(line != NULL && line.GetTimeframe() == tf && line.IsFresh() == is_fresh_line && line.IsSupport() == is_support_line)
      {
         if(line.GetStartTime() < oldest_time)
         {
            oldest_time = line.GetStartTime();
            oldest_index = i;
         }
      }
   }
   
   // Remove the oldest line
   if(oldest_index >= 0)
   {
      string freshness_str = is_fresh_line ? "fresh" : "unfresh";
      string role_str = is_support_line ? "Support" : "Resistance";
      g_snr_lines.Delete(oldest_index);
      Print("Removed oldest ", freshness_str, " ", role_str, " line on ", TimeframeToString(tf), " to respect limit.");
   }
}

//+------------------------------------------------------------------+
//| Check patterns on new bar                                         |
//+------------------------------------------------------------------+
void CheckPatterns(ENUM_TIMEFRAMES tf, string tf_string)
{
   // Check patterns at bar index 2 (just completed bar)
   CheckPatternsAtBar(tf, tf_string, 2);
}

//+------------------------------------------------------------------+
//| Check patterns at specific bar                                    |
//+------------------------------------------------------------------+
void CheckPatternsAtBar(ENUM_TIMEFRAMES tf, string tf_string, int bar_index)
{
   // Need at least bar_index + 1 bars
   if(iBars(_Symbol, tf) < bar_index + 1)
      return;
      
   MqlRates rates[];
   // Copy 2 bars starting from bar_index-1 (to get the two bars for pattern)
   if(CopyRates(_Symbol, tf, bar_index - 1, 2, rates) != 2)
      return;
   
   // Check if Bystra Strategy is enabled
   if(Enable_Bystra_Strategy)
   {
      // Check Engulfing pattern
      CheckEngulfing(rates, tf_string, tf);
      
      // Check Double-Marubozu pattern
      CheckDoubleMarubozu(rates, tf_string, tf);
   }
}

//+------------------------------------------------------------------+
//| Check for Engulfing pattern                                       |
//+------------------------------------------------------------------+
void CheckEngulfing(MqlRates &rates[], string tf_string, ENUM_TIMEFRAMES tf)
{
   // Candle 1 (older) and Candle 2 (newer)
   MqlRates candle1 = rates[0];
   MqlRates candle2 = rates[1];

   // ---- Bullish Engulfing ----------------------------------------
   if(candle1.close < candle1.open &&   // candle-1 bearish
      candle2.close > candle2.open &&   // candle-2 bullish
      candle2.close > candle1.high)     // engulfed
   {
      // Zone from bearish candle's open to bearish candle's low
      CreateZone(candle2.time,           // start at candle-2 time
                 candle1.open,           // high = bearish candle open
                 candle1.low,            // low = bearish candle low
                 true,  "ENG", tf_string,
                 BullEng_FillColor, BullEng_BorderColor, tf);
   }

   // ---- Bearish Engulfing ----------------------------------------
   if(candle1.close > candle1.open &&   // candle-1 bullish
      candle2.close < candle2.open &&   // candle-2 bearish
      candle2.close < candle1.low)      // engulfed
   {
      // Zone from bullish candle's open to bullish candle's high
      CreateZone(candle2.time,           // start at candle-2 time
                 candle1.high,           // high = bullish candle high
                 candle1.open,           // low = bullish candle open
                 false, "ENG", tf_string,
                 BearEng_FillColor, BearEng_BorderColor, tf);
   }
}

//+------------------------------------------------------------------+
//| Check for Double-Marubozu pattern                                 |
//+------------------------------------------------------------------+
void CheckDoubleMarubozu(MqlRates &rates[], string tf_string, ENUM_TIMEFRAMES tf)
{
   // Candle 1 (older) and Candle 2 (newer)
   MqlRates candle1 = rates[0];
   MqlRates candle2 = rates[1];

   // Bodies and ranges
   double body1  = MathAbs(candle1.close - candle1.open);
   double body2  = MathAbs(candle2.close - candle2.open);

   // Rule-3 size filter
   if(body2 == 0 || body1 / body2 < BodyRatio)
      return;

   // ---------------------------------------------------------------
   // Bullish Double-Marubozu
   // ---------------------------------------------------------------
   if(candle1.close > candle1.open && candle2.close > candle2.open)
   {
      if(candle2.close <= candle1.high)   return;   // rule-1
      if(candle2.low   <  candle1.open)   return;   // rule-2

      double zone_high = candle1.high;   // high of first marubozu
      double zone_low  = candle2.low;    // low  of second marubozu

      CreateZone(candle2.time,           // <-- start at second candle
                 zone_high,
                 zone_low,
                 true, "DM", tf_string,
                 BullDM_FillColor, BullDM_BorderColor, tf);
   }

   // ---------------------------------------------------------------
   // Bearish Double-Marubozu
   // ---------------------------------------------------------------
   if(candle1.close < candle1.open && candle2.close < candle2.open)
   {
      if(candle2.close >= candle1.low)   return;   // rule-1
      if(candle2.high  >  candle1.open)  return;   // rule-2

      double zone_high = candle2.high;   // high of second marubozu
      double zone_low  = candle1.low;    // low  of first  marubozu

      CreateZone(candle2.time,           // <-- start at second candle
                 zone_high,
                 zone_low,
                 false,"DM", tf_string,
                 BearDM_FillColor, BearDM_BorderColor, tf);
   }
}

//+------------------------------------------------------------------+
//| Create new zone                                                   |
//+------------------------------------------------------------------+
void CreateZone(datetime time, double high, double low, bool bullish,
                string pattern, string tf, color fill, color border,
                ENUM_TIMEFRAMES tf_enum)
{
   // Don't create invalid zones
   if(high <= low)
      return;
      
   CZone *zone = new CZone();
   
   if(zone.Create(time, high, low, bullish, pattern, tf, fill, border, tf_enum))
   {
      g_zones.Add(zone);
      
      // Debug print
      Print("Created ", pattern, " zone on ", tf, " at ", TimeToString(time), 
            " High: ", DoubleToString(high, _Digits), 
            " Low: ", DoubleToString(low, _Digits));
   }
   else
   {
      delete zone;
   }
}

//+------------------------------------------------------------------+
//| Check for Malaysian SNR patterns                                  |
//+------------------------------------------------------------------+
void CheckMalaysianSNR(ENUM_TIMEFRAMES tf, string tf_string)
{
   // Check patterns at bar index 4 (need up to 4 bars for pattern)
   CheckSNRPatternsAtBar(tf, tf_string, 4);
}

//+------------------------------------------------------------------+
//| Check SNR patterns at specific bar                                |
//+------------------------------------------------------------------+
void CheckSNRPatternsAtBar(ENUM_TIMEFRAMES tf, string tf_string, int bar_index)
{
   // Need at least bar_index + 1 bars
   if(iBars(_Symbol, tf) < bar_index + 1)
      return;
      
   // Check for Bullish V patterns
   CheckBullishV(tf, tf_string, bar_index);
   
   // Check for Bearish Λ patterns
   CheckBearishLambda(tf, tf_string, bar_index);
}

//+------------------------------------------------------------------+
//| Check for Bullish V patterns                                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Check for Bullish V patterns (CORRECTED LOGIC)                     |
//+------------------------------------------------------------------+
void CheckBullishV(ENUM_TIMEFRAMES tf, string tf_string, int bar_index)
{
   MqlRates rates[];
   
   // Pattern 1: Bearish + 3 Bullish
   if(CopyRates(_Symbol, tf, bar_index - 3, 4, rates) == 4)
   {
      if(rates[0].close < rates[0].open &&  // Bearish
         rates[1].close > rates[1].open &&  // Bullish 1
         rates[2].close > rates[2].open &&  // Bullish 2
         rates[3].close > rates[3].open &&  // Bullish 3
         rates[3].close > rates[0].high)    // 3rd closes above bearish
      {
         // Use rates[0].time to anchor at the bearish candle
         CreateSNRLine(rates[0].time, rates[0].close, true, tf_string, tf);
         // NO return; statement here
      }
   }
   
   // Pattern 2: Bearish + 1 Bullish (with wick condition)
   if(CopyRates(_Symbol, tf, bar_index - 1, 2, rates) == 2)
   {
      if(rates[0].close < rates[0].open &&  // Bearish
         rates[1].close > rates[1].open &&  // Bullish
         rates[1].close > rates[0].high)    // Closes above bearish high
      {
         // Check wick condition
         double bullish_range = rates[1].high - rates[1].low;
         double wick_threshold = rates[1].low + (bullish_range * Pattern2_WickPercent / 100.0);
         
         if(rates[0].high < wick_threshold)
         {
            // Use rates[0].time to anchor at the bearish candle
            CreateSNRLine(rates[0].time, rates[0].close, true, tf_string, tf);
            // NO return; statement here
         }
      }
   }
   
   // Pattern 3: Bearish + 2 Bullish
   if(CopyRates(_Symbol, tf, bar_index - 2, 3, rates) == 3)
   {
      if(rates[0].close < rates[0].open &&  // Bearish
         rates[1].close > rates[1].open &&  // Bullish 1
         rates[2].close > rates[2].open &&  // Bullish 2
         rates[2].close > rates[0].high)    // 2nd closes above bearish
      {
         // Use rates[0].time to anchor at the bearish candle
         CreateSNRLine(rates[0].time, rates[0].close, true, tf_string, tf);
         // NO return; statement here
      }
   }
   
   // Pattern 4: Bearish + 2 Bullish (50% rule)
   if(CopyRates(_Symbol, tf, bar_index - 2, 3, rates) == 3)
   {
      if(rates[0].close < rates[0].open &&  // Bearish
         rates[1].close > rates[1].open &&  // Bullish 1
         rates[2].close > rates[2].open)    // Bullish 2
      {
         double bearish_range = rates[0].high - rates[0].low;
         double fifty_percent = rates[0].low + (bearish_range * 0.5);
         
         if(rates[2].close >= fifty_percent)
         {
            // Use rates[0].time to anchor at the bearish candle
            CreateSNRLine(rates[0].time, rates[0].close, true, tf_string, tf);
            // NO return; statement here
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Check for Bearish Λ patterns                                      |
//+------------------------------------------------------------------+
void CheckBearishLambda(ENUM_TIMEFRAMES tf, string tf_string, int bar_index)
{
   MqlRates rates[];
   
   // Pattern 1: Bullish + 3 Bearish
   if(CopyRates(_Symbol, tf, bar_index - 3, 4, rates) == 4)
   {
      if(rates[0].close > rates[0].open &&  // Bullish
         rates[1].close < rates[1].open &&  // Bearish 1
         rates[2].close < rates[2].open &&  // Bearish 2
         rates[3].close < rates[3].open &&  // Bearish 3
         rates[3].close < rates[0].low)     // 3rd closes below bullish
      {
         // Use rates[0].time to anchor at the bullish candle
         CreateSNRLine(rates[0].time, rates[0].close, false, tf_string, tf);
         // NO return; statement here
      }
   }
   
   // Pattern 2: Bullish + 1 Bearish (with wick condition)
   if(CopyRates(_Symbol, tf, bar_index - 1, 2, rates) == 2)
   {
      if(rates[0].close > rates[0].open &&  // Bullish
         rates[1].close < rates[1].open &&  // Bearish
         rates[1].close < rates[0].low)     // Closes below bullish low
      {
         // Check wick condition
         double bearish_range = rates[1].high - rates[1].low;
         double wick_threshold = rates[1].high - (bearish_range * Pattern2_WickPercent / 100.0);
         
         if(rates[0].low > wick_threshold)
         {
            // Use rates[0].time to anchor at the bullish candle
            CreateSNRLine(rates[0].time, rates[0].close, false, tf_string, tf);
            // NO return; statement here
         }
      }
   }
   
   // Pattern 3: Bullish + 2 Bearish
   if(CopyRates(_Symbol, tf, bar_index - 2, 3, rates) == 3)
   {
      if(rates[0].close > rates[0].open &&  // Bullish
         rates[1].close < rates[1].open &&  // Bearish 1
         rates[2].close < rates[2].open &&  // Bearish 2
         rates[2].close < rates[0].low)     // 2nd closes below bullish
      {
         // Use rates[0].time to anchor at the bullish candle
         CreateSNRLine(rates[0].time, rates[0].close, false, tf_string, tf);
         // NO return; statement here
      }
   }
   
   // Pattern 4: Bullish + 2 Bearish (50% rule)
   if(CopyRates(_Symbol, tf, bar_index - 2, 3, rates) == 3)
   {
      if(rates[0].close > rates[0].open &&  // Bullish
         rates[1].close < rates[1].open &&  // Bearish 1
         rates[2].close < rates[2].open)    // Bearish 2
      {
         double bullish_range = rates[0].high - rates[0].low;
         double fifty_percent = rates[0].high - (bullish_range * 0.5);
         
         if(rates[2].close <= fifty_percent)
         {
            // Use rates[0].time to anchor at the bullish candle
            CreateSNRLine(rates[0].time, rates[0].close, false, tf_string, tf);
            // NO return; statement here
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Create new SNR line                                               |
//+------------------------------------------------------------------+
void CreateSNRLine(datetime time, double price, bool is_support,
                   string tf, ENUM_TIMEFRAMES tf_enum)
{
   // Find the timeframe index
   int tf_index = -1;
   for(int i = 0; i < g_snr_tf_count; i++)
   {
      if(g_snr_timeframes[i] == tf_enum)
      {
         tf_index = i;
         break;
      }
   }
   
   if(tf_index < 0)
      return; // Timeframe not found
   
   // A new line is always FRESH. Check its specific S/R limit.
   CountSNRLinesPerTimeframe();
   
   if(is_support)
   {
      // We are creating a FRESH SUPPORT line. Check its limit.
      while(g_snr_fresh_support_count[tf_index] >= g_snr_max_fresh_pairs[tf_index] && g_snr_max_fresh_pairs[tf_index] >= 0)
      {
         RemoveOldestSNRLine(tf_enum, true, true); // remove oldest fresh support
         CountSNRLinesPerTimeframe(); // Recount after removal
      }
   }
   else // is_resistance
   {
      // We are creating a FRESH RESISTANCE line. Check its limit.
      while(g_snr_fresh_resistance_count[tf_index] >= g_snr_max_fresh_pairs[tf_index] && g_snr_max_fresh_pairs[tf_index] >= 0)
      {
         RemoveOldestSNRLine(tf_enum, true, false); // remove oldest fresh resistance
         CountSNRLinesPerTimeframe(); // Recount after removal
      }
   }
   
   
   // Create the new line
   CSNRLine *line = new CSNRLine();
   
   if(line.Create(time, price, is_support, tf, tf_enum))
   {
      g_snr_lines.Add(line);
      CountSNRLinesPerTimeframe(); // Recount to update the new line's presence
      
      // Debug print
      Print("Created ", (is_support ? "Support" : "Resistance"), 
            " line on ", tf, " at ", TimeToString(time), 
            " Price: ", DoubleToString(price, _Digits));
   }
   else
   {
      delete line;
   }
   
   // Also check total limit as a fallback
   while(g_snr_lines.Total() > Max_SNR_Lines)
      g_snr_lines.Delete(0);
}

//+------------------------------------------------------------------+
//| Timer function (optional for future use)                          |
//+------------------------------------------------------------------+
void OnTimer()
{
   // Reserved for future alert functionality
}

//+------------------------------------------------------------------+
//| Trade function (optional for future trading logic)                |
//+------------------------------------------------------------------+
void OnTrade()
{
   // Reserved for future trading functionality
}

//+------------------------------------------------------------------+
//| ChartEvent function                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // Handle chart events if needed
}

in this code No. of lines to display each no. reperesents a pair of Support and resistance right to left for that perticular TF , if lets say we select 1 for H4 then it will pair one fresh SUpport with one Fresh Resistance and display both ignoring other TF for pairing , also only pair fresh with fresh and unfresh with unfresh. discuss