//+------------------------------------------------------------------+
//|   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       |
//+------------------------------------------------------------------+
#property copyright ""
#property version   "1.6"
#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

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

//+------------------------------------------------------------------+
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 --
// 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, i.e. bars[0] = bar at 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;
}
//+------------------------------------------------------------------+

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

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

    // --- Fair Value Gap detection ---
    int fvgSignal = 0;
    if(useFairValueGapFilter)
    {
       // Using shift=1 means last fully formed candles excluding current candle
       fvgSignal = DetectFairValueGap(swingTF, 1);  
       if(fvgSignal == 0)
       {
          if(currentCandleTime != lastFvgNoTradePrintCandle)
          {
              Print("No Fair Value Gap detected, skipping new entries per FVG filter.");
              lastFvgNoTradePrintCandle = currentCandleTime;
          }
          return;  // Skip trades if no FVG detected and filter enabled
       }
       else
       {
          lastFvgNoTradePrintCandle = 0;
       }
    }

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

    if(!enableReverseEntries)
    {
        if(ask > lastSwingHigh && is_uptrend && !IsPositionOpen(POSITION_TYPE_BUY))
        {
            if(!useFairValueGapFilter || fvgSignal == 1)
            {
                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);

                if(!OrderSend(request, result))
                    PrintFormat("Buy order send failed, error %d", GetLastError());
                else if(result.retcode != TRADE_RETCODE_DONE)
                    PrintFormat("Buy order rejected, retcode %d", result.retcode);
                else
                {
                    PrintFormat("Buy opened @ %.5f lot %.2f SL %.5f TP %.5f", ask, lot, request.sl, request.tp);
                    lastTradeCandleTime = currentCandleTime;
                    lastTradeOpenTime = now;
                }
            }
        }
        else if(bid < lastSwingLow && is_downtrend && !IsPositionOpen(POSITION_TYPE_SELL))
        {
            if(!useFairValueGapFilter || fvgSignal == -1)
            {
                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);

                if(!OrderSend(request, result))
                    PrintFormat("Sell order send failed, error %d", GetLastError());
                else if(result.retcode != TRADE_RETCODE_DONE)
                    PrintFormat("Sell order rejected, retcode %d", result.retcode);
                else
                {
                    PrintFormat("Sell opened @ %.5f lot %.2f SL %.5f TP %.5f", bid, lot, request.sl, request.tp);
                    lastTradeCandleTime = currentCandleTime;
                    lastTradeOpenTime = now;
                }
            }
        }
    }
    else
    {
        if(ask > lastSwingHigh && is_downtrend && !IsPositionOpen(POSITION_TYPE_SELL))
        {
            if(!useFairValueGapFilter || fvgSignal == -1)
            {
                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);

                if(!OrderSend(request, result))
                    PrintFormat("Reverse Sell order send failed, error %d", GetLastError());
                else if(result.retcode != TRADE_RETCODE_DONE)
                    PrintFormat("Reverse Sell order rejected, retcode %d", result.retcode);
                else
                {
                    PrintFormat("Reverse Sell opened @ %.5f lot %.2f SL %.5f TP %.5f", bid, lot, request.sl, request.tp);
                    lastTradeCandleTime = currentCandleTime;
                    lastTradeOpenTime = now;
                }
            }
        }
        else if(bid < lastSwingLow && is_uptrend && !IsPositionOpen(POSITION_TYPE_BUY))
        {
            if(!useFairValueGapFilter || fvgSignal == 1)
            {
                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);

                if(!OrderSend(request, result))
                    PrintFormat("Reverse Buy order send failed, error %d", GetLastError());
                else if(result.retcode != TRADE_RETCODE_DONE)
                    PrintFormat("Reverse Buy order rejected, retcode %d", result.retcode);
                else
                {
                    PrintFormat("Reverse Buy opened @ %.5f lot %.2f SL %.5f TP %.5f", ask, lot, request.sl, request.tp);
                    lastTradeCandleTime = currentCandleTime;
                    lastTradeOpenTime = now;
                }
            }
        }
    }
}
//+------------------------------------------------------------------+

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