//+------------------------------------------------------------------+
//|                                       MultiLookbackLinesEA.mq5   |
//|         Auto trading on sweep + close above/below HTF line       |
//|            ATR + swing-based SL, % balance risk-based sizing     |
//+------------------------------------------------------------------+
#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

//--- 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));
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(atrHandle != INVALID_HANDLE)
     IndicatorRelease(atrHandle);

   ClearLines();
   Print("EA Deinitialized");
  }
//+------------------------------------------------------------------+
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);

   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();
         double tp = ask + RRR * (ask - sl);
         double lotSize = CalculateLotSize(ask, sl);

         PrintFormat("BUYing: SL=%.5f TP=%.5f Lot=%.4f", sl, tp, 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();
         double tp = bid - RRR * (sl - bid);
         double lotSize = CalculateLotSize(bid, sl);

         PrintFormat("SELLing: SL=%.5f TP=%.5f Lot=%.4f", sl, tp, 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, 1, 1, atrBuffer) <= 0)
     {
      Print("Failed to copy ATR data");
      return 0.0;
     }
   return atrBuffer[0];
  }
//+------------------------------------------------------------------+
double GetLastSwingLow(int bars=10)
  {
   int totalBars = Bars(_Symbol, InpTimeframe);
   for(int i=1; i<bars && i+1 < totalBars; i++)
     {
      double prev = iLow(_Symbol, InpTimeframe, i);
      if(prev < iLow(_Symbol, InpTimeframe, i-1) && prev < iLow(_Symbol, InpTimeframe, i+1))
         return prev;
     }
   return iLow(_Symbol, InpTimeframe, 1);
  }
//+------------------------------------------------------------------+
double GetLastSwingHigh(int bars=10)
  {
   int totalBars = Bars(_Symbol, InpTimeframe);
   for(int i=1; i<bars && i+1 < totalBars; i++)
    {
      double prev = iHigh(_Symbol, InpTimeframe, i);
      if(prev > iHigh(_Symbol, InpTimeframe, i-1) && prev > iHigh(_Symbol, InpTimeframe, i+1))
         return prev;
    }
   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;
  }
//+------------------------------------------------------------------+
bool HasOpenBuy()
  {
   int total = PositionsTotal();
   for(int i=0; i < total; i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
        {
         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)
        {
         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;
  }
//+------------------------------------------------------------------+