Build an EA for Gold (XAUUSD) in MQL5 β A Complete Case Study
How should you configure an MT5 EA for gold (XAUUSD)?
For XAUUSD in 2026, use session + spread filters, ATR-based stops, read SYMBOL_TRADE_STOPS_LEVEL at runtime, backtest with Every tick, and avoid M1 unless you follow Scalping EA for MT5 rules.
You've built your first MA crossover EA and added risk management. Now it's time to apply that knowledge to a real-world case study: Gold (XAUUSD). Gold is one of the most traded commodities globally β it serves as a safe-haven asset, inflation hedge, and speculative instrument. Building an EA for gold requires symbol-specific adaptations that you won't find in generic forex tutorials. This article walks you through a complete, production-ready Gold EA with spread filters, session filters, and ATR-based stop loss β all based on official MQL5 documentation and best practices from trusted sources.
Why Gold as a Case Study
Gold is an ideal case study because it forces you to:
- Handle symbol-specific properties β Lot size, point value, and stops level differ from forex. You must use SymbolInfoDouble and SymbolInfoInteger instead of hardcoding values.
- Account for volatility β Gold can move $10β$30 within minutes on news. Fixed stop loss in points often fails; ATR-based stops adapt to current volatility.
- Filter by spread and session β Spreads widen during low liquidity or high volatility. The LondonβNew York overlap (13:00β17:00 GMT) typically offers the best liquidity and tightest spreads.
- Validate your EA logic β A real case study teaches you to debug symbol-specific issues (e.g. "Not enough money", invalid stops) that generic EAs don't cover.
By the end of this article you will have a Gold EA that compiles, runs in the Strategy Tester on XAUUSD, and respects all symbol-specific requirements.
Gold-Specific Considerations
| Aspect | Forex (e.g. EURUSD) | Gold (XAUUSD) |
|---|---|---|
| Symbol | Often 5 digits (0.00001) | Often 2 digits (0.01) β SYMBOL_POINT = 0.01 |
| Lot size | Typical min 0.01, step 0.01 | Varies by broker β use SymbolInfoDouble |
| Volatility | Moderate | High β $10β$30 moves in minutes |
| Spread | Usually tight | Can widen significantly on news |
| Pip value | ~$10 per standard lot (EURUSD) | ~$1 per 0.01 move per 0.01 lot (varies) |
Key takeaway: Never hardcode lot size, point value, or stop distance. Always read them from the symbol at runtime:
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double volMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double volStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
Recommended MT5 Settings for Gold EA (2026)
Short answer: Trend gold EAs usually run on H1/H4 with LondonβNY overlap, spread cap 30β80 points (broker-dependent), 2Γ ATR SL, and Every tick backtests. Short-term gold scalping needs tighter spread rules β see Scalping EA for MT5.
| Setting | Trend EA (this guide) | Short-term / scalping |
|---|---|---|
| Timeframe | H1 or H4 | M5βM15 only with spread filter |
| Session | 13:00β17:00 GMT (overlap) | Same; avoid Asian low liquidity |
| Max spread | 40β80 points (tune per broker) | 25β50 points; skip news spikes |
| Stop loss | 2Γ ATR(14) | 1.5β2Γ ATR or fixed with stops level |
| Backtest mode | Every tick | Every tick (required) |
| Mobile | Monitor Demo/live alerts | Design on desktop; verify stops in Journal |
References: MetaTrader 5 Help β Symbols, MQL5 SymbolInfoInteger, community gold EA discussions on MQL5.com.
No-code path: Model MA + session + ATR risk in Strategy Builder β Build EA Without Coding β export and test on XAUUSD. For RSI-only gold mean reversion (no MA), see Gold XAUUSD RSI EA β No Code.
Step 1: Strategy and Adaptations
We adapt the MA crossover strategy from our first EA with Gold-specific rules:
| Component | Choice | Why |
|---|---|---|
| Fast MA | 20-period EMA, Close | Same as first EA β quick reaction to price |
| Slow MA | 50-period EMA, Close | Confirms trend direction |
| Entry | One position at a time; crossover closes opposite | Keeps risk simple |
| Spread filter | Skip trade if spread > max (e.g. 50 points) | Gold spreads widen β avoid bad fills |
| Session filter | Trade only during LondonβNY overlap (13β17 GMT) | Best liquidity, tightest spreads |
| Stop Loss | 2Γ ATR (14) | Gold is volatile β adapt to current volatility |
| Take Profit | 3Γ ATR (14) | Risk-reward ~1.5:1 |
Session times: GMT is used. If your broker uses server time (e.g. GMT+2), adjust the hour range accordingly. Use TimeCurrent() and TimeToStruct() to check the current hour.
Step 2: Symbol Setup
Before coding, ensure XAUUSD is available:
- Open MetaTrader 5 and go to View β Symbols (or press Ctrl+U).
- In the Symbols window, expand Commodities or search for XAUUSD.
- Right-click XAUUSD β Show in Market Watch.
- Add XAUUSD to a chart (e.g. H1) β the EA will attach to this chart.
If XAUUSD is not listed, your broker may use a different name (e.g. GOLD, XAU/USD). Check your broker's symbol specification.
Step 3: Inputs and Handles
We need inputs for MA periods, risk, filters, and session:
#property copyright "AlfaTactix Academy"
#property version "1.00"
input int InpFastPeriod = 20; // Fast MA period
input int InpSlowPeriod = 50; // Slow MA period
input double InpLotSize = 0.01; // Lot size (default 0.01 for gold)
input int InpMagic = 12346; // Magic number
input int InpMaxSpread = 50; // Max spread (points) to allow trade
input int InpSessionStart = 13; // Session start hour (GMT)
input int InpSessionEnd = 17; // Session end hour (GMT)
input double InpAtrMultiplierSL = 2.0; // ATR multiplier for Stop Loss
input double InpAtrMultiplierTP = 3.0; // ATR multiplier for Take Profit
int g_fastHandle = INVALID_HANDLE;
int g_slowHandle = INVALID_HANDLE;
int g_atrHandle = INVALID_HANDLE;
Note: We use 0.01 lot as default for gold β it is a common minimum. The EA will clamp to SYMBOL_VOLUME_MIN and SYMBOL_VOLUME_STEP in code.
Step 4: Spread and Session Filters
Before opening any trade, we check:
1. Spread filter:
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
if(spread > InpMaxSpread) return; // Skip trade β spread too wide
2. Session filter:
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.hour < InpSessionStart || dt.hour >= InpSessionEnd) return;
Step 5: ATR-Based Stop Loss
Gold's volatility requires dynamic stops. We use iATR to get the Average True Range:
g_atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
if(g_atrHandle == INVALID_HANDLE || CopyBuffer(g_atrHandle, 0, 0, 1, atrBuf) < 1)
return; // No ATR data β skip
double atr = atrBuf[0];
double slDist = atr * InpAtrMultiplierSL;
double tpDist = atr * InpAtrMultiplierTP;
// For Buy: SL below entry, TP above
double sl = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - slDist;
double tp = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + tpDist;
// Normalize and respect SYMBOL_TRADE_STOPS_LEVEL
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double minDist = stopsLevel * point;
if(slDist < minDist) slDist = minDist; // Ensure SL is far enough
sl = NormalizeDouble(price - slDist, digits);
tp = NormalizeDouble(price + tpDist, digits);
Step 6: OnInit and OnTick
OnInit: Create handles for iMA (fast, slow) and iATR. Validate inputs and symbol:
int OnInit()
{
if(InpFastPeriod <= 0 || InpSlowPeriod <= 0 || InpFastPeriod >= InpSlowPeriod)
{
Print("Invalid MA periods.");
return(INIT_PARAMETERS_INCORRECT);
}
if(!SymbolSelect(_Symbol, true))
{
Print("Symbol ", _Symbol, " not found. Add to Market Watch.");
return(INIT_FAILED);
}
g_fastHandle = iMA(_Symbol, PERIOD_CURRENT, InpFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_slowHandle = iMA(_Symbol, PERIOD_CURRENT, InpSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
if(g_fastHandle == INVALID_HANDLE || g_slowHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
{
Print("Failed to create indicator handles. Error: ", GetLastError());
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
OnTick: New-bar check β spread filter β session filter β crossover logic β open trade with ATR-based SL/TP. Reuse the logic from Build Your First EA for crossover detection and position management.
Step 7: Compile and Backtest
- Compile in MetaEditor (F7).
- Open Strategy Tester (Ctrl+R).
- Select GoldEA (or your EA name).
- Symbol: XAUUSD.
- Timeframe: H1 (recommended for gold trend strategies).
- Date range: At least 12 months for meaningful backtest.
- Modeling: "Every tick" or "1 minute OHLC" for accuracy.
- Run and check Results, Graph, and Journal tabs.
Summary β What You Built
| Part | Role |
|---|---|
| SymbolInfoDouble/Integer | Read lot limits, point, stops level |
| Spread filter | Skip trade when spread > max |
| Session filter | Trade only during LondonβNY overlap |
| ATR-based SL/TP | Adapt to gold volatility |
| NormalizeDouble | Respect broker stop level |
Full Code
Here is the complete Gold EA. Save as GoldEA.mq5 in MQL5\Experts.
//+------------------------------------------------------------------+
//| GoldEA.mq5 |
//| Gold (XAUUSD) Case Study EA |
//+------------------------------------------------------------------+
#property copyright "AlfaTactix Academy"
#property version "1.00"
input int InpFastPeriod = 20;
input int InpSlowPeriod = 50;
input double InpLotSize = 0.01;
input int InpMagic = 12346;
input int InpMaxSpread = 50;
input int InpSessionStart = 13;
input int InpSessionEnd = 17;
input double InpAtrMultiplierSL = 2.0;
input double InpAtrMultiplierTP = 3.0;
int g_fastHandle = INVALID_HANDLE;
int g_slowHandle = INVALID_HANDLE;
int g_atrHandle = INVALID_HANDLE;
int OnInit()
{
if(InpFastPeriod <= 0 || InpSlowPeriod <= 0 || InpFastPeriod >= InpSlowPeriod)
{
Print("Invalid MA periods.");
return(INIT_PARAMETERS_INCORRECT);
}
if(!SymbolSelect(_Symbol, true))
{
Print("Symbol ", _Symbol, " not found.");
return(INIT_FAILED);
}
g_fastHandle = iMA(_Symbol, PERIOD_CURRENT, InpFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_slowHandle = iMA(_Symbol, PERIOD_CURRENT, InpSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
if(g_fastHandle == INVALID_HANDLE || g_slowHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
{
Print("Failed to create handles. Error: ", GetLastError());
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
if(g_fastHandle != INVALID_HANDLE) IndicatorRelease(g_fastHandle);
if(g_slowHandle != INVALID_HANDLE) IndicatorRelease(g_slowHandle);
if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
}
void OnTick()
{
static datetime lastBar = 0;
datetime currentBar = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBar == lastBar) return;
lastBar = currentBar;
if(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > InpMaxSpread) return; // Spread filter
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.hour < InpSessionStart || dt.hour >= InpSessionEnd) return; // Session filter
double fast[], slow[], atrBuf[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
ArraySetAsSeries(atrBuf, true);
if(CopyBuffer(g_fastHandle, 0, 0, 3, fast) < 3) return;
if(CopyBuffer(g_slowHandle, 0, 0, 3, slow) < 3) return;
if(CopyBuffer(g_atrHandle, 0, 0, 1, atrBuf) < 1) return;
bool buySignal = (fast[1] > slow[1]) && (fast[2] <= slow[2]);
bool sellSignal = (fast[1] < slow[1]) && (fast[2] >= slow[2]);
double atr = atrBuf[0];
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double minDist = MathMax(stopsLevel * point, point);
double volMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double volMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double volStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double lot = MathMax(volMin, MathMin(volMax, InpLotSize));
lot = MathFloor(lot / volStep) * volStep;
lot = NormalizeDouble(lot, 2);
if(buySignal)
{
ClosePositions(POSITION_TYPE_SELL);
if(CountPositions(POSITION_TYPE_BUY) == 0)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double slDist = MathMax(atr * InpAtrMultiplierSL, minDist);
double tpDist = MathMax(atr * InpAtrMultiplierTP, minDist);
double sl = NormalizeDouble(ask - slDist, digits);
double tp = NormalizeDouble(ask + tpDist, digits);
OpenBuy(lot, sl, tp);
}
}
else if(sellSignal)
{
ClosePositions(POSITION_TYPE_BUY);
if(CountPositions(POSITION_TYPE_SELL) == 0)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double slDist = MathMax(atr * InpAtrMultiplierSL, minDist);
double tpDist = MathMax(atr * InpAtrMultiplierTP, minDist);
double sl = NormalizeDouble(bid + slDist, digits);
double tp = NormalizeDouble(bid - tpDist, digits);
OpenSell(lot, sl, tp);
}
}
}
void OpenBuy(double lot, double sl, double tp)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = lot;
req.type = ORDER_TYPE_BUY;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
req.sl = sl;
req.tp = tp;
req.deviation = 20;
req.magic = InpMagic;
if(!OrderSend(req, res)) Print("OpenBuy failed: ", GetLastError());
}
void OpenSell(double lot, double sl, double tp)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = lot;
req.type = ORDER_TYPE_SELL;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
req.sl = sl;
req.tp = tp;
req.deviation = 20;
req.magic = InpMagic;
if(!OrderSend(req, res)) Print("OpenSell failed: ", GetLastError());
}
int CountPositions(ENUM_POSITION_TYPE type)
{
int count = 0;
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) != InpMagic) continue;
if(PositionGetInteger(POSITION_TYPE) != type) continue;
count++;
}
return count;
}
void ClosePositions(ENUM_POSITION_TYPE type)
{
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) != InpMagic) continue;
if(PositionGetInteger(POSITION_TYPE) != type) continue;
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.position = ticket;
req.symbol = _Symbol;
req.volume = PositionGetDouble(POSITION_VOLUME);
req.deviation = 20;
req.magic = InpMagic;
req.type = (type == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
OrderSend(req, res);
}
}
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Symbol not found | XAUUSD not in Market Watch | Add XAUUSD via View β Symbols β Show in Market Watch. |
Invalid stops (10016) | SL/TP too close to price | Increase InpAtrMultiplierSL; enforce SYMBOL_TRADE_STOPS_LEVEL β Error 10016 guide. |
Not enough money (134) | Lot too large for gold margin | Reduce InpLotSize; gold margin is often higher than forex. |
| No trades in backtest | Spread filter or session filter too strict | Increase InpMaxSpread; widen session hours (e.g. 12β18). |
| Requote (10004) | Price moved before fill | Increase req.deviation (e.g. 30β50 for gold). |
Next Steps
- Scalping EA for MT5 β if you move to M5/M15 on gold.
- MT5 Error 10016 β when stops fail on volatile gold ticks.
- Advanced EA: MTF & filters and EA risk management.
- Deploy to Demo first before live.
Bonus: Design this Gold EA in Strategy Builder β MA crossover, ATR SL, spread and session filters β then export MQL5 for XAUUSD. Same logic, zero typing.