//+------------------------------------------------------------------+
//| Advanced_BoS_EA_MT5.mq5                                           |
//| Author: LucienWP                                                 |
//| Version: 1.0                                                    |
//| Published: 2025-05-04                                          |
//| Description: Break Of Structure EA with dynamic lot sizing,    |
//| drawdown protection, swing visualization and position closing  |
//+------------------------------------------------------------------+
#property copyright "LucienWP"
#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 % to calculate lot size
input double      riskPercent     = 1.0;             // Risk % per trade if enabled
input double      maxDrawdownPct  = 5.0;             // Max drawdown % before closing all positions (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;     // 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 ulong       inpMagicNumber = 123456;            // Magic number for EA positions

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

int OnInit()
  {
   Print("AdvancedBoS_EA_MT5 v", __DATE__, " - Starting Initialization");
   PrintFormat("MagicNumber=%d, 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 INIT_FAILED;
     }
   atr_handle = iATR(_Symbol, volatilityTF, atrPeriod);
   if(atr_handle == INVALID_HANDLE)
     {
      Print("Failed to create 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_price)
  {
   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("Invalid tick/contract size for lot calc");
      return inpLot;
     }
   double point_value = tick_value / tick_size;
   double lot = riskMoney / (sl_distance_price * 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--)
     {
      if(PositionSelectByIndex(i))
        {
         if(PositionGetInteger(POSITION_MAGIC) == inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
            totalUnrealized += PositionGetDouble(POSITION_PROFIT);
        }
     }
   if(totalUnrealized < 0)
     {
      double drawdownPercent = MathAbs(totalUnrealized) / balance * 100.0;
      if(drawdownPercent >= maxDrawdownPercent)
        {
         PrintFormat("Drawdown %.2f%% exceeds max %.2f%%, closing all EA positions", drawdownPercent, maxDrawdownPercent);
         for(int i=PositionsTotal()-1; i>=0; i--)
           {
            if(PositionSelectByIndex(i))
              {
               ulong ticket = PositionGetTicket(i);
               if(PositionGetInteger(POSITION_MAGIC) == inpMagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
                 {
                  MqlTradeRequest close_req = {0};
                  MqlTradeResult close_res = {0};
                  ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
                  double volume = PositionGetDouble(POSITION_VOLUME);
                  close_req.action = TRADE_ACTION_DEAL;
                  close_req.position = ticket;
                  close_req.volume = 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 = inpMagicNumber;
                  if(!OrderSend(close_req, close_res))
                     PrintFormat("Failed to close pos %I64d, error %d", ticket, GetLastError());
                  else if(close_res.retcode != TRADE_RETCODE_DONE)
                     PrintFormat("Close pos %I64d failed retcode %d", ticket, close_res.retcode);
                  else
                     PrintFormat("Position %I64d closed due to drawdown protection", ticket);
                 }
              }
           }
        }
     }
  }

void OnTick()
  {
   CloseAllPositionsIfDrawdown(maxDrawdownPct);

   int openPosCount = 0;
   for(int i=0; i<PositionsTotal(); i++)
     {
      if(PositionSelectByIndex(i))
        {
         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[1], atr_buf[1];
   if(CopyBuffer(ma_handle, 0, 1, 1, ma_buf) <= 0)
     {
      Print("EMA data copy failed, err ", GetLastError());
      return;
     }
   if(CopyBuffer(atr_handle, 0, 1, 1, atr_buf) <= 0)
     {
      Print("ATR data copy failed, err ", GetLastError());
      return;
     }
   double ma = ma_buf[0], atr = atr_buf[0];
   if(atr <= 0) atr = 10.0*_Point;

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

   double lastSwingHigh = iHigh(_Symbol, swingTF, highestBar);
   double lastSwingLow  = iLow(_Symbol, swingTF, lowestBar);

   ObjectSetDouble(0, swingHighLineName, OBJPROP_PRICE, lastSwingHigh);
   ObjectSetInteger(0, swingHighLineName, OBJPROP_COLOR, clrGreen);
   ObjectSetDouble(0, swingLowLineName, OBJPROP_PRICE, lastSwingLow);
   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;
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double lot = CalculateLotSize(sl_dist);

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

   if(ask > lastSwingHigh && 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))
         Print("Buy failed, error ", GetLastError());
      else if(result.retcode != TRADE_RETCODE_DONE)
         PrintFormat("Buy 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)
     {
      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))
         Print("Sell failed, error ", GetLastError());
      else if(result.retcode != TRADE_RETCODE_DONE)
         PrintFormat("Sell rejected, retcode %d", result.retcode);
      else
         PrintFormat("Sell opened @ %.5f lot %.2f SL %.5f TP %.5f", bid, lot, request.sl, request.tp);
     }
  }