Summary: A ready-to-compile MQL4 Expert Advisor for XAUUSD. The EA incorporates ATR-based volatility filters, spread guards, and a preference for Asian session trading, supported by market structure insights from BIS and CME Group research.




Gold EA for XAUUSD – MQL4 Source Code



Developing a robust Expert Advisor for XAUUSD requires a deep understanding of gold's unique market microstructure. Unlike major currency pairs, gold exhibits pronounced sensitivity to macroeconomic shifts, significant reactions to US Dollar movements, and a distinct 24-hour trading pattern with varying liquidity across sessions . The code below provides a foundational yet robust framework for trading XAUUSD on the MetaTrader 4 platform. It is structured to be reliable, adaptable, and to account for the asset's specific characteristics.

EA Concept & Design Philosophy



The core concept of this EA is to capture momentum early in the most liquid periods while protecting capital from erratic moves driven by low-liquidity or news events. It operates on a trend-following logic, primarily using a fast and slow moving average crossover, filtered by volatility.

  • Recommended Timeframe: M15 (15-minute chart).

  • Reason: The M15 timeframe provides a balance between capturing meaningful short-term trends and avoiding the "noise" present on lower timeframes like M1 or M5. Testing has shown that many scalping EAs for gold face issues with quote quality on very low timeframes; the M15 chart offers more reliable signals and is less sensitive to spread spikes .


  • Understanding Gold's Volatility: The Core Filter



    My perspective on trading gold, which I have integrated into this EA, is shaped by recent findings from the Bank for International Settlements (BIS) . In their Quarterly Review, BIS analysts Eren, Krohn, and Todorov highlighted that retail investors, often using leveraged ETFs, have significantly contributed to increased volatility in the gold market . They note that the "rebalancing of levered ETFs and margin-triggered forced liquidations amplify price fluctuations" .

    Therefore, a "one-size-fits-all" approach to risk is inadequate for gold. This EA uses the Average True Range (ATR) not just to set stops and targets, but also as a dynamic filter. If volatility is too high (as measured by a multiple of ATR), the EA will not enter new trades, avoiding the dangerous "chop" that often precedes sharp reversals.

    Asian Session Focus



    Additionally, recent data shows a shift in market structure. Liquidity in the Asian trading session (roughly 6:00 to 18:00 Singapore time) has grown substantially, now representing over a third of global gold futures trading . The EA includes an optional input to confine trading to these hours. The logic is that a more liquid market provides better fills, tighter spreads, and more predictable price action, which are crucial for the EA's performance.

    MQL4 Source Code



    ``cpp
    //+------------------------------------------------------------------+
    //| Gold_EA_XAUUSD |
    //| Version 1.0 (Compile-ready MQL4) |
    //| Based on BIS & World Gold Council Data |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2026, FXEAR.com"
    #property link "https://www.fxear.com"
    #property version "1.00"
    #property strict

    //+------------------------------------------------------------------+
    //| Input Parameters with Detailed Comments |
    //+------------------------------------------------------------------+

    // --- Risk & Money Management ---
    input double RiskPercent = 1.5; // Risk per trade (% of account balance)
    // Range: 0.1 - 5.0, Default: 1.5
    // Sets position size based on the stop loss distance.

    input double MaxLotSize = 1.0; // Maximum allowed lot size
    // Range: 0.01 - 10.0, Default: 1.0
    // Caps the position size to prevent over-exposure.

    input int MagicNumber = 20260626; // EA's unique Magic Number
    // Range: Any integer, Default: 20260626
    // Used to identify trades opened by this EA.

    input int Slippage = 30; // Max slippage in points
    // Range: 10 - 100, Default: 30
    // Protects against poor fills during fast moves.

    // --- Trading Logic Filters ---
    input int FastMAPeriod = 8; // Fast Moving Average period
    // Range: 3 - 20, Default: 8
    // The faster line in the crossover strategy.

    input int SlowMAPeriod = 21; // Slow Moving Average period
    // Range: 13 - 50, Default: 21
    // The slower line in the crossover strategy.

    input int ATRPeriod = 14; // Period for ATR indicator
    // Range: 7 - 21, Default: 14
    // Used to gauge market volatility.

    input double ATRMultiplierSL = 1.5; // ATR multiplier for Stop Loss
    // Range: 1.0 - 3.0, Default: 1.5
    // Sets the SL distance relative to current price.

    input double ATRMultiplierTP = 2.0; // ATR multiplier for Take Profit
    // Range: 1.5 - 4.0, Default: 2.0
    // A fixed RR of roughly 1:1.3.

    input double MaxATRFilter = 3.0; // Max volatility filter (in ATR units)
    // Range: 1.0 - 5.0, Default: 3.0
    // If ATR is above this level, no new trades are taken.
    // This is my unique filter to avoid erratic moves.

    // --- Spread & Time Controls ---
    input int MaxSpreadPoints = 50; // Maximum allowed spread in points
    // Range: 10 - 100, Default: 50
    // Prevents the EA from opening trades when spread is wide.

    input bool UseAsianSession = true; // Limit trading to Asian session?
    // Range: True/False, Default: True
    // Focuses trading on the most liquid window .

    input int SessionStartHour = 6; // Start of Asian session (broker's time)
    // Range: 0 - 23, Default: 6 (Singapore time UTC+8)

    input int SessionEndHour = 17; // End of Asian session (broker's time)
    // Range: 0 - 23, Default: 17 (Singapore time UTC+8)

    // --- Trailing Stop ---
    input bool UseTrailing = true; // Enable trailing stop
    // Range: True/False, Default: True
    // Locks in profit as the price moves favorably.

    input double TrailStart = 0.6; // Trailing starts when profit is TrailStart * ATR
    // Range: 0.3 - 1.0, Default: 0.6
    // Activates the trailing mechanism.

    input double TrailDistance = 0.3; // Distance for trailing stop (as a factor of ATR)
    // Range: 0.2 - 0.8, Default: 0.3

    //+------------------------------------------------------------------+
    //| Global Variables |
    //+------------------------------------------------------------------+
    double currentATR;
    int tradeTicket;
    bool isTradingAllowed;

    //+------------------------------------------------------------------+
    //| Expert initialization function |
    //+------------------------------------------------------------------+
    int OnInit()
    {
    // Check if ATR is available
    if(iATR(_Symbol, PERIOD_CURRENT, ATRPeriod, 0) == 0)
    {
    Print("Error initializing ATR. Check symbol and period.");
    return(INIT_FAILED);
    }

    Print("Gold EA for XAUUSD initialized successfully.");
    Print("Based on analysis of BIS data and World Gold Council reports.");
    return(INIT_SUCCEEDED);
    }

    //+------------------------------------------------------------------+
    //| Expert deinitialization function |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
    Print("Gold EA deinitialized. Reason: ", reason);
    }

    //+------------------------------------------------------------------+
    //| Expert tick function |
    //+------------------------------------------------------------------+
    void OnTick()
    {
    // Refresh rates to ensure Ask/Bid are updated
    RefreshRates();

    // --- Core Conditions to Skip Trading ---
    if(!IsNewBar()) return; // Wait for new bar to avoid re-entry.
    if(!IsTradingTimeAllowed()) return; // Check if we are within the allowed session.
    if(IsSpreadTooHigh()) return; // Check for high spreads.
    if(IsVolatilityTooHigh()) return; // My unique volatility filter.

    // Check for existing open positions to avoid duplicates
    if(CountOpenPositions() > 0)
    {
    ManageTrailingStop(); // Trailing for existing positions.
    return; // Exit if a position is open.
    }

    // --- Trading Signal Logic ---
    double fastMA = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
    double slowMA = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
    double prevFast = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
    double prevSlow = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);

    if(fastMA == 0 || slowMA == 0) return; // Safety check

    // --- Entry Logic: Bullish Crossover ---
    if(prevFast <= prevSlow && fastMA > slowMA)
    {
    OpenBuyOrder();
    }
    // --- Entry Logic: Bearish Crossover ---
    else if(prevFast >= prevSlow && fastMA < slowMA)
    {
    OpenSellOrder();
    }
    }

    //+------------------------------------------------------------------+
    //| Signal & Filter Functions |
    //+------------------------------------------------------------------+

    // My Exclusive Insight: Volatility Filter
    // Based on the BIS research on retail flow amplification, I concluded
    // that gold's volatility spikes are often "noise" driven by leverage.
    // Entering during these spikes, even with a correct trend, can result
    // in premature stop-outs. This filter is designed to sidestep that.
    bool IsVolatilityTooHigh()
    {
    // Calculate the current ATR value
    currentATR = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod, 0);

    // If the ATR is above our defined threshold, we skip trading.
    // For example, if ATR=500 and MaxATRFilter=3.0, we skip if price moves > 1500 points.
    if(currentATR > (MaxATRFilter iATR(_Symbol, PERIOD_CURRENT, ATRPeriod, 1)))
    return true;

    return false;
    }

    bool IsSpreadTooHigh()
    {
    int spread = int((Ask - Bid) / Point);
    if(spread >= MaxSpreadPoints) return true;
    return false;
    }

    bool IsTradingTimeAllowed()
    {
    if(!UseAsianSession) return true;
    datetime currentTime = TimeCurrent();
    int currentHour = TimeHour(currentTime);

    // Focus on the highly liquid Asian window
    if(currentHour >= SessionStartHour && currentHour < SessionEndHour)
    return true;

    return false;
    }

    bool IsNewBar()
    {
    static datetime lastBarTime = 0;
    datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
    if(currentBarTime != lastBarTime)
    {
    lastBarTime = currentBarTime;
    return true;
    }
    return false;
    }

    //+------------------------------------------------------------------+
    //| Trading Functions |
    //+------------------------------------------------------------------+

    void OpenBuyOrder()
    {
    double slDistance = currentATR
    ATRMultiplierSL;
    double tpDistance = currentATR ATRMultiplierTP;
    double slPrice = NormalizeDouble(Ask - slDistance, _Digits);
    double tpPrice = NormalizeDouble(Ask + tpDistance, _Digits);

    double lotSize = CalculateLotSize(slDistance);

    // Check if the lot size is valid and within the limit
    if(lotSize <= 0 || lotSize > MaxLotSize) return;

    tradeTicket = OrderSend(Symbol(), OP_BUY, lotSize, Ask, Slippage, slPrice, tpPrice, "Gold Buy", MagicNumber, 0, Green);
    if(tradeTicket > 0)
    Print("BUY order opened. Ticket: ", tradeTicket);
    else
    Print("BUY order failed. Error: ", GetLastError());
    }

    void OpenSellOrder()
    {
    double slDistance = currentATR
    ATRMultiplierSL;
    double tpDistance = currentATR ATRMultiplierTP;
    double slPrice = NormalizeDouble(Bid + slDistance, _Digits);
    double tpPrice = NormalizeDouble(Bid - tpDistance, _Digits);

    double lotSize = CalculateLotSize(slDistance);

    if(lotSize <= 0 || lotSize > MaxLotSize) return;

    tradeTicket = OrderSend(Symbol(), OP_SELL, lotSize, Bid, Slippage, slPrice, tpPrice, "Gold Sell", MagicNumber, 0, Red);
    if(tradeTicket > 0)
    Print("SELL order opened. Ticket: ", tradeTicket);
    else
    Print("SELL order failed. Error: ", GetLastError());
    }

    //+------------------------------------------------------------------+
    //| Core Money Management & Position Functions |
    //+------------------------------------------------------------------+

    double CalculateLotSize(double stopLossDistanceInPoints)
    {
    double riskAmount = AccountBalance()
    (RiskPercent / 100.0);
    double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
    double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);

    // Calculate lot size based on risk and stop loss distance
    double lotSize = riskAmount / (stopLossDistanceInPoints (tickValue / tickSize));
    lotSize = NormalizeDouble(lotSize, 2); // Standardize to 2 decimal places

    // Check min/max lot constraints
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);

    if(lotSize < minLot) lotSize = minLot;
    if(lotSize > maxLot) lotSize = maxLot;

    return lotSize;
    }

    int CountOpenPositions()
    {
    int count = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
    count++;
    }
    }
    return count;
    }

    //+------------------------------------------------------------------+
    //| Trailing Stop Management |
    //+------------------------------------------------------------------+

    void ManageTrailingStop()
    {
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
    if(OrderMagicNumber() != MagicNumber || OrderSymbol() != _Symbol) continue;

    double atrValue = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod, 0);
    if(atrValue <= 0) return;

    // Determine the current profit in points
    int profitPoints;
    if(OrderType() == OP_BUY)
    {
    profitPoints = int((Bid - OrderOpenPrice()) / Point);
    if(profitPoints > 0)
    {
    // Check if profit is enough to start trailing
    if(profitPoints > TrailStart
    atrValue / Point)
    {
    double newSL = NormalizeDouble(Bid - TrailDistance atrValue, _Digits);
    if(newSL > OrderStopLoss() && newSL > OrderOpenPrice())
    {
    if(OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, Blue))
    Print("Trailing stop updated for Buy: ", newSL);
    }
    }
    }
    }
    else if(OrderType() == OP_SELL)
    {
    profitPoints = int((OrderOpenPrice() - Ask) / Point);
    if(profitPoints > 0)
    {
    if(profitPoints > TrailStart
    atrValue / Point)
    {
    double newSL = NormalizeDouble(Ask + TrailDistance * atrValue, _Digits);
    if(newSL < OrderStopLoss() || OrderStopLoss() == 0)
    {
    if(OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, Red))
    Print("Trailing stop updated for Sell: ", newSL);
    }
    }
    }
    }
    break; // Only manage the first open order
    }
    }
    //+------------------------------------------------------------------+
    ``

    Reference:
  • Bank for International Settlements (BIS). "BIS warns: retail gold buying surges 3x, Wall Street accelerates selling, market volatility risk rises," 2026.

  • CME Group. "Asian trading session liquidity in gold futures exceeds one-third of global volume," 2025.


  • 本文首发于FXEAR.com,原创内容,未经授权禁止转载。

    Disclaimer: Trading financial markets involves significant risk. This Expert Advisor is provided for educational and informational purposes only. It is not financial advice. Past performance does not guarantee future results. The author and FXEAR.com are not responsible for any financial losses incurred from using this EA. Always test thoroughly on a demo account before live trading.