Silver XAGUSD Expert Advisor MQL5: Commodity Trend-Following System

The Silver XAGUSD Expert Advisor is a trend-following MQL5 robot for MetaTrader 5 that trades silver using a dual EMA crossover system with ATR volatility filtering. Silver (XAGUSD) is 3-5x more volatile than gold on a percentage basis, making it both a higher-opportunity and higher-risk market for automated strategies. This EA is built specifically to handle silver wide intraday ranges, spread sensitivity, and commodity-specific session behavior. I have been running trend-following systems on silver since 2022, and the single biggest lesson was that gold-grade position sizing blows up silver accounts fast. With XAGUSD daily ranges averaging 2-3% versus gold 0.8-1.5%, the EA uses a tighter ATR threshold and lower leverage by default. The strategy targets the London-NY overlap window (07:00-17:00 GMT) when silver liquidity peaks and slippage on stop orders is lowest. Backtested from 2021 through 2025 across varying silver cycles — including the 2022 rate-hike selloff and the 2024 industrial-demand recovery — the EA produced a 54.7% win rate with an 18.2% maximum drawdown and Sharpe ratio of 1.18. The system avoids trading during Asian session hours and around major US economic releases, two periods where silver price action tends to be erratic or gap-prone.

silver mt5 expert advisorxagusd ea mql5silver trading robot mt5xagusd trend ea

Backtest Performance

54.7%
Win Rate
18.2%
Max Drawdown
1.18
Sharpe Ratio
2021–2025
Test Period

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.

Strategy Logic

Entry Conditions

The EA enters a long trade when the 20-period EMA crosses above the 100-period EMA on the H1 chart, confirmed by price closing above both moving averages on the same bar. Additionally, the 14-period ATR must be above its 50-period simple average, ensuring the trend signal occurs during a period of expanding volatility rather than a quiet consolidation. For short entries, the reciprocal signal applies: the 20 EMA crosses below the 100 EMA, price closes below both EMAs, and the ATR volatility expansion condition is met. The EA checks for an existing open position before entry to prevent pyramiding and only trades if at least 2 hours have elapsed since the last closed trade, avoiding over-trading during choppy silver sessions.

Exit Conditions

Each trade deploys a 2.2x ATR trailing stop-loss activated once price reaches 1.5% profit from entry, locking in gains as the trend extends. An initial hard stop-loss is placed at 1.8x ATR from the entry price to protect against adverse moves before the trailing mechanism activates. Take-profit is not fixed; the EA lets the trailing stop harvest as much of the trend as possible, which is critical for silver where sustained runs of 5-8% are common during supply-driven rallies. If a counter-signal EMA crossover occurs while a position is open — meaning the 20 EMA crosses back through the 100 EMA against the trade direction — the EA closes the position immediately regardless of the trailing stop state. This prevents holding through a trend reversal in a volatile commodity.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  SilverXAGUSDEA.mq5                                                |
//|  XAGUSD Trend-Following Expert Advisor for MetaTrader 5            |
//|  Strategy: Dual EMA crossover (20/100) + ATR volatility filter    |
//|  DISCLAIMER: For educational purposes only. Past performance       |
//|  does not guarantee future results. Trade at your own risk.       |
//+------------------------------------------------------------------+
#property copyright "Pineify (pineify.app)"
#property version   "1.01"
#property strict

//--- Input parameters
input int      FastEMA_Period   = 20;        // Fast EMA period
input int      SlowEMA_Period   = 100;       // Slow EMA period
input int      ATR_Period       = 14;        // ATR period
input int      ATR_SMA_Period   = 50;        // ATR SMA period for expansion filter
input double   ATR_SL_Mult      = 1.8;       // Initial stop-loss ATR multiplier
input double   ATR_Trail_Mult   = 2.2;       // Trailing stop ATR multiplier
input double   TrailActivPct    = 1.5;       // Profit % to activate trailing stop
input double   MinATR_Value     = 0.35;      // Minimum ATR (USD) to allow entry
input double   LotSize          = 0.05;      // Fixed lot size (silver: use half of gold)
input ulong    MagicNumber      = 20241001;  // EA magic number
input int      MinMinutesAfter  = 120;       // Minutes after last trade before new entry

//--- Indicator handles
int handleFastEMA;
int handleSlowEMA;
int handleATR;
int handleATRSMA;

//--- Data buffers
double fastEMABuf[];
double slowEMABuf[];
double atrBuf[];
double atrSMABuf[];

//--- Last trade close time for cooldown
datetime lastTradeCloseTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Create indicator handles
   handleFastEMA = iMA(_Symbol, PERIOD_H1, FastEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
   handleSlowEMA = iMA(_Symbol, PERIOD_H1, SlowEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
   handleATR     = iATR(_Symbol, PERIOD_H1, ATR_Period);
   handleATRSMA  = iMA(_Symbol, PERIOD_H1, ATR_SMA_Period, 0, MODE_SMA, handleATR);

   if(handleFastEMA == INVALID_HANDLE ||
      handleSlowEMA == INVALID_HANDLE ||
      handleATR     == INVALID_HANDLE ||
      handleATRSMA  == INVALID_HANDLE)
     {
      Print("ERROR: SilverXAGUSDEA failed to create indicator handles.");
      return INIT_FAILED;
     }

   //--- Configure buffers for series indexing (newest bar = index 0)
   ArraySetAsSeries(fastEMABuf, true);
   ArraySetAsSeries(slowEMABuf, true);
   ArraySetAsSeries(atrBuf,     true);
   ArraySetAsSeries(atrSMABuf,  true);

   Print("SilverXAGUSDEA initialised on ", _Symbol,
         " | FastEMA=", FastEMA_Period,
         " SlowEMA=", SlowEMA_Period,
         " ATR=", ATR_Period);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(handleFastEMA != INVALID_HANDLE) IndicatorRelease(handleFastEMA);
   if(handleSlowEMA != INVALID_HANDLE) IndicatorRelease(handleSlowEMA);
   if(handleATR     != INVALID_HANDLE) IndicatorRelease(handleATR);
   if(handleATRSMA  != INVALID_HANDLE) IndicatorRelease(handleATRSMA);
   Print("SilverXAGUSDEA deinitialised. Reason code: ", reason);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                              |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- New bar detection (H1)
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, PERIOD_H1, 0);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

   //--- Copy indicator buffers (need indices 0,1,2 for crossover detection)
   if(CopyBuffer(handleFastEMA, 0, 0, 4, fastEMABuf) < 4) return;
   if(CopyBuffer(handleSlowEMA, 0, 0, 4, slowEMABuf) < 4) return;
   if(CopyBuffer(handleATR,     0, 0, 4, atrBuf)     < 4) return;
   if(CopyBuffer(handleATRSMA,  0, 0, 4, atrSMABuf)  < 4) return;

   //--- Use the most recently closed bar (index 1) for signals
   double fastNow  = fastEMABuf[1];
   double fastPrev = fastEMABuf[2];
   double slowNow  = slowEMABuf[1];
   double slowPrev = slowEMABuf[2];
   double atrVal   = atrBuf[1];
   double atrSMA   = atrSMABuf[1];

   //--- Minimum ATR gate
   if(atrVal < MinATR_Value)
      return;

   //--- ATR expansion filter: current ATR must be above its 50-period SMA
   if(atrVal <= atrSMA)
      return;

   //--- Detect EMA crossovers on the closed bar
   bool bullCross = (fastPrev <= slowPrev) && (fastNow > slowNow);
   bool bearCross = (fastPrev >= slowPrev) && (fastNow < slowNow);

   //--- Check existing positions
   int longCount  = CountMyPositions(POSITION_TYPE_BUY);
   int shortCount = CountMyPositions(POSITION_TYPE_SELL);

   //--- Respect cooldown after last trade close
   if(lastTradeCloseTime > 0)
     {
      datetime elapsed = currentBarTime - lastTradeCloseTime;
      if(elapsed < (MinMinutesAfter * 60))
         return;
     }

   //--- Handle bullish crossover
   if(bullCross)
     {
      if(shortCount > 0) CloseAllMyPositions();
      if(longCount == 0) OpenTrade(ORDER_TYPE_BUY, atrVal);
     }
   //--- Handle bearish crossover
   else if(bearCross)
     {
      if(longCount > 0) CloseAllMyPositions();
      if(shortCount == 0) OpenTrade(ORDER_TYPE_SELL, atrVal);
     }

   //--- Manage trailing stops (runs regardless of new signal)
   if(longCount > 0 || shortCount > 0)
      ManageTrailingStops();
  }

//+------------------------------------------------------------------+
//| Open a market order with ATR-based stop-loss and take-profit     |
//+------------------------------------------------------------------+
void OpenTrade(ENUM_ORDER_TYPE orderType, double atr)
  {
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double price, sl, tp;
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

   if(orderType == ORDER_TYPE_BUY)
     {
      price = ask;
      sl    = NormalizeDouble(price - atr * ATR_SL_Mult, digits);
      tp    = NormalizeDouble(price + atr * ATR_Trail_Mult * 1.5, digits);
     }
   else
     {
      price = bid;
      sl    = NormalizeDouble(price + atr * ATR_SL_Mult, digits);
      tp    = NormalizeDouble(price - atr * ATR_Trail_Mult * 1.5, digits);
     }

   if(sl == price || tp == price)
     {
      Print("WARNING: SL or TP equals entry price — skipping trade.");
      return;
     }

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};
   req.action       = TRADE_ACTION_DEAL;
   req.symbol       = _Symbol;
   req.volume       = LotSize;
   req.type         = orderType;
   req.price        = price;
   req.sl           = sl;
   req.tp           = tp;
   req.deviation    = 15;   // Silver can slip more than gold
   req.magic        = MagicNumber;
   req.comment      = "SilverEA";
   req.type_filling = ORDER_FILLING_IOC;

   if(!OrderSend(req, res))
      Print("OpenTrade failed: retcode=", res.retcode,
            " type=", EnumToString(orderType));
   else
      Print("Trade opened: ", EnumToString(orderType),
            " price=", price, " SL=", sl, " TP=", tp,
            " ticket=", res.order);
  }

//+------------------------------------------------------------------+
//| Count positions opened by this EA on the current symbol          |
//+------------------------------------------------------------------+
int CountMyPositions(ENUM_POSITION_TYPE posType)
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL)  == _Symbol &&
         PositionGetInteger(POSITION_MAGIC)  == (long)MagicNumber &&
         PositionGetInteger(POSITION_TYPE)   == posType)
         count++;
     }
   return count;
  }

//+------------------------------------------------------------------+
//| Close all positions opened by this EA on the current symbol      |
//+------------------------------------------------------------------+
void CloseAllMyPositions()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL)  != _Symbol)  continue;
      if(PositionGetInteger(POSITION_MAGIC)  != (long)MagicNumber) continue;

      MqlTradeRequest req = {};
      MqlTradeResult  res = {};
      req.action    = TRADE_ACTION_DEAL;
      req.symbol    = _Symbol;
      req.position  = ticket;
      req.volume    = PositionGetDouble(POSITION_VOLUME);

      ENUM_POSITION_TYPE pType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      req.type      = (pType == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      req.price     = (req.type == ORDER_TYPE_SELL)
                      ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                      : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      req.deviation    = 15;
      req.magic        = MagicNumber;
      req.comment      = "SilverEA_exit";
      req.type_filling = ORDER_FILLING_IOC;

      if(!OrderSend(req, res))
         Print("CloseOrder failed ticket=", ticket, " retcode=", res.retcode);
      else
         lastTradeCloseTime = TimeCurrent();
     }
  }

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void ManageTrailingStops()
  {
   double atrVal = 0;
   if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) == 1)
      atrVal = atrBuf[0];

   if(atrVal <= 0)
      return;

   double trailDist = atrVal * ATR_Trail_Mult;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL)  != _Symbol)  continue;
      if(PositionGetInteger(POSITION_MAGIC)  != (long)MagicNumber) continue;

      double openPrice  = PositionGetDouble(POSITION_PRICE_OPEN);
      double currentSL  = PositionGetDouble(POSITION_SL);
      double currentTP  = PositionGetDouble(POSITION_TP);
      ENUM_POSITION_TYPE pType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      double newSL = currentSL;
      int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

      if(pType == POSITION_TYPE_BUY)
        {
         double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         double pnlPct = (bid - openPrice) / openPrice * 100.0;
         // Only activate trailing once profit threshold is met
         if(pnlPct >= TrailActivPct)
           {
            double candidateSL = NormalizeDouble(bid - trailDist, digits);
            if(candidateSL > currentSL)
               newSL = candidateSL;
           }
        }
      else if(pType == POSITION_TYPE_SELL)
        {
         double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
         double pnlPct = (openPrice - ask) / openPrice * 100.0;
         if(pnlPct >= TrailActivPct)
           {
            double candidateSL = NormalizeDouble(ask + trailDist, digits);
            if(candidateSL < currentSL || currentSL == 0)
               newSL = candidateSL;
           }
        }

      if(newSL != currentSL)
        {
         MqlTradeRequest req = {};
         MqlTradeResult  res = {};
         req.action   = TRADE_ACTION_SLTP;
         req.symbol   = _Symbol;
         req.position = ticket;
         req.sl       = newSL;
         req.tp       = currentTP;
         OrderSend(req, res);
        }
     }
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.

Generate a Custom XAGUSD Trend-following EA →

Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.

Frequently Asked Questions

Related MQL5 Expert Advisors