//+------------------------------------------------------------------+
//|   Break of Structure EA with floating risk cap (full, compile)   |
//|     Updated swing detection to classical swing points method     |
//+------------------------------------------------------------------+
#property copyright ""
#property version   "1.8"
#property strict
#include <Trade\Trade.mqh>

input double      inpLot                 = 0.05;
input bool        useRiskPercent         = true;
input double      riskPercent            = 1.0;   // Position risk % per trade
input int         sl_atr_mult            = 3;
input double      r2r_ratio              = 2.0;
input ENUM_TIMEFRAMES volatilityTF       = PERIOD_M5;
input int         atrPeriod              = 14;
input int         swingLookback          = 20;
input ENUM_TIMEFRAMES swingTF            = PERIOD_M1;

// Shortest-term MA (existing)
input int         maPeriod               = 50;
input ENUM_TIMEFRAMES maTimeframe        = PERIOD_M15;

// Medium-term MA (new)
input int         maPeriod2              = 100;
input ENUM_TIMEFRAMES maTimeframe2       = PERIOD_M15;

// Long-term MA (new)
input int         maPeriod3              = 200;
input ENUM_TIMEFRAMES maTimeframe3       = PERIOD_M15;

input int         tradeStartHour         = 7;
input int         tradeEndHour           = 22;
input int         maxPositions           = 1;
input uint        inpMagicNumber         = 123456;

input bool        enableReverseEntries      = true;
input double      maxRiskPerTradePercent    = 1.0;  // Floating loss cap only!
input double      maxDailyLossAmount        = 350.0;

// --- ADX filter inputs ---
input int    adxPeriod       = 14;              // ADX period
input double adxThreshold    = 25.0;            // ADX minimum threshold to allow trading
input ENUM_TIMEFRAMES adxTimeframe = PERIOD_M15; // ADX timeframe to use for filtering

// --- Losing streak inputs ---
input int maxLosingStreak = 3;   // Max consecutive losing trades allowed

// --- POST-CLOSE COOLDOWN ---
input int postCloseCooldownSeconds = 180; // Cooldown seconds after a position closes before new trades allowed.

// --- Fair Value Gap filter input ---
input bool useFairValueGapFilter = true;  // Enable Fair Value Gap filter for entries

// --- Breakout wait bars timeout (how many bars to wait for FVG after breakout) ---
input int maxBreakoutWaitBars = 3;

// --- Classical Swing detection strength ---
input int swingStrength = 3;              // Number of bars on each side for classical swing detection

CTrade trade;

int ma_handle = INVALID_HANDLE;
int ma_handle2 = INVALID_HANDLE;    // Medium-term MA handle
int ma_handle3 = INVALID_HANDLE;    // Long-term MA handle
int atr_handle = INVALID_HANDLE;
int adx_handle = INVALID_HANDLE;  // ADX handle

string swingHighLineName = "SwingHighLine";
string swingLowLineName  = "SwingLowLine";


static double g_initialEquity = 0.0;
static datetime g_lastResetDay = 0;

static int g_currentLosingStreak = 0;
static datetime g_lastLosingStreakResetDay = 0;
static ulong g_lastHistoryTickTime = 0;

// *** Cooldown globals ***
datetime lastTradeCandleTime = 0;                 // last candle time when trade opened
ENUM_TIMEFRAMES cooldownTimeframe = swingTF;      // timeframe for candle cooldown

datetime lastTradeOpenTime = 0;                    // last trade open timestamp
int tradeIntervalSeconds = 180;                     // min seconds between trades

// --- New for breakout tracking ---
bool breakoutDetected = false;
int breakoutDirection = 0; // 1 = bullish breakout, -1 = bearish breakout
datetime breakoutCandleTime = 0;
int breakoutWaitCounter = 0; // number of bars waited for FVG since breakout

//+------------------------------------------------------------------+
void ResetDailyEquityIfNewDay()
{
    datetime now = TimeCurrent();
    MqlDateTime tm;
    TimeToStruct(now, tm);

    MqlDateTime todayMidnight = {};
    todayMidnight.year = tm.year;
    todayMidnight.mon  = tm.mon;
    todayMidnight.day  = tm.day;
    todayMidnight.hour = 0;
    todayMidnight.min  = 0;
    todayMidnight.sec  = 0;
    datetime midnightTime = StructToTime(todayMidnight);

    if(g_lastResetDay != midnightTime)
    {
        g_lastResetDay = midnightTime;
        g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY);
        PrintFormat("Daily equity reset at %s new initialEquity=%.2f", TimeToString(now, TIME_DATE|TIME_SECONDS), g_initialEquity);
    }
}
//+------------------------------------------------------------------+
void ResetLosingStreakIfNewDay()
{
    datetime now = TimeCurrent();
    MqlDateTime tm;
    TimeToStruct(now, tm);

    MqlDateTime todayMidnight = {};
    todayMidnight.year = tm.year;
    todayMidnight.mon = tm.mon;
    todayMidnight.day = tm.day;
    todayMidnight.hour = 0;
    todayMidnight.min = 0;
    todayMidnight.sec = 0;

    datetime midnightTime = StructToTime(todayMidnight);

    if(g_lastLosingStreakResetDay != midnightTime)
    {
        g_lastLosingStreakResetDay = midnightTime;
        g_currentLosingStreak = 0;
        PrintFormat("Losing streak reset at new day %s", TimeToString(now, TIME_DATE|TIME_SECONDS));
    }
}
//+------------------------------------------------------------------+
void UpdateLosingStreak()
{
    datetime now = TimeCurrent();

    if(!HistorySelect(now - 86400, now))
    {
        Print("HistorySelect failed");
        return;
    }

    ulong maxClosedTime = 0;
    int total = HistoryDealsTotal();

    for(int i = total - 1; i >= 0; i--)
    {
        ulong ticket = HistoryDealGetTicket(i);
        datetime closeTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);

        if(closeTime <= g_lastHistoryTickTime)
            continue;

        maxClosedTime = MathMax(maxClosedTime, (ulong)closeTime);

        string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
        ulong magic = HistoryDealGetInteger(ticket, DEAL_MAGIC);

        if(symbol != _Symbol || magic != inpMagicNumber)
            continue;

        long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);

        if(entryType != DEAL_ENTRY_OUT)
            continue;

        double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);

        if(profit < 0)
        {
            g_currentLosingStreak++;
            PrintFormat("Losing trade detected (ticket %I64d, profit %.2f). Current losing streak: %d", ticket, profit, g_currentLosingStreak);
        }
        else
        {
            if(g_currentLosingStreak > 0)
                PrintFormat("Winning trade detected (ticket %I64d, profit %.2f). Resetting losing streak.", ticket, profit);
            g_currentLosingStreak = 0;
        }
    }

    if(maxClosedTime > 0)
        g_lastHistoryTickTime = maxClosedTime;
}
//+------------------------------------------------------------------+
double CalculateLotSize(double sl_distance_price)
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   if(balance <= 0 || sl_distance_price <= 0)
      return inpLot;

   if(!useRiskPercent)
   {
      double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
      double min_lot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
      double max_lot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
      double lot = inpLot;
      if(lot < min_lot) lot = min_lot;
      if(lot > max_lot) lot = max_lot;
      lot = MathFloor(lot / lot_step) * lot_step;
      return lot;
   }
   else
   {
      double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
      double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
      double contract_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);

      if(tick_value <= 0 || tick_size <= 0 || contract_size <= 0)
         return inpLot;

      double point_value = tick_value / tick_size;
      double calculatedLot = (balance * (riskPercent / 100.0)) / (sl_distance_price * point_value * contract_size);

      if(calculatedLot <= 0)
         return inpLot;

      double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
      double min_lot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
      double max_lot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

      double lot = MathFloor(calculatedLot / lot_step) * lot_step;

      if(lot < min_lot) lot = min_lot;
      if(lot > max_lot) lot = max_lot;
      return lot;
   }
}
//+------------------------------------------------------------------+
bool IsPositionOpen(ENUM_POSITION_TYPE type)
{
   int total = PositionsTotal();
   for(int i = 0; i < total; i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == (long)inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         {
            ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
            if(pos_type == type)
               return true;
         }
      }
   }
   return false;
}
//+------------------------------------------------------------------+

//-- Fair Value Gap detection function --
// Returns:
//  1 if bullish FVG detected (Candle1 High < Candle3 Low)
// -1 if bearish FVG detected (Candle1 Low > Candle3 High)
//  0 if no gap or error
int DetectFairValueGap(ENUM_TIMEFRAMES timeframe, int startShift)
{
   MqlRates bars[3];
   // Copy 3 bars starting from startShift
   if(CopyRates(_Symbol, timeframe, startShift, 3, bars) != 3)
   {
      Print("DetectFairValueGap: failed to copy 3 bars");
      return 0;
   }
   
   // Candle1 = bars[0], Candle2 = bars[1], Candle3 = bars[2]
   double c1_high = bars[0].high;
   double c1_low  = bars[0].low;
   double c3_high = bars[2].high;
   double c3_low  = bars[2].low;
   
   // Bullish FVG: Candle1 High < Candle3 Low
   if(c1_high < c3_low)
   {
      double gapBull = c3_low - c1_high;
      PrintFormat("Bullish Fair Value Gap detected between candle1 high %.5f and candle3 low %.5f (Gap %.5f)", c1_high, c3_low, gapBull);
      return 1;
   }
   
   // Bearish FVG: Candle1 Low > Candle3 High
   if(c1_low > c3_high)
   {
      double gapBear = c1_low - c3_high;
      PrintFormat("Bearish Fair Value Gap detected between candle1 low %.5f and candle3 high %.5f (Gap %.5f)", c1_low, c3_high, gapBear);
      return -1;
   }
   
   return 0;
}
//+------------------------------------------------------------------+
// Classical swing detection functions -----------------------------+
int FindRecentSwingHigh(int startShift, int searchBars, int strength, ENUM_TIMEFRAMES tf)
{
   MqlRates rates[];
   int neededBars = searchBars + strength + 1;

   if(CopyRates(_Symbol, tf, startShift, neededBars, rates) != neededBars)
   {
      Print("FindRecentSwingHigh: Failed to get enough bars");
      return -1;
   }

   for(int i=0; i < searchBars; i++)
   {
      bool isSwingHigh = true;
      double currentHigh = rates[i].high;

      // Bars before
      for(int j=1; j <= strength; j++)
      {
         if(i + j >= ArraySize(rates)) break;
         if(currentHigh <= rates[i + j].high)
         {
            isSwingHigh = false;
            break;
         }
      }
      if(!isSwingHigh) continue;

      // Bars after
      for(int j=1; j <= strength; j++)
      {
         if(i - j < 0) break;
         if(currentHigh <= rates[i - j].high)
         {
            isSwingHigh = false;
            break;
         }
      }

      if(isSwingHigh)
      {
         return startShift + i;
      }
   }
   return -1;
}

int FindRecentSwingLow(int startShift, int searchBars, int strength, ENUM_TIMEFRAMES tf)
{
   MqlRates rates[];
   int neededBars = searchBars + strength + 1;

   if(CopyRates(_Symbol, tf, startShift, neededBars, rates) != neededBars)
   {
      Print("FindRecentSwingLow: Failed to get enough bars");
      return -1;
   }

   for(int i=0; i < searchBars; i++)
   {
      bool isSwingLow = true;
      double currentLow = rates[i].low;

      // Bars before
      for(int j=1; j <= strength; j++)
      {
         if(i + j >= ArraySize(rates)) break;
         if(currentLow >= rates[i + j].low)
         {
            isSwingLow = false;
            break;
         }
      }
      if(!isSwingLow) continue;

      // Bars after
      for(int j=1; j <= strength; j++)
      {
         if(i - j < 0) break;
         if(currentLow >= rates[i - j].low)
         {
            isSwingLow = false;
            break;
         }
      }

      if(isSwingLow)
      {
         return startShift + i;
      }
   }
   return -1;
}
//+------------------------------------------------------------------+

int OnInit()
{
   ma_handle = iMA(_Symbol, maTimeframe, maPeriod, 0, MODE_EMA, PRICE_CLOSE);
   if(ma_handle == INVALID_HANDLE)
   {
      Print("Failed to create EMA handle");
      return INIT_FAILED;
   }

   ma_handle2 = iMA(_Symbol, maTimeframe2, maPeriod2, 0, MODE_EMA, PRICE_CLOSE);
   if(ma_handle2 == INVALID_HANDLE)
   {
      Print("Failed to create medium-term EMA handle");
      IndicatorRelease(ma_handle);
      return INIT_FAILED;
   }

   ma_handle3 = iMA(_Symbol, maTimeframe3, maPeriod3, 0, MODE_EMA, PRICE_CLOSE);
   if(ma_handle3 == INVALID_HANDLE)
   {
      Print("Failed to create long-term EMA handle");
      IndicatorRelease(ma_handle);
      IndicatorRelease(ma_handle2);
      return INIT_FAILED;
   }

   atr_handle = iATR(_Symbol, volatilityTF, atrPeriod);
   if(atr_handle == INVALID_HANDLE)
   {
      Print("Failed to create ATR handle");
      IndicatorRelease(ma_handle);
      IndicatorRelease(ma_handle2);
      IndicatorRelease(ma_handle3);
      return INIT_FAILED;
   }

   adx_handle = iADX(_Symbol, adxTimeframe, adxPeriod);
   if(adx_handle == INVALID_HANDLE)
   {
      Print("Failed to create ADX handle");
      IndicatorRelease(ma_handle);
      IndicatorRelease(ma_handle2);
      IndicatorRelease(ma_handle3);
      IndicatorRelease(atr_handle);
      return INIT_FAILED;
   }

   if(!ObjectCreate(0, swingHighLineName, OBJ_HLINE, 0, TimeCurrent(), 0))
   {
      Print("Failed to create swing high line");
      IndicatorRelease(ma_handle);
      IndicatorRelease(ma_handle2);
      IndicatorRelease(ma_handle3);
      IndicatorRelease(atr_handle);
      IndicatorRelease(adx_handle);
      return INIT_FAILED;
   }
   ObjectSetInteger(0, swingHighLineName, OBJPROP_COLOR, clrGreen);
   ObjectSetInteger(0, swingHighLineName, OBJPROP_WIDTH, 2);
   ObjectSetInteger(0, swingHighLineName, OBJPROP_BACK, true);
   ObjectSetInteger(0, swingHighLineName, OBJPROP_RAY_RIGHT, true);

   if(!ObjectCreate(0, swingLowLineName, OBJ_HLINE, 0, TimeCurrent(), 0))
   {
      Print("Failed to create swing low line");
      ObjectDelete(0, swingHighLineName);
      IndicatorRelease(ma_handle);
      IndicatorRelease(ma_handle2);
      IndicatorRelease(ma_handle3);
      IndicatorRelease(atr_handle);
      IndicatorRelease(adx_handle);
      return INIT_FAILED;
   }
   ObjectSetInteger(0, swingLowLineName, OBJPROP_COLOR, clrRed);
   ObjectSetInteger(0, swingLowLineName, OBJPROP_WIDTH, 2);
   ObjectSetInteger(0, swingLowLineName, OBJPROP_BACK, true);
   ObjectSetInteger(0, swingLowLineName, OBJPROP_RAY_RIGHT, true);
   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+

void OnDeinit(const int reason)
{
   ObjectDelete(0, swingHighLineName);
   ObjectDelete(0, swingLowLineName);

   if(ma_handle != INVALID_HANDLE)
   {
      IndicatorRelease(ma_handle);
      ma_handle = INVALID_HANDLE;
   }
   if(ma_handle2 != INVALID_HANDLE)
   {
      IndicatorRelease(ma_handle2);
      ma_handle2 = INVALID_HANDLE;
   }
   if(ma_handle3 != INVALID_HANDLE)
   {
      IndicatorRelease(ma_handle3);
      ma_handle3 = INVALID_HANDLE;
   }
   if(atr_handle != INVALID_HANDLE)
   {
      IndicatorRelease(atr_handle);
      atr_handle = INVALID_HANDLE;
   }
   if(adx_handle != INVALID_HANDLE)
   {
      IndicatorRelease(adx_handle);
      adx_handle = INVALID_HANDLE;
   }
}
//+------------------------------------------------------------------+

// Static variables to throttle print frequency
static datetime lastFvgNoTradePrintCandle = 0;
static datetime lastAdxBelowThresholdPrintCandle = 0;
static datetime lastPostCloseCooldownPrintTime = 0;

void OnTick()
{
    datetime now = TimeCurrent();

    // Post-close cooldown message throttled (once every 60s)
    if(postCloseCooldownSeconds > 0 && IsRecentPositionClose(postCloseCooldownSeconds))
    {
        if (now - lastPostCloseCooldownPrintTime >= 60)
        {
            PrintFormat("Post-close cooldown active, skipping trades at %s", TimeToString(now));
            lastPostCloseCooldownPrintTime = now;
        }
        return;
    }
    else
    {
        lastPostCloseCooldownPrintTime = 0;
    }

    ResetDailyEquityIfNewDay();
    ResetLosingStreakIfNewDay();

    UpdateLosingStreak();

    if(maxLosingStreak > 0 && g_currentLosingStreak >= maxLosingStreak)
    {
        PrintFormat("Current losing streak %d >= max allowed %d. No new trades allowed now.", g_currentLosingStreak, maxLosingStreak);
        return;
    }

    if(g_initialEquity == 0.0)
        g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY);

    double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    double equityDrawdown = g_initialEquity - currentEquity;

    if(equityDrawdown >= maxDailyLossAmount)
    {
        PrintFormat("Max combined daily loss $%.2f exceeded (drawdown=%.2f). No new trades allowed.", maxDailyLossAmount, equityDrawdown);
        return;
    }

    //--- FLOATING RISK CLOSE LOGIC ---
    if(maxRiskPerTradePercent > 0.0)
    {
        int total = PositionsTotal();
        double balance = AccountInfoDouble(ACCOUNT_BALANCE);
        double maxLossMoney = balance * (maxRiskPerTradePercent / 100.0);

        for(int i=total-1; i>=0; i--)
        {
            ulong ticket = PositionGetTicket(i);
            if(PositionSelectByTicket(ticket))
            {
                if(PositionGetInteger(POSITION_MAGIC) == (long)inpMagicNumber &&
                   PositionGetString(POSITION_SYMBOL) == _Symbol)
                {
                    double currentProfit = PositionGetDouble(POSITION_PROFIT);
                    if(currentProfit < 0 && MathAbs(currentProfit) >= maxLossMoney)
                    {
                        PrintFormat("Closing position %I64d as floating loss reached %.2f (cap is %.2f)",
                                    ticket, MathAbs(currentProfit), maxLossMoney);
                        trade.PositionClose(ticket);
                    }
                }
            }
        }
    }

    //--- POSITION LIMIT CHECK ---
    int openPosCount = 0, totalPositions = PositionsTotal();
    for(int i=0; i<totalPositions; i++)
    {
        ulong ticket = PositionGetTicket(i);
        if(PositionSelectByTicket(ticket))
        {
            if(PositionGetInteger(POSITION_MAGIC) == (long)inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
            {
                openPosCount++;
            }
        }
    }
    if(maxPositions > 0 && openPosCount >= maxPositions)
        return;

    //--- TRADING TIME ---
    MqlDateTime tm;
    TimeToStruct(TimeCurrent(), tm);
    if(tm.hour < tradeStartHour || tm.hour >= tradeEndHour)
        return;

    // --- MINIMUM TIME INTERVAL COOLDOWN CHECK ---
    if(lastTradeOpenTime != 0 && (now - lastTradeOpenTime) < tradeIntervalSeconds)
        return; // interval cooldown active, skip trade

    // --- COOLDOWN PER CANDLE CHECK ---
    MqlRates candleRates[];
    if(CopyRates(_Symbol, cooldownTimeframe, 1, 1, candleRates) != 1)
    {
        Print("Failed to get last closed candle data for cooldown check");
        return;
    }
    datetime currentCandleTime = candleRates[0].time;
    if(currentCandleTime == lastTradeCandleTime)
        return; // candle cooldown active, skip trade

    //--- INDICATORS ---
    double ma_buf1[], ma_buf2[], ma_buf3[];
    if(CopyBuffer(ma_handle, 0, 1, 1, ma_buf1) <= 0) return;
    if(CopyBuffer(ma_handle2, 0, 1, 1, ma_buf2) <= 0) return;
    if(CopyBuffer(ma_handle3, 0, 1, 1, ma_buf3) <= 0) return;

    double MA1 = ma_buf1[0];
    double MA2 = ma_buf2[0];
    double MA3 = ma_buf3[0];

    double atr_buf[];
    if(CopyBuffer(atr_handle, 0, 1, 1, atr_buf) <= 0)
        return;
    double atr = atr_buf[0];
    if(atr <= 0)
        atr = 10.0 * _Point;

    double adx_buf[];
    if(adx_handle == INVALID_HANDLE)
    {
        Print("ADX handle invalid");
        return;
    }
    if(CopyBuffer(adx_handle, 0, 1, 1, adx_buf) <= 0)
    {
        Print("Failed to copy ADX buffer");
        return;
    }
    double adx_value = adx_buf[0];

    // ADX below threshold message throttled (once per candle)
    if(adx_value < adxThreshold)
    {
        if(currentCandleTime != lastAdxBelowThresholdPrintCandle)
        {
            PrintFormat("ADX=%.2f below threshold %.2f, skipping new entries", adx_value, adxThreshold);
            lastAdxBelowThresholdPrintCandle = currentCandleTime;
        }
        return; // skip trade entries when ADX too low
    }
    else
    {
        lastAdxBelowThresholdPrintCandle = 0;
    }

    double sl_dist = atr * sl_atr_mult;
    double tp_dist = sl_dist * r2r_ratio;

    // -- Classical swing detection used here --
    int highestBar = FindRecentSwingHigh(1, swingLookback, swingStrength, swingTF);
    int lowestBar = FindRecentSwingLow(1, swingLookback, swingStrength, swingTF);
    if(highestBar < 0 || lowestBar < 0)
        return;

    double lastSwingHigh = iHigh(_Symbol, swingTF, highestBar);
    double lastSwingLow = iLow(_Symbol, swingTF, lowestBar);

    ObjectSetDouble(0, swingHighLineName, OBJPROP_PRICE, lastSwingHigh);
    ObjectSetDouble(0, swingLowLineName, OBJPROP_PRICE, lastSwingLow);

    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    if(ask <= 0 || bid <= 0)
        return;

    bool is_uptrend = (MA1 > MA2) && (MA2 > MA3) && (ask > MA2);
    bool is_downtrend = (MA1 < MA2) && (MA2 < MA3) && (ask < MA2);

    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

    double lot = CalculateLotSize(sl_dist);
    if(lot < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
        return;

    //------------------------------------------------
    //--- Fix applied here: breakout wait counter increments per bar ONLY ---

    static datetime lastBreakoutWaitBarTime = 0;

    //--- Breakout Logic with delayed FVG confirmation starts here ---

    // First: Detect breakout if no breakoutDetected flag is set
    if(!breakoutDetected)
    {
        // Detect bullish breakout
        if(ask > lastSwingHigh)
        {
            // Confirm trend condition depending on enableReverseEntries
            if(!enableReverseEntries)
            {
                if(is_uptrend)
                {
                    breakoutDetected = true;
                    breakoutCandleTime = currentCandleTime;
                    breakoutDirection = 1; // bullish
                    breakoutWaitCounter = 0;
                    lastBreakoutWaitBarTime = 0; // Reset wait bar tracker
                    PrintFormat("Breakout detected: Bullish at %s", TimeToString(currentCandleTime));
                }
            }
            else
            {
                // Reverse entries enabled: open SELL on bullish swing break if downtrend
                if(is_downtrend)
                {
                    breakoutDetected = true;
                    breakoutCandleTime = currentCandleTime;
                    breakoutDirection = -1; // bearish reverse trade
                    breakoutWaitCounter = 0;
                    lastBreakoutWaitBarTime = 0; // Reset wait bar tracker
                    PrintFormat("Breakout detected (reverse): Bullish break with downtrend at %s", TimeToString(currentCandleTime));
                }
            }
        }
        // Detect bearish breakout
        else if(bid < lastSwingLow)
        {
            if(!enableReverseEntries)
            {
                if(is_downtrend)
                {
                    breakoutDetected = true;
                    breakoutCandleTime = currentCandleTime;
                    breakoutDirection = -1; // bearish
                    breakoutWaitCounter = 0;
                    lastBreakoutWaitBarTime = 0; // Reset wait bar tracker
                    PrintFormat("Breakout detected: Bearish at %s", TimeToString(currentCandleTime));
                }
            }
            else
            {
                // Reverse entries enabled: open BUY on bearish swing break if uptrend
                if(is_uptrend)
                {
                    breakoutDetected = true;
                    breakoutCandleTime = currentCandleTime;
                    breakoutDirection = 1; // bullish reverse trade
                    breakoutWaitCounter = 0;
                    lastBreakoutWaitBarTime = 0; // Reset wait bar tracker
                    PrintFormat("Breakout detected (reverse): Bearish break with uptrend at %s", TimeToString(currentCandleTime));
                }
            }
        }
    }
    else
    {
        // We have breakout detected, waiting for FVG confirmation after breakout

        // Get last fully closed bar time at swingTF timeframe with shift=1
        MqlRates rates[];
        if(CopyRates(_Symbol, swingTF, 1, 1, rates) != 1)
        {
            Print("Failed to get last closed bar for breakout wait");
            return;
        }
        datetime currentWaitBarTime = rates[0].time;

        if(currentWaitBarTime != lastBreakoutWaitBarTime)
        {
            breakoutWaitCounter++;
            lastBreakoutWaitBarTime = currentWaitBarTime;
            PrintFormat("Breakout wait counter incremented to %d at bar time %s", breakoutWaitCounter, TimeToString(currentWaitBarTime));
        }

        if(breakoutWaitCounter > maxBreakoutWaitBars)
        {
            PrintFormat("FVG did not appear after %d bars since breakout, resetting breakout state.", maxBreakoutWaitBars);
            breakoutDetected = false;
            breakoutDirection = 0;
            breakoutWaitCounter = 0;
            lastBreakoutWaitBarTime = 0;
        }
        else
        {
            // Check FVG now on last closed candle(s) on swingTF (shift 1)
            int fvgSignal = DetectFairValueGap(swingTF, 1);

            // Entry condition: FVG matches breakout direction
            if(useFairValueGapFilter && fvgSignal != 0)
            {
                if(fvgSignal == breakoutDirection)
                {
                    // Before opening, check position and cooldowns again
                    if(maxPositions > 0 && openPosCount >= maxPositions)
                    {
                        Print("Max positions reached, cannot open new trade.");
                        breakoutDetected = false;
                        breakoutDirection = 0;
                        breakoutWaitCounter = 0;
                        lastBreakoutWaitBarTime = 0;
                        return;
                    }

                    if(lastTradeOpenTime != 0 && (now - lastTradeOpenTime) < tradeIntervalSeconds)
                    {
                        // Still respecting minimal interval delay
                        Print("Trade interval cooldown active, waiting.");
                        return;
                    }

                    if(lastTradeCandleTime == currentCandleTime)
                    {
                        Print("Candle cooldown active, waiting for new candle.");
                        return;
                    }

                    MqlTradeRequest request = {};
                    MqlTradeResult result = {};
                    request.symbol = _Symbol;
                    request.volume = lot;
                    request.deviation = 10;
                    request.magic = inpMagicNumber;

                    if(!enableReverseEntries)
                    {
                        if(breakoutDirection == 1 && !IsPositionOpen(POSITION_TYPE_BUY))
                        {
                            // Bullish normal trade
                            request.action = TRADE_ACTION_DEAL;
                            request.type = ORDER_TYPE_BUY;
                            request.price = ask;
                            request.sl = NormalizeDouble(ask - sl_dist, digits);
                            request.tp = NormalizeDouble(ask + tp_dist, digits);
                        }
                        else if(breakoutDirection == -1 && !IsPositionOpen(POSITION_TYPE_SELL))
                        {
                            // Bearish normal trade
                            request.action = TRADE_ACTION_DEAL;
                            request.type = ORDER_TYPE_SELL;
                            request.price = bid;
                            request.sl = NormalizeDouble(bid + sl_dist, digits);
                            request.tp = NormalizeDouble(bid - tp_dist, digits);
                        }
                        else
                        {
                            Print("Position already open for this breakout direction.");
                            breakoutDetected = false;
                            breakoutDirection = 0;
                            breakoutWaitCounter = 0;
                            lastBreakoutWaitBarTime = 0;
                            return;
                        }
                    }
                    else
                    {
                        // Reverse entries enabled - logic swapped
                        if(breakoutDirection == -1 && !IsPositionOpen(POSITION_TYPE_SELL))
                        {
                            request.action = TRADE_ACTION_DEAL;
                            request.type = ORDER_TYPE_SELL;
                            request.price = bid;
                            request.sl = NormalizeDouble(bid + sl_dist, digits);
                            request.tp = NormalizeDouble(bid - tp_dist, digits);
                        }
                        else if(breakoutDirection == 1 && !IsPositionOpen(POSITION_TYPE_BUY))
                        {
                            request.action = TRADE_ACTION_DEAL;
                            request.type = ORDER_TYPE_BUY;
                            request.price = ask;
                            request.sl = NormalizeDouble(ask - sl_dist, digits);
                            request.tp = NormalizeDouble(ask + tp_dist, digits);
                        }
                        else
                        {
                            Print("Position already open for this reverse breakout direction.");
                            breakoutDetected = false;
                            breakoutDirection = 0;
                            breakoutWaitCounter = 0;
                            lastBreakoutWaitBarTime = 0;
                            return;
                        }
                    }

                    if(!OrderSend(request, result))
                        PrintFormat("Order send failed, error %d", GetLastError());
                    else if(result.retcode != TRADE_RETCODE_DONE)
                        PrintFormat("Order rejected, retcode %d", result.retcode);
                    else
                    {
                        string side = (request.type == ORDER_TYPE_BUY) ? "Buy" : "Sell";
                        PrintFormat("%s opened @ %.5f lot %.2f SL %.5f TP %.5f confirmed by FVG after breakout",
                                    side, request.price, lot, request.sl, request.tp);
                        lastTradeCandleTime = currentCandleTime;
                        lastTradeOpenTime = now;
                        breakoutDetected = false; // reset after entry
                        breakoutDirection = 0;
                        breakoutWaitCounter = 0;
                        lastBreakoutWaitBarTime = 0;
                    }
                }
                else
                {
                    // FVG present but does not match breakout dir, wait or reset later
                    PrintFormat("FVG detected %d does not match breakout direction %d, waiting for matching FVG...", fvgSignal, breakoutDirection);
                }
            }
            else if(useFairValueGapFilter)
            {
                if(currentCandleTime != lastFvgNoTradePrintCandle)
                {
                    Print("No Fair Value Gap detected after breakout, waiting...");
                    lastFvgNoTradePrintCandle = currentCandleTime;
                }
            }
        }
    }
}
//+------------------------------------------------------------------+

// IsRecentPositionClose function (scans recent closed position deals)
bool IsRecentPositionClose(uint cooldownSecs)
{
    datetime now = TimeCurrent();

    if(!HistorySelect(now - cooldownSecs - 10, now))
    {
        Print("HistorySelect failed");
        return false;
    }

    int totalDeals = HistoryDealsTotal();
    for(int i = totalDeals - 1; i >= 0; i--)
    {
        ulong ticket = HistoryDealGetTicket(i);
        if(ticket == 0)
            continue;

        datetime dealTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
        if(dealTime == 0 || (now - dealTime) > cooldownSecs)
            continue;

        string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
        ulong magic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
        long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY);

        if(symbol == _Symbol && magic == inpMagicNumber && entry == DEAL_ENTRY_OUT)
        {
            PrintFormat("Recent position close detected at %s, cooldown active.", TimeToString(dealTime));
            return true;
        }
    }

    return false;
}
//+------------------------------------------------------------------+