//+------------------------------------------------------------------+
//|                                           AdvancedBoS_EA_MT5.mq5  |
//|  BoS EA with dynamic lot sizing, drawdown close, swing visualization  |
//|  and correct position closing for MT5                            |
//+------------------------------------------------------------------+
#property copyright "LucienWebP"
#property version   "1.0"
#property strict
#property script_show_inputs

input double      inpLot          = 0.05;          // Fixed lot size per trade
input bool        useRiskPercent  = false;         // Use risk %, else fixed lot
input double      riskPercent     = 1.0;           // Risk % per trade if risk mode enabled
input double      maxDrawdownPct  = 5.0;           // Max drawdown % of balance before all close (0 disables)
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;   // Timeframe for ATR calculation
input int         swingLookback  = 20;             // Bars to look back for swing detection on M1
input int         maPeriod       = 50;             // EMA period for trend filter M15
input ENUM_TIMEFRAMES maTimeframe = PERIOD_M15;    // EMA timeframe
input int         tradeStartHour = 7;              // Trade start hour (server time)
input int         tradeEndHour   = 22;             // Trade end hour (server time)
input int         maxPositions   = 1;              // Max simultaneous positions (0=no limit)

int ma_handle = INVALID_HANDLE;
int atr_handle = INVALID_HANDLE;

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

ulong magic_number = 123456;  // You can make this input if you want

//+------------------------------------------------------------------+
int OnInit()
  {
   ma_handle = iMA(_Symbol, maTimeframe, maPeriod, 0, MODE_EMA, PRICE_CLOSE);
   atr_handle = iATR(_Symbol, volatilityTF, 14);
   if(ma_handle == INVALID_HANDLE)
     {
      Print("Failed to get EMA handle");
      return INIT_FAILED;
     }
   if(atr_handle == INVALID_HANDLE)
     {
      Print("Failed to get ATR handle");
      return INIT_FAILED;
     }

   if(!ObjectCreate(0, swingHighLineName, OBJ_HLINE, 0, 0, 0))
      Print("Failed to create swing high line");
   if(!ObjectCreate(0, swingLowLineName, OBJ_HLINE, 0, 0, 0))
      Print("Failed to create swing low line");

   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectDelete(0, swingHighLineName);
   ObjectDelete(0, swingLowLineName);

   if(ma_handle != INVALID_HANDLE)
      IndicatorRelease(ma_handle);
   if(atr_handle != INVALID_HANDLE)
      IndicatorRelease(atr_handle);
  }
//+------------------------------------------------------------------+
double CalculateLotSize(double sl_distance_points)
  {
   if(!useRiskPercent)
      return(inpLot);

   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskMoney = balance * (riskPercent / 100.0);

   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("Tick value or size zero - cannot calculate lot size");
      return(inpLot);
     }

   double point_value = tick_value / tick_size;
   double lot = riskMoney / (sl_distance_points * point_value * contract_size);

   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;
  }
//+------------------------------------------------------------------+
void CloseAllPositionsIfDrawdown(double maxDrawdownPercent)
  {
   if(maxDrawdownPercent <= 0)
      return;

   double totalUnrealized = 0.0;
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
        {
         if(PositionGetInteger(POSITION_MAGIC) == magic_number && PositionGetString(POSITION_SYMBOL) == _Symbol)
            totalUnrealized += PositionGetDouble(POSITION_PROFIT);
        }
     }
   if(totalUnrealized < 0)
     {
      double drawdownPercent = MathAbs(totalUnrealized)/balance*100.0;
      if(drawdownPercent >= maxDrawdownPercent)
        {
         PrintFormat("Drawdown %.2f%% > max %.2f%% - closing all EA positions", drawdownPercent, maxDrawdownPercent);
         for(int i=PositionsTotal()-1; i>=0; i--)
           {
            ulong ticket = PositionGetTicket(i);
            if(PositionSelectByTicket(ticket))
              {
               if(PositionGetInteger(POSITION_MAGIC) == magic_number && PositionGetString(POSITION_SYMBOL) == _Symbol)
                 {
                  MqlTradeRequest close_req;
                  MqlTradeResult close_res;
                  ZeroMemory(close_req);
                  ZeroMemory(close_res);
                  ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
                  close_req.action = TRADE_ACTION_DEAL;
                  close_req.position = ticket;
                  close_req.volume = PositionGetDouble(POSITION_VOLUME);
                  close_req.price = (pos_type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
                  close_req.type = (pos_type == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
                  close_req.deviation = 10;
                  close_req.magic = magic_number;

                  if(!OrderSend(close_req, close_res))
                     PrintFormat("Closing position %I64d failed, error %d", ticket, GetLastError());
                  else
                     PrintFormat("Position %I64d closed due to drawdown protection", ticket);
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   CloseAllPositionsIfDrawdown(maxDrawdownPct);

   int openPosCount = 0;
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
        {
         if(PositionGetInteger(POSITION_MAGIC) == magic_number && 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[], atr_buf[];
   if(CopyBuffer(ma_handle, 0, 1, 1, ma_buf) <= 0)
     {
      Print("Failed copying EMA buffer ", GetLastError());
      return;
     }
   if(CopyBuffer(atr_handle, 0, 1, 1, atr_buf) <= 0)
     {
      Print("Failed copying ATR buffer ", GetLastError());
      return;
     }

   double ma = ma_buf[0];
   double atr = atr_buf[0];
   if(atr <= 0.0)
      atr = 10.0 * _Point;

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

   double last_swing_high = iHigh(_Symbol, PERIOD_M1, iHighest(_Symbol, PERIOD_M1, MODE_HIGH, swingLookback, 1));
   double last_swing_low  = iLow(_Symbol, PERIOD_M1, iLowest(_Symbol, PERIOD_M1, MODE_LOW, swingLookback, 1));

   ObjectSetDouble(0, swingHighLineName, OBJPROP_PRICE, last_swing_high);
   ObjectSetInteger(0, swingHighLineName, OBJPROP_COLOR, clrGreen);
   ObjectSetDouble(0, swingLowLineName, OBJPROP_PRICE, last_swing_low);
   ObjectSetInteger(0, swingLowLineName, OBJPROP_COLOR, clrRed);

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

   double price_htf = iClose(_Symbol, maTimeframe, 1);
   bool is_uptrend = price_htf > ma;
   bool is_downtrend = price_htf < ma;

   double lot = CalculateLotSize(sl_dist);

   double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double lot_min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double lot_max = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

   // Normalize lot based on broker rules
   lot = MathFloor(lot / lot_step) * lot_step;
   lot = MathMax(lot, lot_min);
   lot = MathMin(lot, lot_max);

   if(lot < lot_min)
     {
      PrintFormat("Calculated lot size %.2f less than minimum allowed %.2f, skipping trade", lot, lot_min);
      return;
     }

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

   MqlTradeRequest request;
   MqlTradeResult result;
   ZeroMemory(request);
   ZeroMemory(result);

   request.symbol = _Symbol;
   request.volume = lot;
   request.deviation = 10;
   request.magic = magic_number;

   if(ask > last_swing_high && is_uptrend)
     {
      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 failed with error %d", GetLastError());
      else
         PrintFormat("Buy order opened @ %.5f lot %.2f SL %.5f TP %.5f", ask, lot, request.sl, request.tp);
     }
   else if(bid < last_swing_low && is_downtrend)
     {
      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 failed with error %d", GetLastError());
      else
         PrintFormat("Sell order opened @ %.5f lot %.2f SL %.5f TP %.5f", bid, lot, request.sl, request.tp);
     }
  }
//+------------------------------------------------------------------+