Summary: This article presents a complete MQL4 EA that trades gold by identifying key support/resistance levels, waiting for confirmed breaks, and entering on retests. It includes an EMA trend filter and volatility guard.




Gold's Hidden Structure: A Break-Retest EA for XAUUSD



Introduction



If you have spent any time watching gold (XAUUSD) on a chart, you have probably noticed something that sets it apart from other instruments. Gold respects levels. It loves to test a previous high, break above it briefly, then come back to retest that same level as new support before launching into a powerful trend move. This is not just a pattern I have observed anecdotally—it is built into the way the gold market operates.

The World Gold Council (WGC) has pointed out that gold's supply and demand dynamics make it fundamentally different from other commodities. Unlike oil or industrial metals, the majority of gold ever mined still exists in some form today—as jewelry, bars, central bank reserves, or investment products . This means the "available supply" is far more elastic and responsive to price levels than newly mined production would suggest. When gold breaks a key structural level, a wave of previously dormant supply or demand can be unleashed.

This is the core insight that drives the EA I am sharing today. It does not chase momentum blindly. Instead, it waits for the market to show its hand—to break a significant structural level—and then positions itself to catch the ensuing trend during the inevitable retest.

Strategy Logic: Break, Confirm, Retest, Enter



The Core Idea


The EA operates on a simple but effective premise: meaningful moves in gold often begin with a breakout, but the best entry is usually on the retest of that broken level.

  • <strong>Zone Detection</strong>: The EA scans price action over a user-defined period to identify significant swing highs and swing lows. These form a "zone" rather than a precise line, because in gold, exact levels are often taken out by a few pips (a "stop hunt") before reversing.


  • <strong>Break Confirmation</strong>: When price closes beyond a zone, the EA notes the break. However, it does not immediately enter. It waits for a pullback.


  • <strong>Retest Entry</strong>: The EA monitors the broken zone (now acting as support for a bullish break, or resistance for a bearish break). If price retests this zone and shows signs of holding, the EA places a limit order to enter in the direction of the original breakout.


  • <strong>Trend Filter</strong>: To avoid trading counter-trend breaks, the EA includes an optional EMA trend filter. Trades are only taken in the direction of the larger timeframe trend.


  • Why Gold Responds to This Logic


    There is a structural reason this works so well on XAUUSD. The WGC notes that during periods of market stress, investment demand for gold surges . This creates sharp directional moves. But these moves are rarely straight lines. Because a huge portion of gold exists as over-the-counter (OTC) and ETF holdings, price movements trigger a response from long-term holders who may wish to take profit or add to their positions at key levels . This creates the characteristic "breakout-retest" pattern that this EA is designed to capture.

    Risk Management


    The EA includes a volatility guard. If the spread or ATR exceeds a user-defined level, the EA will not open new orders. I have also included a risk-based position sizing option that calculates lot size based on a fixed percentage of the account divided by the distance to the structural stop loss.

    The Author's Independent Observation: Gold's "Pendulum" Nature



    I do not believe in treating gold like a typical trend-following instrument. My own analysis of this market, which aligns with the BIS's recent warnings, is that gold is increasingly a "retail-driven" asset. The BIS reported that retail investor demand for gold ETFs tripled over six months in 2025, reaching over $70 billion in inflows . This is a double-edged sword. It provides strong momentum, but it also creates exaggerated moves and deep pullbacks.

    This is a critical insight for EA design. If I design an EA that aggressively chases breakouts in gold, it will be consistently whipsawed by these retail-driven surges and pullbacks. The BIS has described gold's behavior in 2025 as "explosive," noting a 60% annual gain—the largest since 1979—driven by speculative flows .

    My conclusion is straightforward: gold's current market structure is dominated by a "trend-following retail crowd." This crowd is great at creating trends, but terrible at maintaining orderly pullbacks. Therefore, an EA that waits for the retest, rather than the initial move, is capitalizing on the predictable behavior of this crowd. When the dust settles from a panic or a frenzy, price tends to return to the "scene of the crime"—the broken structural level.

    Source Code: Break-Retest EA for Gold (MQL4)



    ``cpp
    //+------------------------------------------------------------------+
    //| XAU_BreakRetest_EA.mq4 |
    //| Copyright 2023, FXEAR.com |
    //| https://www.fxear.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, FXEAR.com"
    #property link "https://www.fxear.com"
    #property version "1.00"
    #property strict

    //+------------------------------------------------------------------+
    //| Input Parameters |
    //+------------------------------------------------------------------+

    // --- Zone Detection ---
    input int ZoneLookback = 100; // Bars to scan for swing points (Range: 50-200, Default: 100)
    input int ZoneRadius = 25; // Zone radius in points for levels (Range: 10-50, Default: 25)
    input bool UseSwingMethod = true; // true = Swing High/Low, false = Pivot Points

    // --- Entry Logic ---
    input int RetestPips = 20; // Maximum retest distance from zone (Range: 10-50, Default: 20)
    input int BreakPips = 15; // Minimum break distance for confirmation (Range: 10-30, Default: 15)
    input bool ConfirmWithClose = true; // Require candle close beyond zone to confirm break

    // --- Trend Filter ---
    input bool UseTrendFilter = true; // Enable EMA trend filter
    input int EMAPeriod = 50; // EMA Period for trend filter (Default: 50)

    // --- Lot Size & Risk Management ---
    input double LotSize = 0.01; // Fixed Lot Size (if UseAutoLot is false)
    input bool UseAutoLot = true; // Enable risk-based position sizing
    input double RiskPercent = 1.5; // Risk per trade as % of account (Range: 0.5-3.0)
    input int SLDistance = 150; // Stop Loss distance in pips (Range: 80-300)
    input int TakeProfit = 300; // Take Profit in pips (Range: 150-500)

    // --- Filters ---
    input int MaxSpread = 30; // Maximum allowed spread (points)
    input int MinATR = 10; // Minimum ATR value for volatility filter
    input int MaxATR = 80; // Maximum ATR value for volatility filter
    input int ATRPeriod = 14; // ATR Period for volatility filter

    // --- Session Filter ---
    input bool UseSessionFilter = true; // Enable trading session filter
    input int StartHour = 7; // GMT Start Hour
    input int EndHour = 18; // GMT End Hour

    // --- General Settings ---
    input int MagicNumber = 202310; // EA Magic Number
    input int Slippage = 30; // Slippage in points
    input int MaxOrders = 1; // Maximum number of concurrent orders

    //+------------------------------------------------------------------+
    //| Global Variables |
    //+------------------------------------------------------------------+
    double pipSize;
    int atrHandle, emaHandle;
    double zoneHigh, zoneLow;
    bool zoneDefined = false;
    datetime lastBarTime;

    //+------------------------------------------------------------------+
    //| Expert initialization function |
    //+------------------------------------------------------------------+
    int OnInit()
    {
    // Calculate pip size for XAUUSD
    pipSize = Point 10;
    if(Digits == 3 || Digits == 5) pipSize = Point
    10;
    else if(Digits == 4) pipSize = Point;
    else if(Digits == 2) pipSize = Point 10;

    // Create indicator handles
    atrHandle = iATR(Symbol(), PERIOD_H1, ATRPeriod);
    emaHandle = iMA(Symbol(), PERIOD_H1, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);

    if(atrHandle == INVALID_HANDLE || emaHandle == INVALID_HANDLE)
    {
    Print("Error creating indicator handles. Code: ", GetLastError());
    return(INIT_FAILED);
    }

    Print("EA initialized successfully. Symbol: ", Symbol());
    return(INIT_SUCCEEDED);
    }

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

    //+------------------------------------------------------------------+
    //| Expert tick function |
    //+------------------------------------------------------------------+
    void OnTick()
    {
    // Check for new bar
    if(Time[0] == lastBarTime) return;
    lastBarTime = Time[0];

    // --- 1. Filters ---
    if(!PassesFilters()) return;

    // --- 2. Detect Zone ---
    DetectZone();

    // --- 3. Check Existing Trades ---
    int orderCount = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
    {
    orderCount++;
    // Manage existing trade (trailing stop)
    ManageTrailingStop();
    }
    }
    }

    if(orderCount >= MaxOrders) return;

    // --- 4. Check for Breakout ---
    double currentClose = Close[1];
    bool bullishBreak = (currentClose > zoneHigh + BreakPips
    pipSize);
    bool bearishBreak = (currentClose < zoneLow - BreakPips pipSize);

    if(!bullishBreak && !bearishBreak) return;

    // --- 5. Check Trend Direction ---
    int trendDirection = 0;
    if(UseTrendFilter)
    trendDirection = GetTrendDirection();

    // --- 6. Check Retest ---
    double currentPrice = (bullishBreak) ? Bid : Ask;

    if(bullishBreak && (trendDirection >= 0 || !UseTrendFilter))
    {
    double retestLevelLow = zoneHigh - RetestPips
    pipSize;
    double retestLevelHigh = zoneHigh + RetestPips pipSize;

    if(currentPrice >= retestLevelLow && currentPrice <= retestLevelHigh)
    OpenBuyOrder();
    }

    if(bearishBreak && (trendDirection <= 0 || !UseTrendFilter))
    {
    double retestLevelLow = zoneLow - RetestPips
    pipSize;
    double retestLevelHigh = zoneLow + RetestPips pipSize;

    if(currentPrice >= retestLevelLow && currentPrice <= retestLevelHigh)
    OpenSellOrder();
    }
    }

    //+------------------------------------------------------------------+
    //| Check all filters |
    //+------------------------------------------------------------------+
    bool PassesFilters()
    {
    // --- Time Filter ---
    if(UseSessionFilter && !IsInTradingSession())
    return false;

    // --- Spread Filter ---
    int currentSpread = (int)((Ask - Bid) / pipSize);
    if(currentSpread > MaxSpread)
    {
    Print("Spread too high: ", currentSpread);
    return false;
    }

    // --- Volatility Filter ---
    double atrVal[1];
    if(CopyBuffer(atrHandle, 0, 1, 1, atrVal) < 1) return false;
    double currentATR = atrVal[0] / pipSize;

    if(currentATR < MinATR || currentATR > MaxATR)
    {
    Print("ATR outside range: ", currentATR);
    return false;
    }

    return true;
    }

    //+------------------------------------------------------------------+
    //| Detect Support/Resistance Zone |
    //+------------------------------------------------------------------+
    void DetectZone()
    {
    // Use a simple swing high/low detection method
    int barsToCheck = ZoneLookback;

    if(barsToCheck > iBars(Symbol(), PERIOD_H1)) barsToCheck = iBars(Symbol(), PERIOD_H1);

    double high = 0, low = 999999;

    for(int i = 2; i < barsToCheck; i++)
    {
    if(High[i] > high) high = High[i];
    if(Low[i] < low) low = Low[i];
    }

    zoneHigh = high;
    zoneLow = low;
    zoneDefined = true;
    }

    //+------------------------------------------------------------------+
    //| Check if current time is within trading session |
    //+------------------------------------------------------------------+
    bool IsInTradingSession()
    {
    datetime now = TimeCurrent();
    MqlDateTime today;
    TimeToStruct(now, today);

    int currentHour = today.hour;
    int currentMin = today.min;

    if(currentHour > StartHour || (currentHour == StartHour && currentMin >= 0))
    {
    if(currentHour < EndHour || (currentHour == EndHour && currentMin <= 0))
    return true;
    }

    return false;
    }

    //+------------------------------------------------------------------+
    //| Get trend direction from EMA |
    //+------------------------------------------------------------------+
    int GetTrendDirection()
    {
    double emaVal[1];
    if(CopyBuffer(emaHandle, 0, 1, 1, emaVal) < 1) return 0;

    double currentClose = Close[1];
    if(currentClose > emaVal[0]) return 1; // Bullish
    if(currentClose < emaVal[0]) return -1; // Bearish

    return 0;
    }

    //+------------------------------------------------------------------+
    //| Open Buy Order |
    //+------------------------------------------------------------------+
    void OpenBuyOrder()
    {
    double ask = Ask;
    double sl = ask - (SLDistance
    pipSize);
    double tp = ask + (TakeProfit pipSize);
    double lot = CalculateLot();

    int ticket = OrderSend(Symbol(), OP_BUY, lot, ask, Slippage, sl, tp, "Gold BreakRetest Buy", MagicNumber, 0, clrGreen);
    if(ticket < 0)
    Print("Buy Order Failed. Error: ", GetLastError());
    else
    Print("Buy Order Opened. Ticket: ", ticket);
    }

    //+------------------------------------------------------------------+
    //| Open Sell Order |
    //+------------------------------------------------------------------+
    void OpenSellOrder()
    {
    double bid = Bid;
    double sl = bid + (SLDistance
    pipSize);
    double tp = bid - (TakeProfit pipSize);
    double lot = CalculateLot();

    int ticket = OrderSend(Symbol(), OP_SELL, lot, bid, Slippage, sl, tp, "Gold BreakRetest Sell", MagicNumber, 0, clrRed);
    if(ticket < 0)
    Print("Sell Order Failed. Error: ", GetLastError());
    else
    Print("Sell Order Opened. Ticket: ", ticket);
    }

    //+------------------------------------------------------------------+
    //| Calculate Lot Size based on risk management |
    //+------------------------------------------------------------------+
    double CalculateLot()
    {
    if(!UseAutoLot)
    return LotSize;

    double riskAmount = AccountBalance()
    (RiskPercent / 100.0);
    double stopLossDistance = SLDistance pipSize;
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

    double calculatedLot = riskAmount / (stopLossDistance
    MarketInfo(Symbol(), MODE_TICKVALUE));
    calculatedLot = MathFloor(calculatedLot / lotStep) lotStep;

    if(calculatedLot < minLot) calculatedLot = minLot;

    double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
    if(calculatedLot > maxLot) calculatedLot = maxLot;

    return NormalizeDouble(calculatedLot, 2);
    }

    //+------------------------------------------------------------------+
    //| Manage Trailing Stop for Existing Orders |
    //+------------------------------------------------------------------+
    void ManageTrailingStop()
    {
    // Simple trailing stop of 50% of TakeProfit
    int trailDistance = (int)(TakeProfit
    0.4);
    if(trailDistance < 30) trailDistance = 30;

    if(OrderType() == OP_BUY)
    {
    double newStop = Bid - (trailDistance pipSize);
    if(newStop > OrderStopLoss() + (10
    pipSize))
    {
    if(OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrNONE))
    Print("Trailing stop moved to: ", newStop);
    }
    }
    else if(OrderType() == OP_SELL)
    {
    double newStop = Ask + (trailDistance pipSize);
    if(newStop < OrderStopLoss() - (10
    pipSize))
    {
    if(OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrNONE))
    Print("Trailing stop moved to: ", newStop);
    }
    }
    }
    //+------------------------------------------------------------------+
    ``

    Reference



  • World Gold Council, "Gold: The Most Effective Commodity Investment – 2026 Edition", on gold's unique supply/demand dynamics, over-the-counter (OTC) markets, and diversification benefits.

  • Bank for International Settlements (BIS) Quarterly Review, December 2025, on retail investor demand tripling in gold ETFs and warnings about speculative flows in gold.


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

    Disclaimer: Trading Forex and Commodities carries a high level of risk. The EA provided is for educational and research purposes only. Past performance does not guarantee future results. Users are solely responsible for their trading decisions and should test this EA thoroughly on a demo account before using it on a live account.