//+------------------------------------------------------------------+
//|   Break of Structure EA with floating risk cap (full, compile)   |
//|            Added ADX filter and Losing Streak filter             |
//|      Added cooldown per candle & minimum time interval cooldown  |
//|    Improved cooldown after close - scanning recent closed deals  |
//|        Added Fair Value Gap (FVG) filter for entry signals       |
//|     Updated to open trades when FVG appears AFTER breakout       |
//|     Added fixed swing lines feature as requested                 |
//+------------------------------------------------------------------+
#property copyright ""
#property version   "1.7"
#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        enableTrailingSL       = true;

enum ENUM_TrailingStopMode
{
   TrailingNone = 0,
   TrailingMarketStructure = 1,
   TrailingATR = 2,
   TrailingHybrid = 3
};
input ENUM_TrailingStopMode trailingStopMode = TrailingHybrid;
input int    ms_swingLookback   = 20;
input double atr_trail_mult     = 2.5;
input uint   TrailUpdateIntervalSec    = 15;
input double MinSLBufferATRMult        = 1.0;
input double TrailingProfitTriggerPerc = 0.5;
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;

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";

struct TrailingUpdateInfo
{
   ulong position_ticket;
   datetime last_update;
};
TrailingUpdateInfo g_trailingUpdateInfo[];

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

//+------------------------------------------------------------------+
//|                     --- CUSTOM SWING LINES START ---             |
//+------------------------------------------------------------------+

#define MAX_SWINGS 5

struct SwingLine
{
    double price;           // Swing price (high or low)
    datetime startTime;     // Candle open time of the swing candle
    datetime endTime;       // Time when price touched the swing line; 0 if not touched
    bool isHigh;            // True=swing high, False=swing low
    string objName;         // Chart horizontal line object name
};
SwingLine swings[MAX_SWINGS];
int swingCount = 0;

// Track last processed swingTF bar time so ProcessSwings acts once per bar
datetime lastProcessedSwingBarTime = 0;

// Helper to return seconds per timeframe
int PeriodSeconds(ENUM_TIMEFRAMES tf)
{
    switch(tf)
    {
        case PERIOD_M1: return 60;
        case PERIOD_M5: return 300;
        case PERIOD_M15: return 900;
        case PERIOD_M30: return 1800;
        case PERIOD_H1: return 3600;
        case PERIOD_H4: return 14400;
        case PERIOD_D1: return 86400;
        default: return 60; // default to 1 minute for unknown timeframe
    }
}

// Cleanup all swing lines on chart and reset
void DeleteAllSwingLines()
{
    for(int i=0; i<swingCount; i++)
        ObjectDelete(0, swings[i].objName);
    swingCount = 0;
}

// Main function to detect, track and draw fixed swing lines
void ProcessSwings()
{
    MqlRates rates[];
    if(CopyRates(_Symbol, swingTF, 1, 1, rates) != 1)
    {
        Print("ProcessSwings: failed to get last closed bar time");
        return;
    }
    datetime currentSwingBarTime = rates[0].time;
    if(currentSwingBarTime == lastProcessedSwingBarTime)
        return; // skip if processed this bar already
    lastProcessedSwingBarTime = currentSwingBarTime;

    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

    // Check existing swings for price touching the swing line
    for(int i=0; i<swingCount; i++)
    {
        if(swings[i].endTime != 0)
            continue; // already touched

        if( (swings[i].isHigh && bid >= swings[i].price) ||
            (!swings[i].isHigh && ask <= swings[i].price) )
        {
            swings[i].endTime = currentSwingBarTime;
            ObjectSetInteger(0, swings[i].objName, OBJPROP_TIME2, currentSwingBarTime);
            ObjectSetInteger(0, swings[i].objName, OBJPROP_STYLE, STYLE_DASH);
            PrintFormat("Swing line touched at %s price %.5f", TimeToString(currentSwingBarTime), swings[i].price);
        }
        else
        {
            // Extend line forward until touched
            ObjectSetInteger(0, swings[i].objName, OBJPROP_TIME2, currentSwingBarTime);
        }
    }

    // Add new swing line if:
    // - no swings exist, or
    // - last swing line was touched at least swingLookback bars ago
    if( (swingCount == 0) ||
        (swings[swingCount-1].endTime != 0 && 
         (currentSwingBarTime - swings[swingCount-1].endTime) >= swingLookback * PeriodSeconds(swingTF)) )
    {
        int highBar = iHighest(_Symbol, swingTF, MODE_HIGH, swingLookback, 1);
        int lowBar = iLowest(_Symbol, swingTF, MODE_LOW, swingLookback, 1);
        if(highBar < 0 || lowBar < 0)
            return;

        datetime highTime = iTime(_Symbol, swingTF, highBar);
        datetime lowTime = iTime(_Symbol, swingTF, lowBar);
        double highPrice = iHigh(_Symbol, swingTF, highBar);
        double lowPrice = iLow(_Symbol, swingTF, lowBar);

        bool addHighSwing = (highTime > lowTime);

        // Remove oldest if max swings reached
        if(swingCount >= MAX_SWINGS)
        {
            ObjectDelete(0, swings[0].objName);
            for(int i=1; i<swingCount; i++)
                swings[i-1] = swings[i];
            swingCount--;
        }

        SwingLine newSwing;
        if(addHighSwing)
        {
            newSwing.price = highPrice;
            newSwing.startTime = highTime;
            newSwing.isHigh = true;
        }
        else
        {
            newSwing.price = lowPrice;
            newSwing.startTime = lowTime;
            newSwing.isHigh = false;
        }
        newSwing.endTime = 0;
        newSwing.objName = StringFormat("SwingLine_%d_%d", newSwing.startTime, swingCount);

        if(!ObjectCreate(0, newSwing.objName, OBJ_HLINE, 0, newSwing.startTime, newSwing.price))
        {
            Print("Failed to create swing line object");
            return;
        }
        ObjectSetInteger(0, newSwing.objName, OBJPROP_COLOR, newSwing.isHigh ? clrGreen : clrRed);
        ObjectSetInteger(0, newSwing.objName, OBJPROP_WIDTH, 2);
        ObjectSetInteger(0, newSwing.objName, OBJPROP_RAY_RIGHT, false);
        ObjectSetInteger(0, newSwing.objName, OBJPROP_BACK, true);
        ObjectSetInteger(0, newSwing.objName, OBJPROP_TIME2, currentSwingBarTime);
        ObjectSetInteger(0, newSwing.objName, OBJPROP_STYLE, STYLE_SOLID);

        swings[swingCount++] = newSwing;

        PrintFormat("New swing %s line created at %s price %.5f",
                    newSwing.isHigh ? "HIGH" : "LOW", TimeToString(newSwing.startTime), newSwing.price);
    }
}
//+------------------------------------------------------------------+
//|                     --- CUSTOM SWING LINES END ---               |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
bool CanUpdateTrailing(ulong ticket)
{
   datetime now = TimeCurrent();
   int count = ArraySize(g_trailingUpdateInfo);
   for(int i=0; i<count; i++)
   {
      if(g_trailingUpdateInfo[i].position_ticket == ticket)
      {
         if(now - g_trailingUpdateInfo[i].last_update < (int)TrailUpdateIntervalSec)
            return false;
         g_trailingUpdateInfo[i].last_update = now;
         return true;
      }
   }
   ArrayResize(g_trailingUpdateInfo, count + 1);
   g_trailingUpdateInfo[count].position_ticket = ticket;
   g_trailingUpdateInfo[count].last_update = now;
   return true;
}
//+------------------------------------------------------------------+
void UpdateTrailingStopByMarketStructure(ulong position_ticket)
{
    if(!PositionSelectByTicket(position_ticket))
        return;

    ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
    double current_sl = PositionGetDouble(POSITION_SL);
    double current_tp = PositionGetDouble(POSITION_TP);
    double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
    string symbol = PositionGetString(POSITION_SYMBOL);

    if(current_tp <= 0)
        return;

    if(!CanUpdateTrailing(position_ticket))
        return;

    double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
    int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);

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

    if(pos_type == POSITION_TYPE_BUY)
    {
        double half_tp_level = entry_price + 0.5 * (current_tp - entry_price);
        if(bid < half_tp_level)
            return;

        int lowestBar = iLowest(symbol, PERIOD_CURRENT, MODE_LOW, ms_swingLookback, 0);
        if(lowestBar < 0)
            return;

        double swing_low = iLow(symbol, PERIOD_CURRENT, lowestBar);
        double buffer = 2 * point;
        double new_sl = NormalizeDouble(swing_low - buffer, digits);

        if(new_sl > current_sl && new_sl < entry_price)
        {
            MqlTradeRequest req = {};
            MqlTradeResult res = {};
            req.action = TRADE_ACTION_SLTP;
            req.position = position_ticket;
            req.symbol = symbol;
            req.sl = new_sl;
            req.tp = current_tp;
            req.deviation = 10;

            if(!OrderSend(req, res))
                PrintFormat("Failed to update trailing SL for position %I64d, error %d", position_ticket, GetLastError());
            else if(res.retcode != TRADE_RETCODE_DONE)
                PrintFormat("Trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
            else
                PrintFormat("Trailing SL moved up for position %I64d: new SL=%.5f", position_ticket, new_sl);
        }
    }
    else if(pos_type == POSITION_TYPE_SELL)
    {
        double half_tp_level = entry_price - 0.5 * (entry_price - current_tp);
        if(ask > half_tp_level)
            return;

        int highestBar = iHighest(symbol, PERIOD_CURRENT, MODE_HIGH, ms_swingLookback, 0);
        if(highestBar < 0)
            return;

        double swing_high = iHigh(symbol, PERIOD_CURRENT, highestBar);
        double buffer = 2 * point;
        double new_sl = NormalizeDouble(swing_high + buffer, digits);

        if(new_sl < current_sl && new_sl > entry_price)
        {
            MqlTradeRequest req = {};
            MqlTradeResult res = {};
            req.action = TRADE_ACTION_SLTP;
            req.position = position_ticket;
            req.symbol = symbol;
            req.sl = new_sl;
            req.tp = current_tp;
            req.deviation = 10;

            if(!OrderSend(req, res))
                PrintFormat("Failed to update trailing SL for position %I64d, error %d", position_ticket, GetLastError());
            else if(res.retcode != TRADE_RETCODE_DONE)
                PrintFormat("Trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
            else
                PrintFormat("Trailing SL moved down for position %I64d: new SL=%.5f", position_ticket, new_sl);
        }
    }
}
//+------------------------------------------------------------------+
void UpdateTrailingStopByATR(ulong position_ticket)
{
    if(!PositionSelectByTicket(position_ticket))
        return;

    ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
    double current_sl = PositionGetDouble(POSITION_SL);
    double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
    string symbol = PositionGetString(POSITION_SYMBOL);

    if(!CanUpdateTrailing(position_ticket))
        return;

    double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
    int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);

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

    double current_price = (pos_type == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID) 
                                                           : SymbolInfoDouble(symbol, SYMBOL_ASK);
    if(current_price <= 0) return;

    double new_sl = 0;
    if(pos_type == POSITION_TYPE_BUY)
    {
        new_sl = NormalizeDouble(current_price - atr * atr_trail_mult, digits);
        double min_sl_dist = atr * MinSLBufferATRMult;
        if(new_sl < (current_price - min_sl_dist))
            new_sl = current_price - min_sl_dist;

        if(new_sl > current_sl && new_sl < current_price)
        {
            MqlTradeRequest req={};
            MqlTradeResult res={};
            req.action = TRADE_ACTION_SLTP;
            req.position = position_ticket;
            req.symbol = symbol;
            req.sl = new_sl;
            req.tp = PositionGetDouble(POSITION_TP);
            req.deviation = 10;

            if(!OrderSend(req, res))
                PrintFormat("ATR trailing SL update failed for position %I64d, error %d", position_ticket, GetLastError());
            else if(res.retcode != TRADE_RETCODE_DONE)
                PrintFormat("ATR trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
            else
                PrintFormat("ATR trailing SL moved up for position %I64d: new SL=%.5f", position_ticket, new_sl);
        }
    }
    else if(pos_type == POSITION_TYPE_SELL)
    {
        new_sl = NormalizeDouble(current_price + atr * atr_trail_mult, digits);
        double min_sl_dist = atr * MinSLBufferATRMult;
        if(new_sl > (current_price + min_sl_dist))
            new_sl = current_price + min_sl_dist;

        if(new_sl < current_sl && new_sl > current_price)
        {
            MqlTradeRequest req={};
            MqlTradeResult res={};
            req.action = TRADE_ACTION_SLTP;
            req.position = position_ticket;
            req.symbol = symbol;
            req.sl = new_sl;
            req.tp = PositionGetDouble(POSITION_TP);
            req.deviation = 10;

            if(!OrderSend(req, res))
                PrintFormat("ATR trailing SL update failed for position %I64d, error %d", position_ticket, GetLastError());
            else if(res.retcode != TRADE_RETCODE_DONE)
                PrintFormat("ATR trailing SL update rejected for position %I64d, retcode %d", position_ticket, res.retcode);
            else
                PrintFormat("ATR trailing SL moved down for position %I64d: new SL=%.5f", position_ticket, new_sl);
        }
    }
}
//+------------------------------------------------------------------+
void UpdateTrailingStopHybrid(ulong position_ticket)
{
   if(!PositionSelectByTicket(position_ticket))
      return;

   if(!CanUpdateTrailing(position_ticket))
      return;

   ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
   double sl = PositionGetDouble(POSITION_SL);
   double tp = PositionGetDouble(POSITION_TP);
   double entry_price = PositionGetDouble(POSITION_PRICE_OPEN);
   string symbol = PositionGetString(POSITION_SYMBOL);

   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);

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

   double min_sl_distance = atr * MinSLBufferATRMult;
   double initial_sl_dist = MathAbs(entry_price - sl);
   if(initial_sl_dist <= 0) initial_sl_dist = atr * sl_atr_mult;

   double current_price = (pos_type == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID)
                                                   : SymbolInfoDouble(symbol, SYMBOL_ASK);
   if(current_price <= 0) return;

   double move_in_profit = (pos_type == POSITION_TYPE_BUY) ? (current_price - entry_price) : (entry_price - current_price);
   if(move_in_profit < TrailingProfitTriggerPerc * initial_sl_dist)
      return;

   double ms_new_sl = 0;
   bool ms_sl_valid = false;
   if(pos_type == POSITION_TYPE_BUY)
   {
      int lowestBar = iLowest(symbol, PERIOD_CURRENT, MODE_LOW, ms_swingLookback, 0);
      if(lowestBar >= 0)
      {
         double swing_low = iLow(symbol, PERIOD_CURRENT, lowestBar);
         ms_new_sl = NormalizeDouble(swing_low - 2*point, digits);
         ms_sl_valid = (ms_new_sl > sl) && (ms_new_sl < entry_price);
      }
   }
   else if(pos_type == POSITION_TYPE_SELL)
   {
      int highestBar = iHighest(symbol, PERIOD_CURRENT, MODE_HIGH, ms_swingLookback, 0);
      if(highestBar >= 0)
      {
         double swing_high = iHigh(symbol, PERIOD_CURRENT, highestBar);
         ms_new_sl = NormalizeDouble(swing_high + 2*point, digits);
         ms_sl_valid = (ms_new_sl < sl) && (ms_new_sl > entry_price);
      }
   }

   double atr_new_sl = 0;
   if(pos_type == POSITION_TYPE_BUY)
   {
      atr_new_sl = NormalizeDouble(current_price - atr * atr_trail_mult, digits);
      atr_new_sl = MathMax(atr_new_sl, current_price - min_sl_distance);
      atr_new_sl = (atr_new_sl > sl && atr_new_sl < current_price) ? atr_new_sl : 0;
   }
   else if(pos_type == POSITION_TYPE_SELL)
   {
      atr_new_sl = NormalizeDouble(current_price + atr * atr_trail_mult, digits);
      atr_new_sl = MathMin(atr_new_sl, current_price + min_sl_distance);
      atr_new_sl = (atr_new_sl < sl && atr_new_sl > current_price) ? atr_new_sl : 0;
   }

   double new_sl = 0;
   if(ms_sl_valid && atr_new_sl > 0)
   {
      if(pos_type == POSITION_TYPE_BUY)
         new_sl = MathMax(ms_new_sl, atr_new_sl);
      else
         new_sl = MathMin(ms_new_sl, atr_new_sl);
   }
   else if(ms_sl_valid)
      new_sl = ms_new_sl;
   else if(atr_new_sl > 0)
      new_sl = atr_new_sl;
   else
      return;

   bool update_sl = false;
   if(pos_type == POSITION_TYPE_BUY)
      update_sl = (new_sl > sl && new_sl < current_price);
   else
      update_sl = (new_sl < sl && new_sl > current_price);

   if(update_sl)
   {
      MqlTradeRequest req={};
      MqlTradeResult res={};
      req.action = TRADE_ACTION_SLTP;
      req.position = position_ticket;
      req.symbol = symbol;
      req.sl = new_sl;
      req.tp = tp;
      req.deviation = 10;

      if(!OrderSend(req,res))
         PrintFormat("Trailing SL update failed for position %I64d, err=%d", position_ticket, GetLastError());
      else if(res.retcode != TRADE_RETCODE_DONE)
         PrintFormat("Trailing SL update rejected for position %I64d, retcode=%d", position_ticket, res.retcode);
      else
         PrintFormat("Trailing SL updated for position %I64d: old SL=%.5f new SL=%.5f", position_ticket, sl, new_sl);
   }
}
//+------------------------------------------------------------------+

//-- Fair Value Gap detection function --
int DetectFairValueGap(ENUM_TIMEFRAMES timeframe, int startShift)
{
   MqlRates bars[3];
   if(CopyRates(_Symbol, timeframe, startShift, 3, bars) != 3)
   {
      Print("DetectFairValueGap: failed to copy 3 bars");
      return 0;
   }
   
   double c1_high = bars[0].high;
   double c1_low  = bars[0].low;
   double c3_high = bars[2].high;
   double c3_low  = bars[2].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;
   }
   
   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;
}
//+------------------------------------------------------------------+

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);

   DeleteAllSwingLines();

   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);
                    }
                }
            }
        }
    }

    //--- TRAILING SL ---
    if(enableTrailingSL && trailingStopMode != TrailingNone)
    {
       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)
             {
                if(trailingStopMode == TrailingMarketStructure)
                   UpdateTrailingStopByMarketStructure(ticket);
                else if(trailingStopMode == TrailingATR)
                   UpdateTrailingStopByATR(ticket);
                else if(trailingStopMode == TrailingHybrid)
                   UpdateTrailingStopHybrid(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;

    int highestBar = iHighest(_Symbol, swingTF, MODE_HIGH, swingLookback, 1);
    int lowestBar = iLowest(_Symbol, swingTF, MODE_LOW, swingLookback, 1);
    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;

    //------------------------------------------------
    //--- YOUR FIXED SWING LINES LOGIC ---
    ProcessSwings();
    //------------------------------------------------

    //------------------------------------------------
    //--- Breakout Logic with delayed FVG confirmation starts here ---
    // (Your existing breakout, FVG, entry logic unchanged)
    // --- (Code omitted here for brevity as it is unchanged) ---
    //------------------------------------------------
}
//+------------------------------------------------------------------+

// 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;
}
//+------------------------------------------------------------------+