//+------------------------------------------------------------------+
//|                                       MultiLookbackLinesEA.mq5   |
//|         Auto trading on sweep + close above/below HTF line       |
//|            ATR + swing-based SL, % balance risk-based sizing     |
//|           Added trailing stop feature, improved ATR and swings   |
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>
CTrade trade;

//--- Inputs
input ENUM_TIMEFRAMES InpTimeframe         = PERIOD_H1;    // Lookback timeframe
input int             InpLookbackCandles   = 10;           // Base candle count
input double          InpMultiplier        = 7.0;          // Max line multiplier
input bool            IncludeCurrentCandle = false;       // Include current candle?
input double          MinimalDistancePoints = 20;         // Min line distance (points)
input color           ColorHighLines       = clrRed;
input color           ColorLowLines        = clrBlue;

//--- Trading Logic
input int             SelectedLineMultiplier = 2;          // Line multiplier for entries
input double          RiskPercent = 1.0;                   // Risk per trade (%)
input double          ATR_Multiplier_SL = 1.0;             // SL = swing ± ATR
input int             ATR_Period = 14;                      // ATR period
input double          RRR = 2.0;                            // TP = RRR × SL

//--- ATR offset for GetATR function
input int             ATR_BufferOffset = 1;                 // ATR data buffer offset (0 = latest bar, 1 = previous bar, ...)

//--- Trailing stop inputs
input bool            UseTrailingStop = false;              // Enable trailing stop
input double          ATR_Multiplier_TP = 1.0;              // Trailing stop distance = ATR * multiplier
input int             TrailingCheckDelayBars = 0;           // Bars delay before starting trailing (0=no delay)

//--- Constants
#define PREFIX_HIGH "MultiHL_High_"
#define PREFIX_LOW  "MultiHL_Low_"

//--- Globals
datetime lastBarTime = 0;
int atrHandle = INVALID_HANDLE;

//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpLookbackCandles < 1)
     {
      Print("Error: InpLookbackCandles must be >= 1");
      return INIT_FAILED;
     }

   atrHandle = iATR(_Symbol, InpTimeframe, ATR_Period);
   if(atrHandle == INVALID_HANDLE)
     {
      Print("Failed to create ATR indicator handle");
      return INIT_FAILED;
     }

   ClearLines();
   UpdateLines();
   Print("EA Initialized. Timeframe: ", EnumToString(InpTimeframe));

   if(UseTrailingStop)
      EventSetMillisecondTimer(500);  // Timer event every 0.5 seconds for trailing stops

   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(atrHandle != INVALID_HANDLE)
     IndicatorRelease(atrHandle);

   ClearLines();
   EventKillTimer();
   Print("EA Deinitialized");
  }
//+------------------------------------------------------------------+
void OnTimer()
  {
   // Called by timer event for trailing stop management
   if(!UseTrailingStop)
      return;

   ManageTrailingStops();
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime currentBarTime = iTime(_Symbol, InpTimeframe, 0);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

   UpdateLines();

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

   double close1 = iClose(_Symbol, InpTimeframe, 1);
   if(close1 == 0)
     {
      Print("Close price not available, skipping tick.");
      return;
     }

   string lowLineName = PREFIX_LOW + IntegerToString(SelectedLineMultiplier);
   string highLineName = PREFIX_HIGH + IntegerToString(SelectedLineMultiplier);

   // BUY Entry - on close above low line
   if(ObjectFind(0, lowLineName) != -1)
     {
      double lowToWatch = ObjectGetDouble(0, lowLineName, OBJPROP_PRICE);

      PrintFormat("BUY Condition: close1=%.5f lowToWatch=%.5f HasOpenBuy=%s", close1, lowToWatch, HasOpenBuy()?"YES":"NO");

      if(close1 > lowToWatch && !HasOpenBuy())
        {
         double sl = GetSwingLowSL();
         // Ensure SL is below entry price by min distance
         sl = AdjustSLForMinimumDistance(ask, sl);
         double tp = ask + RRR * (ask - sl);

         double lotSize = CalculateLotSize(ask, sl);

         PrintFormat("BUYing: SL=%.5f TP=%.5f Lot=%.4f", sl, tp, lotSize);
         PrintFormat("Entry: ATR=%.5f, SL distance=%.1f points, MinimalDistancePoints=%.0f, LotSize=%.4f", 
                     GetATR(), MathAbs(ask - sl)/SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE), MinimalDistancePoints, lotSize);

         if(lotSize >= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
           {
            if(trade.Buy(lotSize, _Symbol, ask, sl, tp, "Buy on close above low line"))
               Print("BUY opened.");
            else
               Print("BUY order failed error: ", GetLastError());
           }
         else
            Print("BUY lot size too small.");
        }
     }

   // SELL Entry - on close below high line
   if(ObjectFind(0, highLineName) != -1)
     {
      double highToWatch = ObjectGetDouble(0, highLineName, OBJPROP_PRICE);

      PrintFormat("SELL Condition: close1=%.5f highToWatch=%.5f HasOpenSell=%s", close1, highToWatch, HasOpenSell() ? "YES":"NO");

      if(close1 < highToWatch && !HasOpenSell())
        {
         double sl = GetSwingHighSL();
         // Ensure SL is above entry price by min distance
         sl = AdjustSLForMinimumDistance(bid, sl);
         double tp = bid - RRR * (sl - bid);

         double lotSize = CalculateLotSize(bid, sl);

         PrintFormat("SELLing: SL=%.5f TP=%.5f Lot=%.4f", sl, tp, lotSize);
         PrintFormat("Entry: ATR=%.5f, SL distance=%.1f points, MinimalDistancePoints=%.0f, LotSize=%.4f", 
                     GetATR(), MathAbs(bid - sl)/SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE), MinimalDistancePoints, lotSize);

         if(lotSize >= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
           {
            if(trade.Sell(lotSize, _Symbol, bid, sl, tp, "Sell on close below high line"))
               Print("SELL opened.");
            else
               Print("SELL order failed error: ", GetLastError());
           }
         else
            Print("SELL lot size too small.");
        }
     }
  }
//+------------------------------------------------------------------+
void UpdateLines()
  {
   int maxMult = (int)MathFloor(MathMax(InpMultiplier, 1.0));
   int startPos = IncludeCurrentCandle ? 0 : 1;

   double filteredHighsPrices[];
   double filteredLowsPrices[];

   ClearLines();

   for(int m=1; m<=maxMult; m++)
     {
      int barsToLookBack = InpLookbackCandles * m;
      MqlRates rates[];
      int copied = CopyRates(_Symbol, InpTimeframe, startPos, barsToLookBack, rates);
      if(copied <= 0)
         continue;

      double highest = -DBL_MAX;
      double lowest  =  DBL_MAX;
      for(int i=0; i<copied; i++)
        {
         highest = MathMax(highest, rates[i].high);
         lowest  = MathMin(lowest,  rates[i].low);
        }

      string highLineName = PREFIX_HIGH + IntegerToString(m);
      bool highTooClose = false;
      for(int i=0; i<ArraySize(filteredHighsPrices); i++)
        {
         if(MathAbs(highest - filteredHighsPrices[i])/_Point < MinimalDistancePoints)
           {
            if(highest > filteredHighsPrices[i])
              {
               filteredHighsPrices[i] = highest;
               DrawOrUpdateLine(highLineName, highest, ColorHighLines);
              }
            highTooClose = true;
            break;
           }
        }
      if(!highTooClose)
        {
         ArrayResize(filteredHighsPrices, ArraySize(filteredHighsPrices)+1);
         filteredHighsPrices[ArraySize(filteredHighsPrices)-1] = highest;
         DrawOrUpdateLine(highLineName, highest, ColorHighLines);
        }

      string lowLineName = PREFIX_LOW + IntegerToString(m);
      bool lowTooClose = false;
      for(int i=0; i<ArraySize(filteredLowsPrices); i++)
        {
         if(MathAbs(lowest - filteredLowsPrices[i])/_Point < MinimalDistancePoints)
           {
            if(lowest < filteredLowsPrices[i])
              {
               filteredLowsPrices[i] = lowest;
               DrawOrUpdateLine(lowLineName, lowest, ColorLowLines);
              }
            lowTooClose = true;
            break;
           }
        }
      if(!lowTooClose)
        {
         ArrayResize(filteredLowsPrices, ArraySize(filteredLowsPrices)+1);
         filteredLowsPrices[ArraySize(filteredLowsPrices)-1] = lowest;
         DrawOrUpdateLine(lowLineName, lowest, ColorLowLines);
        }
     }
  }
//+------------------------------------------------------------------+
void DrawOrUpdateLine(string name, double price, color clr)
  {
   if(ObjectFind(0, name) == -1)
     {
      if(!ObjectCreate(0, name, OBJ_HLINE, 0, 0, price))
        {
         Print("Failed to create line: ", name);
         return;
        }
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
     }
   else
      ObjectSetDouble(0, name, OBJPROP_PRICE, price);
  }
//+------------------------------------------------------------------+
void ClearLines()
  {
   int total = ObjectsTotal(0);
   for(int i=total-1; i>=0; i--)
     {
      string name = ObjectName(0, i);
      if(StringFind(name, PREFIX_HIGH) == 0 || StringFind(name, PREFIX_LOW) == 0)
         ObjectDelete(0, name);
     }
  }
//+------------------------------------------------------------------+
double GetATR()
  {
   if(atrHandle == INVALID_HANDLE)
     {
      Print("ATR handle invalid");
      return 0.0;
     }

   double atrBuffer[];
   if(CopyBuffer(atrHandle, 0, ATR_BufferOffset, 1, atrBuffer) <= 0)
     {
      Print("Failed to copy ATR data at offset ", ATR_BufferOffset);
      return 0.0;
     }
   return atrBuffer[0];
  }
//+------------------------------------------------------------------+
double GetLastSwingLow(int bars=10)
  {
   int totalBars = Bars(_Symbol, InpTimeframe);
   if(totalBars < bars + 2)
      bars = totalBars - 2; // avoid out-of-range

   for(int i=1; i < bars; i++)
     {
      double low_current = iLow(_Symbol, InpTimeframe, i);
      double low_prev    = iLow(_Symbol, InpTimeframe, i - 1);
      double low_next    = iLow(_Symbol, InpTimeframe, i + 1);

      if(low_current < low_prev && low_current < low_next)
         return low_current;
     }
   return iLow(_Symbol, InpTimeframe, 1);
  }
//+------------------------------------------------------------------+
double GetLastSwingHigh(int bars=10)
  {
   int totalBars = Bars(_Symbol, InpTimeframe);
   if(totalBars < bars + 2)
      bars = totalBars - 2;

   for(int i=1; i < bars; i++)
     {
      double high_current = iHigh(_Symbol, InpTimeframe, i);
      double high_prev    = iHigh(_Symbol, InpTimeframe, i - 1);
      double high_next    = iHigh(_Symbol, InpTimeframe, i + 1);

      if(high_current > high_prev && high_current > high_next)
         return high_current;
     }
   return iHigh(_Symbol, InpTimeframe, 1);
  }
//+------------------------------------------------------------------+
double GetSwingLowSL()
  {
   double atr = GetATR();
   double swingLow = GetLastSwingLow();
   return swingLow - ATR_Multiplier_SL * atr;
  }
//+------------------------------------------------------------------+
double GetSwingHighSL()
  {
   double atr = GetATR();
   double swingHigh = GetLastSwingHigh();
   return swingHigh + ATR_Multiplier_SL * atr;
  }
//+------------------------------------------------------------------+
double AdjustSLForMinimumDistance(double entryPrice, double slPrice)
  {
   double minDistancePoints = MinimalDistancePoints;
   double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double minDistancePrice = minDistancePoints * tick_size;

   double slDiff = MathAbs(entryPrice - slPrice);
   if(slDiff < minDistancePrice)
     {
      if(slPrice > entryPrice)
         slPrice = entryPrice + minDistancePrice; // For Sell SL above entry
      else
         slPrice = entryPrice - minDistancePrice; // For Buy SL below entry
     }
   return slPrice;
  }
//+------------------------------------------------------------------+
bool HasOpenBuy()
  {
   int total = PositionsTotal();
   for(int i=0; i < total; i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionSelectByTicket(ticket))
        {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            return true;
        }
     }
   return false;
  }
//+------------------------------------------------------------------+
bool HasOpenSell()
  {
   int total = PositionsTotal();
   for(int i=0; i < total; i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionSelectByTicket(ticket))
        {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            return true;
        }
     }
   return false;
  }
//+------------------------------------------------------------------+
double CalculateLotSize(double entryPrice, double slPrice)
  {
   double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   double risk_money = AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0;

   double stop_loss_price_diff = MathAbs(entryPrice - slPrice);
   int stop_loss_points = (int)(stop_loss_price_diff / tick_size);
   if(stop_loss_points < 1) stop_loss_points = 1;

   double risk_per_lot = stop_loss_points * tick_value;

   if(risk_per_lot <= 0)
     {
      Print("CalculateLotSize: Invalid risk per lot <= 0. Check tick_value and stop loss.");
      return 0.0;
     }

   double lots = risk_money / risk_per_lot;

   lots = MathFloor(lots / lot_step) * lot_step;
   lots = MathMax(min_lot, MathMin(max_lot, lots));
   lots = NormalizeDouble(lots, 2);

   PrintFormat("CalculateLotSize: risk_money=%.2f, stop_loss_points=%d, tick_value=%.5f, risk_per_lot=%.2f, lots=%.4f",
               risk_money, stop_loss_points, tick_value, risk_per_lot, lots);

   return lots;
  }
//+------------------------------------------------------------------+
void ManageTrailingStops()
  {
   int totalPositions = PositionsTotal();
   double atr = GetATR();
   if(atr <= 0)
      return;

   double trailDistancePrice = atr * ATR_Multiplier_TP;
   double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);

   for(int i=totalPositions-1; i>=0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      if(!PositionSelectByTicket(ticket))
         continue;

      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;

      datetime posOpenTime = (datetime)PositionGetInteger(POSITION_TIME);

      int posOpenBar = iBarShift(_Symbol, _Period, posOpenTime, true);
      int currentBar = 0;
      if(TrailingCheckDelayBars > 0)
        {
         if(currentBar - posOpenBar < TrailingCheckDelayBars)
            continue;
        }

      ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      double currentSL = PositionGetDouble(POSITION_SL);
      double currentPrice = 0.0;

      if(posType == POSITION_TYPE_BUY)
         currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      else if(posType == POSITION_TYPE_SELL)
         currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      else
         continue;

      double newSL = 0.0;
      bool changeSL = false;

      PrintFormat("TrailingStop Check: Pos=%I64u Type=%d Open=%.5f SL=%.5f CurrentPrice=%.5f ATR=%.5f TrailDist=%.5f",
                  ticket, posType, openPrice, currentSL, currentPrice, atr, trailDistancePrice);

      if(posType == POSITION_TYPE_BUY)
        {
         double possibleSL = currentPrice - trailDistancePrice;
         if(possibleSL > currentSL && possibleSL > openPrice)
           {
            newSL = possibleSL;
            changeSL = true;
           }
        }
      else if(posType == POSITION_TYPE_SELL)
        {
         double possibleSL = currentPrice + trailDistancePrice;
         if(possibleSL < currentSL && possibleSL < openPrice)
           {
            newSL = possibleSL;
            changeSL = true;
           }
        }

      if(changeSL)
        {
         if(MathAbs(newSL - currentSL) < tick_size)
            continue;

         PrintFormat("Trailing Stop: Position[%I64u] Type=%d OldSL=%.5f NewSL=%.5f", ticket, posType, currentSL, newSL);

         if(!trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP)))
           {
            PrintFormat("Trailing Stop modify failed for ticket=%I64u error=%d", ticket, GetLastError());
           }
         else
           {
            PrintFormat("Trailing Stop applied for ticket=%I64u newSL=%.5f", ticket, newSL);
           }
        }
     }
  }
//+------------------------------------------------------------------+