Build an EA for Gold (XAUUSD) in MQL5 β A Complete Case Study
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);
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 (130) | SL/TP too close to price | Increase InpAtrMultiplierSL or check SYMBOL_TRADE_STOPS_LEVEL. |
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
Extend this EA with multi-timeframe filters and trailing stops. Before going live, deploy to Demo first and backtest thoroughly.
Bonus Tip: Want to design this Gold EA visually? Try AlfaTactix Strategy Builder free β add MA crossover, ATR-based SL, spread and session filters with a visual interface, then export MQL5 for XAUUSD. Same logic, zero typing.