//+------------------------------------------------------------------+
//| Advanced_BoS_EA_MT5.mq5                                           |
//| Author: LucienWP                                                  |
//| Version: 1.4                                                     |
//| Published: 2025-05-XX                                            |
//| Description: Break Of Structure EA                                |
//| with dynamic lot sizing, dynamic trailing stops (market structure|
//| or ATR or hybrid), throttling and safety buffers                 |
//+------------------------------------------------------------------+
#property copyright "LucienWP"
#property version   "1.4"
#property strict
#property script_show_inputs

#include <Trade\Trade.mqh>

input double      inpLot                 = 0.05;            // Fixed lot size per trade
input bool        useRiskPercent         = false;           // Use risk % to calculate lot size
input double      riskPercent            = 1.0;             // Risk % per trade if enabled

input int         sl_atr_mult            = 3;               // SL multiplier for ATR (initial SL distance)
input double      r2r_ratio              = 2.0;             // Risk-to-Reward ratio (TP = SL * r2r_ratio)
input ENUM_TIMEFRAMES volatilityTF       = PERIOD_M5;       // ATR timeframe
input int         atrPeriod              = 14;              // ATR period
input int         swingLookback          = 20;              // Bars lookback for swing detection (trade entries)
input ENUM_TIMEFRAMES swingTF            = PERIOD_M1;       // Swing detection timeframe for entries
input int         maPeriod               = 50;              // EMA period for trend filter
input ENUM_TIMEFRAMES maTimeframe        = PERIOD_M15;      // EMA timeframe
input int         tradeStartHour         = 7;               // Trading start hour (server)
input int         tradeEndHour           = 22;              // Trading end hour (server)
input int         maxPositions           = 1;               // Max simultaneous EA positions (0=no limit)
input uint        inpMagicNumber         = 123456;          // Magic number for EA positions (uint for compatibility)

// Trailing stop enable and mode
input bool        enableTrailingSL       = true;            // Enable trailing stop

enum ENUM_TrailingStopMode
{
   TrailingNone = 0,                  // No trailing stop
   TrailingMarketStructure = 1,      // Trailing by market structure swings
   TrailingATR = 2,                  // Trailing by ATR-based stop loss
   TrailingHybrid = 3                // Hybrid trailing: market structure + ATR
};
input ENUM_TrailingStopMode trailingStopMode = TrailingHybrid;  // Trailing stop mode selector

// New inputs for trailing settings (separate from entry/trade)
input int    ms_swingLookback   = 20;    // Swing trailing lookback bars (separate from trade entry)
input double atr_trail_mult     = 2.5;   // ATR trailing multiplier (can differ from SL multiplier)

// Trailing enhancements
input uint TrailUpdateIntervalSec    = 15;     // Minimum seconds between trailing stop updates per position
input double MinSLBufferATRMult      = 1.0;    // Minimum SL distance buffer in ATR multiples
input double TrailingProfitTriggerPerc = 0.5;  // Trailing activates once price moves this fraction of SL distance in profit

// New input for reverse entry logic
input bool enableReverseEntries = false;           // Reverse trade entry logic (Sell on swing high break, Buy on swing low break)

// New inputs for max risk and daily loss limits
input double maxRiskPerTradePercent = 1.0;          // Max risk per trade as % of account balance
input double maxDailyLossPercent = 2.0;             // Max daily loss as % of account balance

CTrade trade;

int ma_handle = INVALID_HANDLE;
int atr_handle = INVALID_HANDLE;
string swingHighLineName = "SwingHighLine";
string swingLowLineName  = "SwingLowLine";

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

// Globals for keeping daily loss state
double dailyLossMoney = 0.0;
datetime dailyLossResetTime = 0;

//+------------------------------------------------------------------+
void OnInit()
  {
   PrintFormat("AdvancedBoS_EA_MT5 v%s - Starting Initialization", __DATE__);
   PrintFormat("MagicNumber=%u, SwingTF=%d, ATR period=%d, SL ATR Mult=%d", 
               inpMagicNumber, swingTF, atrPeriod, sl_atr_mult);

   ma_handle = iMA(_Symbol, maTimeframe, maPeriod, 0, MODE_EMA, PRICE_CLOSE);
   if(ma_handle == INVALID_HANDLE)
     {
      Print("Failed to create EMA handle");
      return;
     }
   atr_handle = iATR(_Symbol, volatilityTF, atrPeriod);
   if(atr_handle == INVALID_HANDLE)
     {
      Print("Failed to create ATR handle");
      IndicatorRelease(ma_handle);
      return;
     }

   if(!ObjectCreate(0, swingHighLineName, OBJ_HLINE, 0, 0, 0))
     {
      Print("Failed to create swing high line");
      IndicatorRelease(ma_handle);
      IndicatorRelease(atr_handle);
      return;
     }
   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, 0, 0))
     {
      Print("Failed to create swing low line");
      ObjectDelete(0, swingHighLineName);
      IndicatorRelease(ma_handle);
      IndicatorRelease(atr_handle);
      return;
     }
   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);

   // Initialize daily loss reset time
   dailyLossResetTime = 0;
   dailyLossMoney = 0.0;
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectDelete(0, swingHighLineName);
   ObjectDelete(0, swingLowLineName);

   if(ma_handle != INVALID_HANDLE)
     {
      IndicatorRelease(ma_handle);
      ma_handle = INVALID_HANDLE;
     }
   if(atr_handle != INVALID_HANDLE)
     {
      IndicatorRelease(atr_handle);
      atr_handle = INVALID_HANDLE;
     }
  }

//+------------------------------------------------------------------+
// Resets daily loss tracking at start of each new day
void ResetDailyLossIfNewDay()
{
    datetime now = TimeCurrent();
    MqlDateTime tm {};
    TimeToStruct(now, tm);

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

    if(dailyLossResetTime != resetTimeToday)
    {
        dailyLossResetTime = resetTimeToday;
        dailyLossMoney = 0.0;
        Print("Daily loss tracker reset.");
    }
}

//+------------------------------------------------------------------+
// Update daily loss as sum of closed negative trades for current day & symbol
void UpdateDailyLoss()
{
    datetime dayStart = dailyLossResetTime;
    datetime dayEnd = dayStart + 86400; // Next day

    double lossToday = 0.0;

    int dealsTotal = HistoryDealsTotal();

    for(int i=dealsTotal-1; i>=0; i--)
    {
        ulong ticket = HistoryDealGetTicket(i);
        string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
        if(symbol != _Symbol)
            continue;

        ulong magic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
        if(magic != inpMagicNumber)
            continue;

        datetime dealTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
        if(dealTime < dayStart || dealTime >= dayEnd)
            continue;

        double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
        if(profit < 0)
            lossToday += (-profit);
    }

    dailyLossMoney = lossToday;
}

//+------------------------------------------------------------------+
// Returns true if daily loss limit exceeded (stop trading)
bool IsDailyLossExceeded()
{
    double balance = AccountInfoDouble(ACCOUNT_BALANCE);
    if(balance <= 0) return false;

    double lossPercent = 100.0 * dailyLossMoney / balance;

    if(lossPercent >= maxDailyLossPercent)
    {
        PrintFormat("Daily loss limit reached: %.2f%% (Max allowed %.2f%%). Halting new trades.", lossPercent, maxDailyLossPercent);
        return true;
    }
    return false;
}

//+------------------------------------------------------------------+
// Calculate lot size, capped by max risk per trade percent
double CalculateLotSize(double sl_distance_price)
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   if(balance <= 0 || sl_distance_price <= 0)
   {
      Print("Invalid inputs for lot size calculation");
      return inpLot;
   }

   double maxRiskMoney = balance * (maxRiskPerTradePercent / 100.0);

   double lot = 0.0;

   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);
      lot = inpLot;
      if(lot < min_lot) lot = min_lot;
      if(lot > max_lot) lot = max_lot;
      lot = MathFloor(lot / lot_step) * lot_step;
   }
   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)
      {
         Print("Invalid symbol trade parameters for lot calc.");
         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 lotMaxRisk = maxRiskMoney / (sl_distance_price * point_value * contract_size);

      lot = MathMin(calculatedLot, lotMaxRisk);

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

      lot = MathFloor(lot / 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) == 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;
}
//+------------------------------------------------------------------+
// Market Structure trailing stop update function
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; // No valid TP set, skip trailing

    if(!CanUpdateTrailing(position_ticket))
        return; // throttle updates

    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);
        }
    }
}
//+------------------------------------------------------------------+
// ATR Trailing stop update function
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; // throttle updates

    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)
    {
        PrintFormat("ATR data copy failed in trailing ATR for position %I64d", position_ticket);
        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);
        }
    }
}
//+------------------------------------------------------------------+
// Hybrid trailing stop update combining Market Structure and ATR stops
void UpdateTrailingStopHybrid(ulong position_ticket)
{
   if(!PositionSelectByTicket(position_ticket))
      return;

   if(!CanUpdateTrailing(position_ticket))
      return; // throttle trailing updates

   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)
   {
      PrintFormat("ATR data copy failed in trailing update for position %I64d", position_ticket);
      return;
   }
   double atr = atr_buf[0];
   if(atr <= 0)
      return;

   double min_sl_distance = atr * MinSLBufferATRMult;

   double initial_sl_dist = (pos_type == POSITION_TYPE_BUY) ? (entry_price - sl) : (sl - entry_price);
   if(initial_sl_dist <= 0) initial_sl_dist = atr * sl_atr_mult; // fallback

   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; // not enough profit to start trailing

   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; // no valid new SL

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

void OnTick()
{
    ResetDailyLossIfNewDay();
    UpdateDailyLoss();

    if(IsDailyLossExceeded())
        return;

    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) == inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
             {
                if(trailingStopMode == TrailingMarketStructure)
                   UpdateTrailingStopByMarketStructure(ticket);
                else if(trailingStopMode == TrailingATR)
                   UpdateTrailingStopByATR(ticket);
                else if(trailingStopMode == TrailingHybrid)
                   UpdateTrailingStopHybrid(ticket);
             }
          }
       }
    }

    int openPosCount = 0;
    int total = PositionsTotal();
    for(int i=0; i<total; i++)
    {
        ulong ticket = PositionGetTicket(i);
        if(PositionSelectByTicket(ticket))
        {
            if(PositionGetInteger(POSITION_MAGIC) == inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
            {
                openPosCount++;
            }
        }
    }
    if(maxPositions > 0 && openPosCount >= maxPositions)
        return;

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

    double ma_buf[];
    ArrayResize(ma_buf, 1);
    if(CopyBuffer(ma_handle, 0, 1, 1, ma_buf) <= 0)
    {
        PrintFormat("EMA data copy failed, error %d", GetLastError());
        return;
    }

    double atr_buf[];
    ArrayResize(atr_buf, 1);
    if(CopyBuffer(atr_handle, 0, 1, 1, atr_buf) <= 0)
    {
        PrintFormat("ATR data copy failed, error %d", GetLastError());
        return;
    }

    double ma = ma_buf[0];
    double atr = atr_buf[0];
    if(atr <= 0)
    {
        atr = 10.0 * _Point;
        Print("ATR value invalid or zero, falling back to 10 points");
    }

    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)
    {
        Print("Error getting swing high/low bars");
        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;

    double price_htf = iClose(_Symbol, maTimeframe, 1);
    if(price_htf <= 0 || ma <= 0)
        return;

    bool is_uptrend = (price_htf > ma);
    bool is_downtrend = (price_htf < ma);
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

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

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

    if(!enableReverseEntries)
    {
        if(ask > lastSwingHigh && is_uptrend)
        {
            if(!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);

                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);
            }
        }
        else if(bid < lastSwingLow && is_downtrend)
        {
            if(!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);

                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);
            }
        }
    }
    else
    {
        if(ask > lastSwingHigh && is_uptrend)
        {
            if(!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);

                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);
            }
        }
        else if(bid < lastSwingLow && is_downtrend)
        {
            if(!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);

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