Summary: This article provides an MQL4 Expert Advisor for XAUUSD that integrates a news-event blackout filter. Designed for traders who want to avoid slippage and erratic moves during major data releases.




The News Filter EA for XAUUSD: A Practical Approach to Event-Risk Management



Introduction



I have always found it curious that so many EAs, particularly those marketed for gold, completely ignore the calendar. They run their trend-following or mean-reversion logic as if every moment in the market is created equal. Anyone who has traded XAUUSD through a US Non-Farm Payroll (NFP) release knows this is far from the truth. The price can gap, spreads can widen to over 50 points, and the most sophisticated algorithm can be rendered helpless by a single data point.

This EA is my answer to that problem. It is not a "holy grail" or a complex predictive engine. It is a practical tool that does one thing well: it stops the EA from trading during high-impact news events. This is a "cold" approach, but in my experience, avoiding a disaster is more valuable than capturing a volatile move that can just as easily turn against you.

Why Focus on News Events for Gold?



Gold is particularly sensitive to US economic data. The BIS recently highlighted that gold has exhibited "explosive behaviour," trading in tandem with equities in a way not seen in 50 years . The report noted that retail investors are piling into gold ETFs, and the market structure is currently fragile .

The World Gold Council also confirms that gold is an exceptionally liquid market, with OTC volumes averaging US$180bn per day . However, this liquidity can vanish in milliseconds during a news event. The spread widens as market makers pull their orders, and the price jumps to a new level without trading through the intermediate prices. This is known as slippage, and it is the primary killer of automated strategies during news.

The Strategy: "Do Nothing" is a Deliberate Choice



Many developers focus on how to trade the news. I focus on how to survive it. This EA is built on a simple premise: if the market is about to be hit by a known volatility shock, do not trade. Discretion is the better part of valour.

The logic is straightforward:

  • <strong>Identify the Blackout Period</strong>: The EA uses a hard-coded list of event times. This is not a dynamic web-based calendar. I chose this for reliability and speed. A web call can fail, but a local calculation is instantaneous.

  • <strong>Block New Trades</strong>: During the blackout period, the EA's OnTick() function returns immediately, preventing any new orders.

  • <strong>Cancel Pending Orders</strong>: If the EA uses limit or stop orders, these are deleted to prevent them from being triggered by a sudden spike.

  • <strong>Adjust Open Positions (Optional)</strong>: I have included a parameter to either ignore open positions or move them to breakeven. I usually prefer to "do nothing" to open positions unless they are very close to the take profit.


  • I borrowed the idea of a "blackout flag" from common practices discussed in the community, but I was careful to implement the timer logic independently of the bar updates . This is critical. If you check the news flag only on a new bar, you might miss a news event that occurs mid-bar and leaves your EA unprotected for the rest of the session.

    The Author's Perspective: "Trading Time vs. News Time"



    Most traders obsess over trading sessions—Asian, London, New York. I have a slightly different view. It is not the "session" that matters as much as the "event window."

    For example, the London session is generally more volatile than the Asian session, but that is a generalized historical average. An NFP release at 8:30 AM EST will generate volatility regardless of whether it is the start of the London session or not. Therefore, I designed this EA to be "time-zone agnostic." It uses the broker's server time. You, the user, must set the hours. The EA does not care if it is 2 PM or 2 AM; if you tell it to block, it blocks.

    This is my independent contribution: I prioritize the "news time" over the "session time". I do not use a traditional session filter. Instead, I use a "news time" filter that acts as a hard stop on activity, effectively creating a "do not disturb" mode.

    Code: News Filter EA for XAUUSD



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

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

    // --- Risk Management ---
    input double LotSize = 0.01; // Base Lot Size
    input double RiskPercent = 1.5; // Risk % of Equity (If UseAutoLot = true)
    input bool UseAutoLot = true; // Enable Auto Lot Sizing
    input int StopLossPips = 300; // Stop Loss in Pips
    input int TakeProfitPips = 600; // Take Profit in Pips

    // --- Strategy Filters ---
    input int FastMAPeriod = 20; // Fast MA (Trend)
    input int SlowMAPeriod = 50; // Slow MA (Trend)
    input int ATRPeriod = 14; // ATR Period
    input int VolatilityFilterMin = 20; // Minimum ATR Value (Points)
    input int VolatilityFilterMax = 60; // Maximum ATR Value (Points)

    // --- News Filter Settings ---
    input bool EnableNewsFilter = true; // Master Switch for News Filter
    input int NewsStartHour = 12; // Blackout Start Hour (Broker Time)
    input int NewsStartMinute = 25; // Blackout Start Minute
    input int NewsEndHour = 14; // Blackout End Hour (Broker Time)
    input int NewsEndMinute = 35; // Blackout End Minute
    input bool CancelPendingOnNews = true; // Delete pending orders during blackout

    // --- General ---
    input int MagicNumber = 20250607; // EA Magic Number
    input int Slippage = 30; // Slippage in Points
    input bool UseBreakEven = true; // Enable Breakeven
    input int BreakEvenPips = 150; // Pips to Breakeven

    //+------------------------------------------------------------------+
    //| Global Variables |
    //+------------------------------------------------------------------+
    int fastMA, slowMA, atr;
    datetime lastBarTime;
    double pipSize;
    bool isNewsBlackout = false;

    //+------------------------------------------------------------------+
    //| Expert initialization function |
    //+------------------------------------------------------------------+
    int OnInit()
    {
    // Calculate Pip Size (10 points per pip for 5-digit brokers)
    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
    fastMA = iMA(Symbol(), PERIOD_H1, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
    slowMA = iMA(Symbol(), PERIOD_H1, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
    atr = iATR(Symbol(), PERIOD_H1, ATRPeriod);

    if(fastMA == INVALID_HANDLE || slowMA == INVALID_HANDLE || atr == INVALID_HANDLE)
    {
    Print("Indicator creation failed. Error: ", GetLastError());
    return(INIT_FAILED);
    }

    Print("News Filter EA initialized. Symbol: ", Symbol(), " Magic: ", MagicNumber);
    return(INIT_SUCCEEDED);
    }

    //+------------------------------------------------------------------+
    //| Expert deinitialization function |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
    IndicatorRelease(fastMA);
    IndicatorRelease(slowMA);
    IndicatorRelease(atr);
    }

    //+------------------------------------------------------------------+
    //| Expert tick function |
    //+------------------------------------------------------------------+
    void OnTick()
    {
    // --- 1. News Filter Logic (Independent of Bar Time) ---
    if(EnableNewsFilter)
    {
    isNewsBlackout = IsInNewsWindow();

    if(isNewsBlackout)
    {
    // If blackout is active, cancel pending orders if enabled
    if(CancelPendingOnNews)
    DeletePendingOrders();

    // Stop further processing (No new trades)
    return;
    }
    }

    // --- 2. Check for New Bar (To avoid multiple signals per bar) ---
    if(Time[0] == lastBarTime) return;
    lastBarTime = Time[0];

    // --- 3. Get Indicator Data ---
    double fastMAVal[1], slowMAVal[1], atrVal[1];
    if(CopyBuffer(fastMA, 0, 1, 1, fastMAVal) < 1) return;
    if(CopyBuffer(slowMA, 0, 1, 1, slowMAVal) < 1) return;
    if(CopyBuffer(atr, 0, 1, 1, atrVal) < 1) return;

    double currentATR = atrVal[0];

    // --- 4. Volatility Filter ---
    if(currentATR < VolatilityFilterMin
    pipSize || currentATR > VolatilityFilterMax pipSize)
    return;

    // --- 5. Check Existing Trades ---
    int total = OrdersTotal();
    for(int i = total - 1; i >= 0; i--)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
    {
    // Manage the existing trade
    ManageTrade(OrderTicket(), OrderType(), OrderOpenPrice());
    return; // Only one trade at a time
    }
    }
    }

    // --- 6. Entry Logic (Only if no position and not in news blackout) ---
    int direction = 0;
    if(fastMAVal[0] > slowMAVal[0]) direction = 1;
    else if(fastMAVal[0] < slowMAVal[0]) direction = -1;

    if(direction == 1)
    OpenBuy();
    else if(direction == -1)
    OpenSell();
    }

    //+------------------------------------------------------------------+
    //| Check if current time is inside the news blackout window |
    //+------------------------------------------------------------------+
    bool IsInNewsWindow()
    {
    datetime now = TimeCurrent();
    MqlDateTime today;
    TimeToStruct(now, today);

    int currentHour = today.hour;
    int currentMin = today.min;
    int currentTotalMin = currentHour
    60 + currentMin;

    int startTotalMin = NewsStartHour 60 + NewsStartMinute;
    int endTotalMin = NewsEndHour
    60 + NewsEndMinute;

    // Check if current time is between start and end
    if(currentTotalMin >= startTotalMin && currentTotalMin <= endTotalMin)
    {
    Print("News Blackout Active. Time: ", TimeToString(now));
    return true;
    }

    return false;
    }

    //+------------------------------------------------------------------+
    //| Delete all pending orders for this EA |
    //+------------------------------------------------------------------+
    void DeletePendingOrders()
    {
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
    {
    if(OrderType() == OP_BUYLIMIT || OrderType() == OP_SELLLIMIT ||
    OrderType() == OP_BUYSTOP || OrderType() == OP_SELLSTOP)
    {
    bool res = OrderDelete(OrderTicket());
    if(res)
    Print("Pending Order Deleted during News: ", OrderTicket());
    }
    }
    }
    }
    }

    //+------------------------------------------------------------------+
    //| Open Buy Order |
    //+------------------------------------------------------------------+
    void OpenBuy()
    {
    double ask = Ask;
    double sl = ask - StopLossPips pipSize;
    double tp = ask + TakeProfitPips
    pipSize;
    double lot = CalculateLot();

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

    //+------------------------------------------------------------------+
    //| Open Sell Order |
    //+------------------------------------------------------------------+
    void OpenSell()
    {
    double bid = Bid;
    double sl = bid + StopLossPips pipSize;
    double tp = bid - TakeProfitPips
    pipSize;
    double lot = CalculateLot();

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

    //+------------------------------------------------------------------+
    //| Calculate Lot Size (Auto or Fixed) |
    //+------------------------------------------------------------------+
    double CalculateLot()
    {
    if(!UseAutoLot)
    return LotSize;

    double riskAmount = AccountBalance() (RiskPercent / 100.0);
    double stopLossDistance = StopLossPips
    pipSize;
    double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);

    double calculatedLot = riskAmount / (stopLossDistance tickValue);
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

    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 Trade: Trailing Stop and Breakeven |
    //+------------------------------------------------------------------+
    void ManageTrade(int ticket, int type, double openPrice)
    {
    // Breakeven Logic
    if(UseBreakEven)
    {
    double currentPrice = (type == OP_BUY) ? Bid : Ask;
    double profitInPips = 0;
    if(type == OP_BUY) profitInPips = (currentPrice - openPrice) / pipSize;
    else profitInPips = (openPrice - currentPrice) / pipSize;

    if(profitInPips >= BreakEvenPips)
    {
    double newSL = (type == OP_BUY) ? openPrice + (pipSize 5) : openPrice - (pipSize 5);
    if(OrderStopLoss() != newSL)
    {
    OrderModify(ticket, openPrice, newSL, OrderTakeProfit(), 0, clrNONE);
    }
    return;
    }
    }

    // Simple Fixed Trailing (For simplicity, we use a fixed 100 pip trail)
    double trailDistance = 100 pipSize;
    double currentSL = OrderStopLoss();
    double newSL = 0;

    if(type == OP_BUY)
    {
    double newStop = Bid - trailDistance;
    if(newStop > openPrice && (currentSL == 0 || newStop > currentSL))
    newSL = newStop;
    }
    else if(type == OP_SELL)
    {
    double newStop = Ask + trailDistance;
    if(newStop < openPrice && (currentSL == 0 || newStop < currentSL))
    newSL = newStop;
    }

    if(newSL != 0 && currentSL != newSL)
    {
    OrderModify(ticket, openPrice, newSL, OrderTakeProfit(), 0, clrNONE);
    }
    }
    //+------------------------------------------------------------------+
    ``

    Reference



  • BIS Quarterly Review, "Volatility challenges risk-taking", December 2025, on market structure and retail investor impact on gold volatility.

  • World Gold Council, "Gold Market Primer: Market Size and Structure", March 2026, on gold's liquidity and market composition.


  • 本文首发于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.