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.