//+------------------------------------------------------------------+
//| Expert Advisor: Trend-Following Momentum Bot (Optimized v1.6)   |
//| Symbol: XAUUSD (Gold) | Timeframe: M15                          |
//| Author: Grok + ChatGPT Optimization                             |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade trade;

// Input Parameters
int magicNumber = 12345;
input double PartialTP_ProfitTarget = 1000.0;   // Lower target for XM
input double PartialTP_CloseFraction = 0.4;    // Smaller fraction
input int SMA_Short_Period = 50;
input int SMA_Long_Period = 200;
input int RSI_Period = 14;
input int MACD_Fast = 12;
input int MACD_Slow = 26;
input int MACD_Signal = 9;
input int ADX_Period = 14;
input double Risk_Percent = 0.5;
input double Max_Total_Risk = 1.5;
input double Reward_Risk_Ratio = 2.5;
input double MACD_MinGap = 0.5;
input bool Use_Trailing_Stop = true;
input double ATR_Multiplier = 2.5;
input double Min_ATR_Threshold = 1.0;
input bool Use_Session_Filter = true;
input int StartHour = 10;
input int EndHour = 19;
input bool Use_Dynamic_TP = false;
input double ATR_TP_Multiplier = 3.0;
input bool Use_Stacking_Mode = false;
input int Max_Stacked_Trades = 2;
input double Stacking_Lot_Multiplier = 1.5;
input bool Enable_Logging = false;
input int Cooldown_Minutes = 10;
// New Inputs for Suggested Features
input double Max_Spread_Points = 30.0;     // Max allowed spread in points
input double Min_SL_Points = 50.0;         // Minimum SL distance in points
input bool Use_Early_Exit = true;          // Enable early exit on signal flip

double account_balance;
int handle_sma_short, handle_sma_long, handle_rsi, handle_macd, handle_adx, handle_atr;
datetime last_buy_trade_time = 0;          // Separate cooldown for BUY
datetime last_sell_trade_time = 0;         // Separate cooldown for SELL

// Check if current time is within allowed trading session
bool IsWithinSession()
{
   if (!Use_Session_Filter) return true;
   MqlDateTime tm;
   TimeToStruct(TimeCurrent(), tm);
   int hour = tm.hour;
   return (hour >= StartHour && hour <= EndHour);
}

// Check if market is open for trading (Gold: Sun 22:00 to Fri 22:00 SAST)
bool IsMarketOpen()
{
   MqlDateTime tm;
   TimeToStruct(TimeCurrent(), tm);
   int day = tm.day_of_week; // 0=Sunday, 1=Monday, ..., 6=Saturday
   int hour = tm.hour;
   if (day == 0) // Sunday
      return hour >= 22;
   else if (day >= 1 && day <= 4) // Monday to Thursday
      return true;
   else if (day == 5) // Friday
      return hour <= 22;
   else // Saturday
      return false;
}

// Check if cooldown period has passed for specific direction
bool IsCooldownOver(bool is_buy)
{
   if (Cooldown_Minutes == 0) return true;
   datetime current_time = TimeCurrent();
   if (is_buy)
      return (current_time - last_buy_trade_time >= Cooldown_Minutes * 60);
   else
      return (current_time - last_sell_trade_time >= Cooldown_Minutes * 60);
}

// Check if spread is within acceptable range
bool IsSpreadAcceptable()
{
   if (!SymbolInfoInteger(_Symbol, SYMBOL_SELECT))
   {
      Print("❌ Symbol not selected: ", _Symbol);
      return false;
   }
   long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double spread_price = spread * point;
   Print("📏 Current Spread: ", spread_price, " (", spread, " points)");
   return (spread_price <= (Max_Spread_Points * point));
}

// Count open positions
int CountOpenPositions(string symbol)
{
   int count = 0;
   for (int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if (ticket > 0 && PositionSelectByTicket(ticket))
      {
         if (PositionGetString(POSITION_SYMBOL) == symbol)
            count++;
      }
   }
   return count;
}

// Trailing stop logic
void UpdateTrailingStop()
{
   double atr[];
   ArraySetAsSeries(atr, true);
   if (CopyBuffer(handle_atr, 0, 0, 1, atr) < 1) return;
   double trail_distance = atr[0] * ATR_Multiplier;
   for (int i = 0; i < PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol)
    {
         double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
         double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
         double current_sl = PositionGetDouble(POSITION_SL);
         double current_tp = PositionGetDouble(POSITION_TP);
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         if (type == POSITION_TYPE_BUY)
         {
            double new_sl = current_price - trail_distance;
            if (new_sl > current_sl && new_sl > open_price)
               trade.PositionModify(ticket, new_sl, current_tp);
         }
         else if (type == POSITION_TYPE_SELL)
         {
            double new_sl = current_price + trail_distance;
            if (new_sl < current_sl || current_sl == 0)
               trade.PositionModify(ticket, new_sl, current_tp);
         }
      }
   }
}

// Early exit logic on signal flip
void CheckEarlyExit()
{
   if (!Use_Early_Exit) return;
   double sma_short[], sma_long[], adx[], rsi[], macd_main[], macd_signal[];
   ArraySetAsSeries(sma_short, true); ArraySetAsSeries(sma_long, true);
   ArraySetAsSeries(adx, true); ArraySetAsSeries(rsi, true);
   ArraySetAsSeries(macd_main, true); ArraySetAsSeries(macd_signal, true);
   if (CopyBuffer(handle_sma_short, 0, 0, 2, sma_short) < 2 ||
       CopyBuffer(handle_sma_long, 0, 0, 1, sma_long) < 1 ||
       CopyBuffer(handle_adx, 0, 0, 1, adx) < 1 ||
       CopyBuffer(handle_rsi, 0, 0, 1, rsi) < 1 ||
       CopyBuffer(handle_macd, 0, 0, 1, macd_main) < 1 ||
       CopyBuffer(handle_macd, 1, 0, 1, macd_signal) < 1)
      return;
   bool uptrend = sma_short[0] > sma_long[0] && sma_short[0] > sma_short[1] && adx[0] > 25;
   bool downtrend = sma_short[0] < sma_long[0] && sma_short[0] < sma_short[1] && adx[0] > 25;
   bool macd_cross_up = (macd_main[0] - macd_signal[0]) > MACD_MinGap;
   bool macd_cross_down = (macd_signal[0] - macd_main[0]) > MACD_MinGap;
   for (int i = 0; i < PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol)
      {
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         if (type == POSITION_TYPE_BUY && downtrend && macd_cross_down && rsi[0] < 45)
         {
            trade.PositionClose(ticket);
            Print("🚪 Early Exit: Buy closed due to bearish signal flip");
         }
         else if (type == POSITION_TYPE_SELL && uptrend && macd_cross_up && rsi[0] > 55)
         {
            trade.PositionClose(ticket);
            Print("🚪 Early Exit: Sell closed due to bullish signal flip");
         }
      }
   }
}

// Log indicator values for diagnostics
void LogIndicators(double sma_short, double sma_long, double adx, double rsi,
                  double macd_main, double macd_signal, double atr)
{
   if (!Enable_Logging) return;
   Print("📊 Indicators: SMA_Short=", sma_short, " SMA_Long=", sma_long,
         " ADX=", adx, " RSI=", rsi, " MACD_Main=", macd_main,
         " MACD_Signal=", macd_signal, " ATR=", atr);
}

int OnInit()
{
   account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
   trade.SetExpertMagicNumber(magicNumber);
   trade.SetMarginMode();
   trade.SetTypeFilling(ORDER_FILLING_FOK); // XM requires FOK filling mode
   trade.SetDeviationInPoints(5); // Max slippage
   handle_sma_short = iMA(_Symbol, PERIOD_M15, SMA_Short_Period, 0, MODE_SMA, PRICE_CLOSE);
   handle_sma_long = iMA(_Symbol, PERIOD_M15, SMA_Long_Period, 0, MODE_SMA, PRICE_CLOSE);
   handle_rsi = iRSI(_Symbol, PERIOD_M5, RSI_Period, PRICE_CLOSE);
   handle_macd = iMACD(_Symbol, PERIOD_M5, MACD_Fast, MACD_Slow, MACD_Signal, PRICE_CLOSE);
   handle_adx = iADX(_Symbol, PERIOD_M15, ADX_Period);
   handle_atr = iATR(_Symbol, PERIOD_M5, 14);
   if (handle_sma_short == INVALID_HANDLE || handle_sma_long == INVALID_HANDLE ||
       handle_rsi == INVALID_HANDLE || handle_macd == INVALID_HANDLE ||
       handle_adx == INVALID_HANDLE || handle_atr == INVALID_HANDLE)
   {
      Print("❌ Failed to initialize indicators");
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

void OnTick()
{
   if (!IsMarketOpen()) return;
   // Position management
   if (Use_Trailing_Stop) UpdateTrailingStop();
   if (Use_Early_Exit) CheckEarlyExit();
   CheckPartialTakeProfit();
   // Trading logic
   if (IsWithinSession() && IsSpreadAcceptable())
   {
      int open_positions = CountOpenPositions(_Symbol);
      if (open_positions >= (Use_Stacking_Mode ? Max_Stacked_Trades : 3)) return;
      double sma_short[], sma_long[], adx[], rsi[], macd_main[], macd_signal[], atr[];
      ArraySetAsSeries(sma_short, true); ArraySetAsSeries(sma_long, true);
      ArraySetAsSeries(adx, true); ArraySetAsSeries(rsi, true);
      ArraySetAsSeries(macd_main, true); ArraySetAsSeries(macd_signal, true);
      ArraySetAsSeries(atr, true);
      if (CopyBuffer(handle_sma_short, 0, 0, 2, sma_short) < 2 ||
          CopyBuffer(handle_sma_long, 0, 0, 1, sma_long) < 1 ||
          CopyBuffer(handle_adx, 0, 0, 1, adx) < 1 ||
          CopyBuffer(handle_rsi, 0, 0, 1, rsi) < 1 ||
          CopyBuffer(handle_macd, 0, 0, 1, macd_main) < 1 ||
          CopyBuffer(handle_macd, 1, 0, 1, macd_signal) < 1 ||
          CopyBuffer(handle_atr, 0, 0, 1, atr) < 1 || atr[0] < Min_ATR_Threshold)
         return;
      double close[], open[];
      ArraySetAsSeries(close, true); ArraySetAsSeries(open, true);
      if (CopyClose(_Symbol, PERIOD_M5, 0, 2, close) < 2 ||
          CopyOpen(_Symbol, PERIOD_M5, 0, 2, open) < 2) return;
      LogIndicators(sma_short[0], sma_long[0], adx[0], rsi[0],
                    macd_main[0], macd_signal[0], atr[0]);
      bool uptrend = sma_short[0] > sma_long[0] && sma_short[0] > sma_short[1] && adx[0] > 25;
      bool downtrend = sma_short[0] < sma_long[0] && sma_short[0] < sma_short[1] && adx[0] > 25;
      bool macd_cross_up = (macd_main[0] - macd_signal[0]) > MACD_MinGap;
      bool macd_cross_down = (macd_signal[0] - macd_main[0]) > MACD_MinGap;
      bool bullish_candle = close[1] > open[1] && close[1] > sma_short[1];
      bool bearish_candle = close[1] < open[1] && close[1] < sma_short[1];
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double money_risk = account_balance * (Risk_Percent / 100.0);
      double pip_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
      double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
      double lot_multiplier = Use_Stacking_Mode ? MathPow(Stacking_Lot_Multiplier, open_positions) : 1.0;
     
     Print("Buy Conditions: Uptrend=", uptrend, " RSI>55=", rsi[0]>55, 
      " MACD Cross Up=", macd_cross_up, " Bullish Candle=", bullish_candle);
Print("Sell Conditions: Downtrend=", downtrend, " RSI<45=", rsi[0]<45, 
      " MACD Cross Down=", macd_cross_down, " Bearish Candle=", bearish_candle);
     
      if (uptrend && rsi[0] > 55 && macd_cross_up && bullish_candle && IsCooldownOver(true))
      {
         double swing_low = iLow(_Symbol, PERIOD_M5, 1);
         for (int i = 2; i <= 6; i++) swing_low = MathMin(swing_low, iLow(_Symbol, PERIOD_M5, i));
         double sl = swing_low - 10 * _Point;
         if ((ask - sl) < Min_SL_Points * _Point) sl = ask - Min_SL_Points * _Point;
         double distance = ask - sl;
         double tp = 0;
         if (Use_Trailing_Stop)
            tp = 0;
         else if (Use_Dynamic_TP)
            tp = ask + atr[0] * ATR_TP_Multiplier;
         else
            tp = ask + distance * Reward_Risk_Ratio;
         double lot = money_risk / (distance / _Point * pip_value);
         lot = NormalizeDouble(MathMax(lot * lot_multiplier, min_lot), 2);
         if ((open_positions * money_risk + money_risk) <= account_balance * (Max_Total_Risk / 100.0))
         {
            if (trade.Buy(lot, _Symbol, ask, sl, tp, "TrendBot v1.6 Buy"))
            {
               Print("✅ Buy @", ask, " SL:", sl, " TP:", tp, " Lot:", lot);
               last_buy_trade_time = TimeCurrent();
            }
         }
      }
      if (downtrend && rsi[0] < 45 && macd_cross_down && bearish_candle && IsCooldownOver(false))
      {
         double swing_high = iHigh(_Symbol, PERIOD_M5, 1);
         for (int i = 2; i <= 6; i++) swing_high = MathMax(swing_high, iHigh(_Symbol, PERIOD_M5, i));
         double sl = swing_high + 10 * _Point;
         if ((sl - bid) < Min_SL_Points * _Point) sl = bid + Min_SL_Points * _Point;
         double distance = sl - bid;
         double tp = 0;
         if (Use_Trailing_Stop)
            tp = 0;
         else if (Use_Dynamic_TP)
            tp = bid - atr[0] * ATR_TP_Multiplier;
         else
            tp = bid - distance * Reward_Risk_Ratio;
         double lot = money_risk / (distance / _Point * pip_value);
         lot = NormalizeDouble(MathMax(lot * lot_multiplier, min_lot), 2);
         if ((open_positions * money_risk + money_risk) <= account_balance * (Max_Total_Risk / 100.0))
         {
            if (trade.Sell(lot, _Symbol, bid, sl, tp, "TrendBot v1.6 Sell"))
            {
               Print("✅ Sell @", bid, " SL:", sl, " TP:", tp, " Lot:", lot);
               last_sell_trade_time = TimeCurrent();
            }
         }
      }
   }
}

void OnDeinit(const int reason)
{
   Print("Bot stopped: Reason ", reason);
}

void CheckPartialTakeProfit()
{
   double minLot = 0.01; // XM Ultra Low fixed minimum
   double lotStep = 0.01; // XM Ultra Low fixed step
   for (int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if (ticket <= 0) continue;
      if (PositionSelectByTicket(ticket) &&
          PositionGetString(POSITION_SYMBOL) == _Symbol)
      {
         double profit = PositionGetDouble(POSITION_PROFIT);
         double volume = PositionGetDouble(POSITION_VOLUME);
         if (profit >= PartialTP_ProfitTarget && volume > minLot * 2)
         {
            double close_volume = NormalizeDouble(volume * PartialTP_CloseFraction, 2);
            if (close_volume < minLot) close_volume = minLot;
            if (volume - close_volume < minLot) close_volume = volume - minLot;
            close_volume = MathFloor(close_volume / lotStep) * lotStep;
            close_volume = NormalizeDouble(close_volume, 2);
            CTrade partialTrade;
            partialTrade.SetExpertMagicNumber(magicNumber);
            partialTrade.SetTypeFilling(ORDER_FILLING_FOK);
            for (int attempt = 0; attempt < 3; attempt++)
            {
               if (partialTrade.PositionClosePartial(ticket, close_volume))
               {
                  Print("✅ XM Partial Closed: ", close_volume,
                        " of ", volume, " lots | Profit: R", profit);
                  break;
               }
               else
               {
                  int error = GetLastError();
                  Print("❌ XM Partial Close Failed (Attempt ", attempt + 1,
                        "): ", ErrorDescription(error), " | Code: ", error);
                  if (error == 10014 || error == 135) // Requote or off quotes
                  {
                     Sleep(500);
                  }
                  else break;
               }
            }
         }
         else
         {
            Print("⚠️ Partial TP conditions not met: Vol=", volume,
                  " Profit=", profit, " Target=", PartialTP_ProfitTarget);
         }
      }
   }
}

string ErrorDescription(int error)
{
   switch (error)
   {
      case 10006: return "XM: Trade context busy - retry needed";
      case 10013: return "XM: Invalid volume (must be multiple of 0.01)";
      case 10014: return "XM: Price changed - requote occurred";
      case 10015: return "XM: Broker rejected order";
      case 10021: return "XM: Not enough volume for partial close";
      case 10027: return "XM: Trade disabled";
      case 10030: return "XM: Invalid ticket";
      case 135:   return "XM: Off quotes - market closed or frozen";
      case 136:   return "XM: Broker is too busy";
      case 137:   return "XM: Broker is too busy (try again)";
      case 138:   return "XM: Requote - new prices received";
      case 146:   return "XM: Trading subsystem is busy";
      case 4001:  return "XM: Incorrect function call sequence";
      default:    return "Unknown error (" + IntegerToString(error) + ") - contact XM support";
   }
}  give me a list of think you will improve on this bot if you were a trading house