//+------------------------------------------------------------------+
//|                                      AutoTradingMultiLookback.mq5|
//+------------------------------------------------------------------+
#property copyright "Enhanced Trading System"
#property version   "2.01"
#property strict

#include <Trade\Trade.mqh>
CTrade trade;

//--- inputs
input ENUM_TIMEFRAMES InpTimeframe        = PERIOD_H1;    // Timeframe to look back on
input int            InpLookbackCandles   = 10;           // Base number of candles (>=1)
input double         InpMultiplier        = 7.0;          // Max multiplier (integer part used, >=1)
input bool           IncludeCurrentCandle = false;        // Include current forming candle in calculations

input double         MinimalDistancePoints = 20;          // Minimal distance between lines, in points

// Trading inputs
input double         RiskPercent          = 2.0;          // Risk percentage of balance per trade
input int            StopLossPips         = 50;           // Stop Loss in pips (0 = no SL)
input int            TakeProfitPips       = 100;          // Take Profit in pips (0 = no TP)
input int            MaxOpenTrades        = 3;            // Maximum open trades at once
input bool           EnableBuyTrades      = true;         // Enable buy trades (price cross low and close above)
input bool           EnableSellTrades     = true;         // Enable sell trades (price cross high and close below)
input int            MinMultiplier        = 1;            // Minimum multiplier to trade
input int            MaxMultiplier        = 7;            // Maximum multiplier to trade

// Optional color inputs
input color          ColorHighLines       = clrRed;
input color          ColorLowLines        = clrBlue;

#define PREFIX_HIGH "AutoTrade_High_"
#define PREFIX_LOW  "AutoTrade_Low_"
#define MAGIC_NUMBER 12345

//--- globals
datetime lastBarTime = 0;
double   multiplier  = 1.0;

// Arrays to store line levels and their states
struct LineData
{
   double price;
   int multiplier;
   bool crossedAbove;  // For high lines: price crossed above
   bool crossedBelow;  // For low lines: price crossed below
};

LineData highLines[];
LineData lowLines[];

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

   multiplier = InpMultiplier;
   if(multiplier < 1.0)
   {
      Print("Warning: InpMultiplier < 1, setting to 1");
      multiplier = 1.0;
   }

   if(RiskPercent <= 0 || RiskPercent > 10)
   {
      Print("Error: RiskPercent must be between 0.1 and 10");
      return(INIT_FAILED);
   }

   // Initialize trade object
   trade.SetExpertMagicNumber(MAGIC_NUMBER);
   
   ClearLines();
   UpdateLines();

   Print("AutoTradingMultiLookback EA initialized");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ClearLines();
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Get time of the latest completed bar on the input timeframe
   datetime currentBarTime = iTime(_Symbol, InpTimeframe, 0);
   if(currentBarTime != lastBarTime)
   {
      lastBarTime = currentBarTime;
      UpdateLines();
   }

   // Check for trading signals on every tick
   CheckTradingSignals();
}

//+------------------------------------------------------------------+
//| Core function to draw or update all lines                       |
//+------------------------------------------------------------------+
void UpdateLines()
{
   int maxMult = (int)MathFloor(multiplier);
   if(maxMult < 1) maxMult = 1;

   int startPos = IncludeCurrentCandle ? 0 : 1;

   // Clear previous lines and reset arrays
   ClearLines();
   ArrayResize(highLines, 0);
   ArrayResize(lowLines, 0);

   // Temporary arrays for filtering
   double filteredHighsPrices[];
   int    filteredHighsMultipliers[];
   double filteredLowsPrices[];
   int    filteredLowsMultipliers[];

   for(int m = 1; m <= maxMult; m++)
   {
      int barsToLookBack = InpLookbackCandles * m;

      MqlRates rates[];
      int copied = CopyRates(_Symbol, InpTimeframe, startPos, barsToLookBack, rates);

      if(copied <= 0)
      {
         PrintFormat("CopyRates failed for %d bars on timeframe %s", barsToLookBack, EnumToString(InpTimeframe));
         continue;
      }

      double highest = -DBL_MAX;
      double lowest  =  DBL_MAX;

      for(int i = 0; i < copied; i++)
      {
         if(rates[i].high > highest) highest = rates[i].high;
         if(rates[i].low < lowest) lowest = rates[i].low;
      }

      // Filter and add high lines
      ProcessHighLine(highest, m, filteredHighsPrices, filteredHighsMultipliers);
      
      // Filter and add low lines
      ProcessLowLine(lowest, m, filteredLowsPrices, filteredLowsMultipliers);
   }
}

//+------------------------------------------------------------------+
//| Process high line with filtering                                 |
//+------------------------------------------------------------------+
void ProcessHighLine(double highest, int mult, double &filteredHighsPrices[], int &filteredHighsMultipliers[])
{
   bool highCloseFound = false;
   int  highCloseIndex = -1;

   for(int k = 0; k < ArraySize(filteredHighsPrices); k++)
   {
      double distPoints = MathAbs(highest - filteredHighsPrices[k]) / _Point;
      if(distPoints < MinimalDistancePoints)
      {
         highCloseFound = true;
         highCloseIndex = k;
         break;
      }
   }

   string highLineName = PREFIX_HIGH + IntegerToString(mult);

   if(!highCloseFound)
   {
      // Add new high line
      ArrayResize(filteredHighsPrices, ArraySize(filteredHighsPrices) + 1);
      ArrayResize(filteredHighsMultipliers, ArraySize(filteredHighsMultipliers) + 1);
      filteredHighsPrices[ArraySize(filteredHighsPrices) - 1] = highest;
      filteredHighsMultipliers[ArraySize(filteredHighsMultipliers) - 1] = mult;

      // Add to trading array
      ArrayResize(highLines, ArraySize(highLines) + 1);
      highLines[ArraySize(highLines) - 1].price = highest;
      highLines[ArraySize(highLines) - 1].multiplier = mult;
      highLines[ArraySize(highLines) - 1].crossedAbove = false;

      DrawOrUpdateLine(highLineName, highest, ColorHighLines);
   }
   else if(highest > filteredHighsPrices[highCloseIndex])
   {
      // Replace with higher value
      filteredHighsPrices[highCloseIndex] = highest;
      int replacedMultiplier = filteredHighsMultipliers[highCloseIndex];
      string replacedLineName = PREFIX_HIGH + IntegerToString(replacedMultiplier);
      
      // Update in trading array
      for(int k = 0; k < ArraySize(highLines); k++)
      {
         if(highLines[k].multiplier == replacedMultiplier)
         {
            highLines[k].price = highest;
            highLines[k].crossedAbove = false;
            break;
         }
      }
      
      DrawOrUpdateLine(replacedLineName, highest, ColorHighLines);
   }
}

//+------------------------------------------------------------------+
//| Process low line with filtering                                  |
//+------------------------------------------------------------------+
void ProcessLowLine(double lowest, int mult, double &filteredLowsPrices[], int &filteredLowsMultipliers[])
{
   bool lowCloseFound = false;
   int  lowCloseIndex = -1;

   for(int k = 0; k < ArraySize(filteredLowsPrices); k++)
   {
      double distPoints = MathAbs(lowest - filteredLowsPrices[k]) / _Point;
      if(distPoints < MinimalDistancePoints)
      {
         lowCloseFound = true;
         lowCloseIndex = k;
         break;
      }
   }

   string lowLineName = PREFIX_LOW + IntegerToString(mult);

   if(!lowCloseFound)
   {
      // Add new low line
      ArrayResize(filteredLowsPrices, ArraySize(filteredLowsPrices) + 1);
      ArrayResize(filteredLowsMultipliers, ArraySize(filteredLowsMultipliers) + 1);
      filteredLowsPrices[ArraySize(filteredLowsPrices) - 1] = lowest;
      filteredLowsMultipliers[ArraySize(filteredLowsMultipliers) - 1] = mult;

      // Add to trading array
      ArrayResize(lowLines, ArraySize(lowLines) + 1);
      lowLines[ArraySize(lowLines) - 1].price = lowest;
      lowLines[ArraySize(lowLines) - 1].multiplier = mult;
      lowLines[ArraySize(lowLines) - 1].crossedBelow = false;

      DrawOrUpdateLine(lowLineName, lowest, ColorLowLines);
   }
   else if(lowest < filteredLowsPrices[lowCloseIndex])
   {
      // Replace with lower value
      filteredLowsPrices[lowCloseIndex] = lowest;
      int replacedMultiplier = filteredLowsMultipliers[lowCloseIndex];
      string replacedLineName = PREFIX_LOW + IntegerToString(replacedMultiplier);
      
      // Update in trading array
      for(int k = 0; k < ArraySize(lowLines); k++)
      {
         if(lowLines[k].multiplier == replacedMultiplier)
         {
            lowLines[k].price = lowest;
            lowLines[k].crossedBelow = false;
            break;
         }
      }
      
      DrawOrUpdateLine(replacedLineName, lowest, ColorLowLines);
   }
}

//+------------------------------------------------------------------+
//| Check for trading signals (Updated with candle close logic)     |
//+------------------------------------------------------------------+
void CheckTradingSignals()
{
   double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   // --- Get previous closed candle data on input timeframe
   MqlRates prevCandle[];
   if(CopyRates(_Symbol, InpTimeframe, 1, 1, prevCandle) <= 0)
   {
      Print("Failed to get previous candle rates");
      return;
   }
   double prevClose = prevCandle[0].close;

   // --- SELL signals ---
   if(EnableSellTrades)
   {
      for(int j = 0; j < ArraySize(highLines); j++)
      {
         if(highLines[j].multiplier < MinMultiplier || highLines[j].multiplier > MaxMultiplier)
            continue;

         double linePrice = highLines[j].price;

         // Tick-based crossing logic
         if(!highLines[j].crossedAbove && currentBid > linePrice)
         {
            highLines[j].crossedAbove = true;
            PrintFormat("Tick: Price crossed ABOVE high line multiplier %d at %.5f", highLines[j].multiplier, linePrice);
         }

         if(highLines[j].crossedAbove && currentBid < linePrice)
         {
            if(GetOpenTradesCount() < MaxOpenTrades)
            {
               OpenSellTrade(highLines[j].multiplier, linePrice);
            }
            highLines[j].crossedAbove = false;
         }

         // Candle close based logic:
         // If previously crossedAbove, and prev candle close BELOW the line, open sell
         if(highLines[j].crossedAbove && prevClose < linePrice)
         {
            if(GetOpenTradesCount() < MaxOpenTrades)
            {
               OpenSellTrade(highLines[j].multiplier, linePrice);
               highLines[j].crossedAbove = false;
            }
         }
      }
   }

   // --- BUY signals ---
   if(EnableBuyTrades)
   {
      for(int j = 0; j < ArraySize(lowLines); j++)
      {
         if(lowLines[j].multiplier < MinMultiplier || lowLines[j].multiplier > MaxMultiplier)
            continue;

         double linePrice = lowLines[j].price;

         // Tick-based crossing logic
         if(!lowLines[j].crossedBelow && currentAsk < linePrice)
         {
            lowLines[j].crossedBelow = true;
            PrintFormat("Tick: Price crossed BELOW low line multiplier %d at %.5f", lowLines[j].multiplier, linePrice);
         }

         if(lowLines[j].crossedBelow && currentAsk > linePrice)
         {
            if(GetOpenTradesCount() < MaxOpenTrades)
            {
               OpenBuyTrade(lowLines[j].multiplier, linePrice);
            }
            lowLines[j].crossedBelow = false;
         }

         // Candle close based logic:
         // If previously crossedBelow, and previous candle close ABOVE line, open buy
         if(lowLines[j].crossedBelow && prevClose > linePrice)
         {
            if(GetOpenTradesCount() < MaxOpenTrades)
            {
               OpenBuyTrade(lowLines[j].multiplier, linePrice);
               lowLines[j].crossedBelow = false;
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Open sell trade                                                  |
//+------------------------------------------------------------------+
void OpenSellTrade(int multiplier, double triggerPrice)
{
   double lotSize = CalculateLotSize();
   if(lotSize <= 0) return;

   double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl = 0, tp = 0;

   // Adjust pip multiplier for 5-digit or 3-digit brokers
   double pipMultiplier = (_Digits == 5 || _Digits == 3) ? 10.0 : 1.0;

   // Calculate Stop Loss
   if(StopLossPips > 0)
   {
      sl = price + (StopLossPips * _Point * pipMultiplier);
   }

   // Calculate Take Profit
   if(TakeProfitPips > 0)
   {
      tp = price - (TakeProfitPips * _Point * pipMultiplier);
   }

   string comment = StringFormat("SELL M%d at %.5f", multiplier, triggerPrice);

   if(trade.Sell(lotSize, _Symbol, price, sl, tp, comment))
   {
      PrintFormat("SELL order opened: Lot=%.2f, Price=%.5f, SL=%.5f, TP=%.5f, Multiplier=%d",
                  lotSize, price, sl, tp, multiplier);
   }
   else
   {
      PrintFormat("Failed to open SELL order. Error: %d", GetLastError());
   }
}

//+------------------------------------------------------------------+
//| Open buy trade                                                   |
//+------------------------------------------------------------------+
void OpenBuyTrade(int multiplier, double triggerPrice)
{
   double lotSize = CalculateLotSize();
   if(lotSize <= 0) return;

   double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double sl = 0, tp = 0;

   // Adjust pip multiplier for 5-digit or 3-digit brokers
   double pipMultiplier = (_Digits == 5 || _Digits == 3) ? 10.0 : 1.0;

   // Calculate Stop Loss
   if(StopLossPips > 0)
   {
      sl = price - (StopLossPips * _Point * pipMultiplier);
   }

   // Calculate Take Profit
   if(TakeProfitPips > 0)
   {
      tp = price + (TakeProfitPips * _Point * pipMultiplier);
   }

   string comment = StringFormat("BUY M%d at %.5f", multiplier, triggerPrice);

   if(trade.Buy(lotSize, _Symbol, price, sl, tp, comment))
   {
      PrintFormat("BUY order opened: Lot=%.2f, Price=%.5f, SL=%.5f, TP=%.5f, Multiplier=%d",
                  lotSize, price, sl, tp, multiplier);
   }
   else
   {
      PrintFormat("Failed to open BUY order. Error: %d", GetLastError());
   }
}

//+------------------------------------------------------------------+
//| Calculate lot size based on risk percentage                      |
//+------------------------------------------------------------------+
double CalculateLotSize()
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmount = balance * (RiskPercent / 100.0);

   // Adjust pip multiplier for 5-digit or 3-digit brokers
   double pipMultiplier = (_Digits == 5 || _Digits == 3) ? 10.0 : 1.0;

   if(StopLossPips <= 0)
   {
      // If no stop loss, use fixed risk amount with default pip value
      double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
      double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
      double pointValue = tickValue * (_Point / tickSize);
      double lotSize = riskAmount / (50 * pointValue * pipMultiplier); // Assume 50 pips risk
      return NormalizeLot(lotSize);
   }

   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double pointValue = tickValue * (_Point / tickSize);

   double lotSize = riskAmount / (StopLossPips * pointValue * pipMultiplier);

   return NormalizeLot(lotSize);
}

//+------------------------------------------------------------------+
//| Normalize lot size                                               |
//+------------------------------------------------------------------+
double NormalizeLot(double lot)
{
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(lot < minLot) return minLot;
   if(lot > maxLot) return maxLot;

   return MathFloor(lot / lotStep) * lotStep;
}

//+------------------------------------------------------------------+
//| Get count of open trades                                         |
//+------------------------------------------------------------------+
int GetOpenTradesCount()
{
   int count = 0;
   int totalPositions = PositionsTotal();

   for(int i = 0; i < totalPositions; i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue; // safety

      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
            PositionGetInteger(POSITION_MAGIC) == MAGIC_NUMBER)
         {
            count++;
         }
      }
   }
   return count;
}

//+------------------------------------------------------------------+
//| Draw or update horizontal line                                   |
//+------------------------------------------------------------------+
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);
      ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
   }
   else
   {
      ObjectSetDouble(0, name, OBJPROP_PRICE, price);
   }
}

//+------------------------------------------------------------------+
//| Clear lines with defined prefixes                                |
//+------------------------------------------------------------------+
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);
   }
}
//+------------------------------------------------------------------+