On this page
Back to tutorials

XAUUSD Gold EA in MQL5 (2026) | ATR, Spread Filter, Session Rules

Build a practical Gold (XAUUSD) Expert Advisor for MetaTrader 5: spread limits, session filters, ATR-based stops, and robust execution logic with clear MQL5 steps.

πŸ“– 36 min read

πŸ“ 7,200 words

🏷️ MQL5 & Expert Advisors

Share this article:

Want to build a no-code strategy right now?

Create your free account in seconds and start building immediately.


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

AspectForex (e.g. EURUSD)Gold (XAUUSD)
SymbolOften 5 digits (0.00001)Often 2 digits (0.01) β€” SYMBOL_POINT = 0.01
Lot sizeTypical min 0.01, step 0.01Varies by broker β€” use SymbolInfoDouble
VolatilityModerateHigh β€” $10–$30 moves in minutes
SpreadUsually tightCan 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:

ComponentChoiceWhy
Fast MA20-period EMA, CloseSame as first EA β€” quick reaction to price
Slow MA50-period EMA, CloseConfirms trend direction
EntryOne position at a time; crossover closes oppositeKeeps risk simple
Spread filterSkip trade if spread > max (e.g. 50 points)Gold spreads widen β€” avoid bad fills
Session filterTrade only during London–NY overlap (13–17 GMT)Best liquidity, tightest spreads
Stop Loss2Γ— ATR (14)Gold is volatile β€” adapt to current volatility
Take Profit3Γ— 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:

  1. Open MetaTrader 5 and go to View β†’ Symbols (or press Ctrl+U).
  2. In the Symbols window, expand Commodities or search for XAUUSD.
  3. Right-click XAUUSD β†’ Show in Market Watch.
  4. 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

  1. Compile in MetaEditor (F7).
  2. Open Strategy Tester (Ctrl+R).
  3. Select GoldEA (or your EA name).
  4. Symbol: XAUUSD.
  5. Timeframe: H1 (recommended for gold trend strategies).
  6. Date range: At least 12 months for meaningful backtest.
  7. Modeling: "Every tick" or "1 minute OHLC" for accuracy.
  8. Run and check Results, Graph, and Journal tabs.

Summary β€” What You Built

PartRole
SymbolInfoDouble/IntegerRead lot limits, point, stops level
Spread filterSkip trade when spread > max
Session filterTrade only during London–NY overlap
ATR-based SL/TPAdapt to gold volatility
NormalizeDoubleRespect 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

SymptomCauseFix
Symbol not foundXAUUSD not in Market WatchAdd XAUUSD via View β†’ Symbols β†’ Show in Market Watch.
Invalid stops (130)SL/TP too close to priceIncrease InpAtrMultiplierSL or check SYMBOL_TRADE_STOPS_LEVEL.
Not enough money (134)Lot too large for gold marginReduce InpLotSize; gold margin is often higher than forex.
No trades in backtestSpread filter or session filter too strictIncrease InpMaxSpread; widen session hours (e.g. 12–18).
Requote (10004)Price moved before fillIncrease 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.

Build your no-code trading strategy now β€” free

Create your account and start building a complete no-code strategy right now. Add indicators, filters, and risk rules, then export MQL5 in minutes.

Frequently Asked Questions

Gold (XAUUSD) represents one troy ounce of gold in USD. It has higher volatility, wider spreads during news, and different point values. SYMBOL_POINT is typically 0.01 for gold. Lot size and margin requirements differ from forex β€” always use SymbolInfoDouble() to read broker limits.

Gold spreads vary by broker and session. Typical spreads range from 20–50 points in normal conditions. Use SymbolInfoInteger(symbol, SYMBOL_SPREAD) and compare to a max threshold (e.g. 50–100 points). Avoid trading during high volatility when spreads widen.

Gold can move $10–$30 within minutes on news. A fixed stop loss in points may be too tight (causing frequent stops) or too wide (large losses). ATR adapts to current volatility β€” e.g. 2Γ— ATR gives the stop room to breathe.

H1 or H4 are common for trend-following strategies. M15 is used for shorter-term. Gold is volatile β€” avoid M1 unless you have a scalping strategy with tight spreads and session filters.

Press Ctrl+U (or View β†’ Symbols). In the Symbols window, find "Commodities" or search for "XAUUSD". Right-click the symbol and select "Show in Market Watch". Add it to your chart before running the EA.

Yes. The logic (MA crossover, spread filter, session filter, ATR) works for any symbol. Change the chart symbol or hardcode a different symbol in the EA. For forex, adjust spread thresholds and ATR multiplier accordingly.

Build your no-code strategy now β€” free

Create Free Account