//+------------------------------------------------------------------+
//| Advanced_BoS_EA_MT5.mq5                                           |
//| Author: LucienWP                                                  |
//| Version: 1.1                                                     |
//| Published: 2025-05-XX                                            |
//| Description: Break Of Structure EA                                |
//| with dynamic lot sizing, dynamic trailing stop based on market   |
//| structure, and configurable enable/disable trailing stop option  |
//+------------------------------------------------------------------+
#property copyright "LucienWP"
#property version   "1.1"
#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
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
input ENUM_TIMEFRAMES swingTF            = PERIOD_M1;       // Swing detection timeframe
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)

input bool        enableTrailingSL       = true;            // Enable dynamic trailing stop based on market structure

CTrade trade;

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

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

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

double CalculateLotSize(double sl_distance_price)
  {
   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 fixedLot = inpLot;
      if(fixedLot < min_lot) fixedLot = min_lot;
      if(fixedLot > max_lot) fixedLot = max_lot;
      fixedLot = MathFloor(fixedLot / lot_step) * lot_step;
      return fixedLot;
     }

   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   if(balance <= 0)
     {
      Print("Invalid account balance for lot size calculation");
      return inpLot;
     }

   if(sl_distance_price <= 0)
     {
      Print("Invalid SL distance for lot size calculation");
      return inpLot;
     }

   double riskMoney = balance * (riskPercent / 100.0);
   if(riskMoney <= 0)
     {
      Print("Invalid risk money calculation");
      return inpLot;
     }

   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 tick/contract size for lot calculation");
      return inpLot;
     }

   double point_value = tick_value / tick_size;
   if(point_value <=0)
     {
      Print("Invalid point value for lot calculation");
      return inpLot;
     }

   double lot = riskMoney / (sl_distance_price * point_value * contract_size);
   if(lot <= 0)
     {
      Print("Lot calculation gave non-positive result");
      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);

   lot = MathFloor(lot / lot_step) * lot_step;
   lot = MathMax(lot, min_lot);
   lot = MathMin(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;
}

// Dynamic Market Structure Trailing Stop with half-price trigger
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

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

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

            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 OnTick()
{
    if(enableTrailingSL)
    {
       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)
             {
                UpdateTrailingStopByMarketStructure(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)
    {
        Print("Invalid bid or ask price");
        return;
    }

    double price_htf = iClose(_Symbol, maTimeframe, 1);
    if(price_htf <= 0 || ma <= 0)
    {
        Print("Invalid price or MA value");
        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))
    {
        PrintFormat("Calculated lot %.2f is less than minimum allowed lot", lot);
        return;
    }

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

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