Complete .mq5 source code

Silver XAGUSD Expert Advisor MQL5: Commodity Trend-Following System

An educational H1 EMA-cross Expert Advisor for a broker-provided silver symbol. The example uses ATR expansion, fixed lots, initial SL/TP and a price-percentage-activated trailing stop. It does not implement a news calendar, session schedule, gold/silver correlation check or account leverage control. No reproducible performance report is supplied.

Strategy logic

Entry conditions

On a new H1 bar, a completed 20/100 EMA cross can qualify when completed ATR is above both its minimum price-unit input and its moving average. There is no additional candle-close-above-both-EMAs condition. Entry is blocked while a matching position exists or the in-memory cooldown has not elapsed. The defaults illustrate a rule set, not an optimal silver configuration.

Exit conditions

Initial SL is 1.8 ATR from the requested entry price and TP is fixed at 1.5 times the trailing multiplier, or 3.3 ATR with the defaults. Every processed H1 bar, a 1.5% favorable price move can activate a 2.2 ATR trailing distance; that is a price move, not account profit, and it does not guarantee a profitable fill. Opposite completed crosses request closure before entry filters. Failed closes retry on subsequent ticks; the EA does not reverse immediately after a close attempt.

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 lots; validate the broker contract
input ulong    MagicNumber      = 20241001;  // EA magic number
input int      MinMinutesAfter  = 120;       // Minutes after last trade before new entry

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

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

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

//+------------------------------------------------------------------+
//| Expert initialization function                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(FastEMA_Period<1 || SlowEMA_Period<=FastEMA_Period || ATR_Period<1 || ATR_SMA_Period<1 || ATR_SL_Mult<=0 || ATR_Trail_Mult<=0 || TrailActivPct<0 || MinATR_Value<0 || LotSize<=0 || MinMinutesAfter<0) return INIT_PARAMETERS_INCORRECT;
   //--- 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()
  {
   if(closePending) { CloseAllMyPositions(); closePending=CountMyPositions(POSITION_TYPE_BUY)+CountMyPositions(POSITION_TYPE_SELL)>0; }
   //--- New bar detection (H1)
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, PERIOD_H1, 0);
   if(currentBarTime == 0 || currentBarTime == lastBarTime)
      return;

   //--- 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];

   if(atrVal<=0 || atrSMA<=0) return;
   lastBarTime=currentBarTime;
   ManageTrailingStops(atrVal); // Every processed H1 bar, independent of entry filters.
   bool bullCross=fastPrev<=slowPrev && fastNow>slowNow;
   bool bearCross=fastPrev>=slowPrev && fastNow<slowNow;
   if((bullCross && CountMyPositions(POSITION_TYPE_SELL)>0) ||
      (bearCross && CountMyPositions(POSITION_TYPE_BUY)>0))
   {
      CloseAllMyPositions();
      closePending=CountMyPositions(POSITION_TYPE_BUY)+CountMyPositions(POSITION_TYPE_SELL)>0;
      return; // Never reverse immediately after a close attempt.
   }
   if(closePending || CountMyPositions(POSITION_TYPE_BUY)+CountMyPositions(POSITION_TYPE_SELL)>0) return;
   if(atrVal<MinATR_Value || atrVal<=atrSMA) return;
   if(lastTradeCloseTime>0 && TimeCurrent()-lastTradeCloseTime<MinMinutesAfter*60) return;
   if(bullCross) OpenTrade(ORDER_TYPE_BUY,atrVal);
   else if(bearCross) OpenTrade(ORDER_TYPE_SELL,atrVal);
  }
void OnTradeTransaction(const MqlTradeTransaction &trans,const MqlTradeRequest &request,const MqlTradeResult &result)
{
   if(trans.type!=TRADE_TRANSACTION_DEAL_ADD || !HistoryDealSelect(trans.deal)) return;
   if(HistoryDealGetString(trans.deal,DEAL_SYMBOL)!=_Symbol || HistoryDealGetInteger(trans.deal,DEAL_MAGIC)!=(long)MagicNumber) return;
   long entry=HistoryDealGetInteger(trans.deal,DEAL_ENTRY);
   if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY || entry==DEAL_ENTRY_INOUT)
      lastTradeCloseTime=(datetime)HistoryDealGetInteger(trans.deal,DEAL_TIME);
}

//+------------------------------------------------------------------+
//| 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;   // Requested deviation in broker points
   req.magic        = MagicNumber;
   req.comment      = "SilverEA";
   req.type_filling = ORDER_FILLING_IOC;

   if(!OrderSend(req, res) || (res.retcode!=TRADE_RETCODE_DONE && res.retcode!=TRADE_RETCODE_DONE_PARTIAL))
      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) || (res.retcode!=TRADE_RETCODE_DONE && res.retcode!=TRADE_RETCODE_DONE_PARTIAL))
         Print("CloseOrder failed ticket=", ticket, " retcode=", res.retcode);
      else
         lastTradeCloseTime = TimeCurrent();
     }
  }

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void ManageTrailingStops(double atrVal)
  {
   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;
         if(!OrderSend(req,res) || res.retcode!=TRADE_RETCODE_DONE) Print("Trail failed: ",res.retcode);
        }
     }
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor, save it in the MQL5/Experts folder, and compile with F7.

Generate a custom XAGUSD trend-following EA

Describe the entry, exit, position, order, and risk rules you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.

Frequently Asked Questions

Related MQL5 pages

Risk and testing note

These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.