News Trading Integration in MT5 EA: Building a Complete News Management System in MQL5

A news trading integration system is one of the most critical components of a professional MT5 Expert Advisor. Without it, your trading bot is flying blind during high-impact economic releases — NFP, CPI, FOMC, GDP, and central bank decisions that can move the market 50-100 pips in seconds.

This guide walks through a production-grade news management system implemented in MQL5 for a 6-strategy multi-pair Expert Advisor. You will learn how to fetch live economic calendar data via WebRequest, parse XML with CDATA sections, perform ET to GMT timezone conversion, classify events by impact level, and block trading during volatile windows with configurable buffers.

Every code block is taken from a battle-tested multi-pair EA running across 20+ forex pairs with 6 simultaneous strategies — the same code you need to make your EA prop firm compliant and news-aware.

Why Your MT5 EA Needs a News Management System

Most prop firm challenges and funded accounts explicitly prohibit trading during major news events. But even without regulatory reasons, trading during news releases is statistically disastrous for algorithmic strategies:

  • Spread widening: Major pairs can spike from 0.2 pips to 5-10 pips during NFP or FOMC, destroying any scalping strategy.
  • Slippage and requotes: Market execution EAs get hit with massive slippage — your 10-pip TP becomes a 30-pip loss.
  • Stop loss hunting: Algorithms and institutions use news releases to trigger clustered stop losses before reversing.
  • Gap risk: Weekend gaps and Monday open spikes from Sunday news events can blow through grid strategies.

A news filter that checks a live economic calendar before every trade is the only reliable defense.

Architecture Overview

The news management system consists of four layers that work together in a pipeline:

┌─────────────────────────────┐
│  Layer 1: WebRequest Fetch  │ ← HTTPS GET to nfs.faireconomy.media
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│  Layer 2: XML + CDATA Parse │ ← Manual string parsing in MQL5
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│  Layer 3: ET → GMT Convert  │ ← US DST rule calculation
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│  Layer 4: Impact Filter     │ ← High/Medium + buffer zone check
└─────────────────────────────┘

Layer 1: Fetching Economic Calendar Data with WebRequest

MQL5 provides the WebRequest function for HTTP requests. The EA fetches the economic calendar from nfs.faireconomy.media, a free XML feed of the Forex Factory calendar:

// Input parameter for the news URL
input string InpNewsURL = "https://nfs.faireconomy.media/ff_calendar_thisweek.xml";

// News fetch interval — 6 hours to avoid rate limits
#define NEWS_FETCH_INTERVAL 21600

// Storage for up to 300 events
#define MAX_NEWS_EVENTS 300

// Event struct
struct NewsEvent {
   datetime dt;
   string country;
   string impact;
};
NewsEvent gNewsEvents[MAX_NEWS_EVENTS];
int gNewsCount = 0;

The fetch function uses WebRequest with GET method, stores the raw response bytes, and converts them to a string for parsing. It only re-fetches every 6 hours (NEWS_FETCH_INTERVAL) to avoid unnecessary bandwidth and potential rate limiting:

// Global fetch timestamp
datetime gLastNewsFetch = 0;

// Called from OnInit or OnTick with interval check
void FetchNews() {
   if(!InpNewsFilter) return;
   if(TimeCurrent() - gLastNewsFetch < NEWS_FETCH_INTERVAL) return;
   gNewsCount = 0;

   char resp[];
   uchar data[];
   string hdrs;
   ResetLastError();
   int res = WebRequest("GET", InpNewsURL, "", 0, data, resp, hdrs);
   if(res == -1 || res != 200) {
     Print("News fetch failed: curl err ", GetLastError(), " http ", res);
     return;
   }

   // Convert byte array to string
   string xml;
   int respLen = ArraySize(resp);
   for(int i = 0; i < respLen; i++)
     xml += CharToString(resp[i]);

   // Parse XML events
   ParseEvents(xml);
   gLastNewsFetch = TimeCurrent();
   Print("News loaded: ", gNewsCount, " events");
}

⚠ Critical Setup Requirement

WebRequest requires the URL domain to be whitelisted in MetaTrader 5. Go to Tools → Expert Advisors → Allow WebRequest for URLs and add: https://nfs.faireconomy.media. Without this, WebRequest returns error 4062 (function not allowed).

Layer 2: Manual XML and CDATA Parsing in MQL5

MQL5 has no built-in XML parser. Every character must be extracted manually using StringFind and StringSubstr. The Forex Factory XML feed uses CDATA sections for text fields, adding an extra layer of extraction complexity.

The raw XML structure looks like this:

<event>    <country><![CDATA[USD]]></country>    <date><![CDATA[06/14/2026]]></date>    <time><![CDATA[8:30am]]></time>    <impact><![CDATA[High]]></impact>    <title><![CDATA[Non-Farm Employment Change]]></title>    <forecast><![CDATA[200K]]></forecast>    <previous><![CDATA[175K]]></previous> </event>

The ExtractCDATA function handles this with three steps: find the tag, extract the content between tags, and strip the CDATA wrapper if present:

// Extract field value from XML block, handling CDATA
int ExtractCDATA(string &src, int pos, string tag, string &out) {
   string openTag = "<" + tag + ">";
   string closeTag = "</" + tag + ">";
   int o = StringFind(src, openTag, pos);
   if(o < 0) return -1;
   int c = StringFind(src, closeTag, o);
   if(c < 0) return -1;
  
   int dataStart = o + StringLen(openTag);
   string raw = StringSubstr(src, dataStart, c - dataStart);
  
   // Strip CDATA wrapper if present
   int cd1 = StringFind(raw, "<![CDATA[");
   if(cd1 >= 0) {
     cd1 += 9; // skip past <![CDATA[
     int cd2 = StringFind(raw, "]]>", cd1);
     if(cd2 > cd1)
       raw = StringSubstr(raw, cd1, cd2 - cd1);
   }
   StringTrimLeft(raw);
   StringTrimRight(raw);
   out = raw;
   return c + StringLen(closeTag);
}

The main parsing loop iterates through all <event> blocks, extracts country, date, time, and impact for each:

// Parse all events from XML string
void ParseEvents(string &xml) {
   int pos = 0;
   while(gNewsCount < MAX_NEWS_EVENTS) {
     int evStart = StringFind(xml, "<event>", pos);
     if(evStart < 0) break;
     int evEnd = StringFind(xml, "</event>", evStart);
     if(evEnd < 0) break;
     string block = StringSubstr(xml, evStart, evEnd - evStart + 8);
  
     string country, dateStr, timeStr, impact;
     if(ExtractCDATA(block, 0, "country", country) < 0) { pos = evEnd; continue; }
     if(ExtractCDATA(block, 0, "date", dateStr) < 0) { pos = evEnd; continue; }
     if(ExtractCDATA(block, 0, "time", timeStr) < 0) { pos = evEnd; continue; }
     if(ExtractCDATA(block, 0, "impact", impact) < 0) { pos = evEnd; continue; }
  
     // Convert date string to datetime
     datetime evDt = ParseNewsDate(dateStr);
     if(evDt <= 0) { pos = evEnd; continue; }
  
     // Convert time string to minutes and apply ET offset
     int evMin = ParseNewsTime(timeStr);
     int etOff = ETOffsetFromGMT();
     evDt += evMin * 60 + etOff * 3600;
  
     gNewsEvents[gNewsCount].dt = evDt;
     gNewsEvents[gNewsCount].country = country;
     gNewsEvents[gNewsCount].impact = impact;
     gNewsCount++;
     pos = evEnd;
   }
}

Layer 3: Eastern Time to GMT Conversion with US DST Rules

The Forex Factory calendar lists times in Eastern Time (ET). Converting to GMT is non-trivial because US Daylight Saving Time follows specific rules: DST starts the second Sunday of March at 2:00 AM and ends the first Sunday of November at 2:00 AM.

The EA implements a production-grade DST calculator that computes the correct offset (EST = UTC-5, EDT = UTC-4) for any date without relying on external libraries:

// Calculate current Eastern Time offset from GMT
int ETOffsetFromGMT() {
   MqlDateTime dt;
   TimeGMT(dt);
   int m = dt.mon, d = dt.day;
   if(m <= 2) return 5; // Jan-Feb: EST (UTC-5)
   if(m >= 4 && m <= 10) return 4; // Apr-Oct: EDT (UTC-4)
   if(m == 12) return 5; // Dec: EST
   // March: DST starts 2nd Sunday
   if(m == 3) {
     if(d >= 15) return 4;
     if(d <= 7) return 5;
     // Compute 2nd Sunday between 8-14
     MqlDateTime mar8 = dt;
     mar8.mon = 3; mar8.day = 8;
     datetime t8 = StructToTime(mar8);
     TimeToStruct(t8, mar8);
     int secondSun = 8 + ((7 - mar8.day_of_week) % 7);
     return (d >= secondSun) ? 4 : 5;
   }
   // November: DST ends 1st Sunday
   if(m == 11) {
     if(d > 7) return 5;
     MqlDateTime nov1 = dt;
     nov1.mon = 11; nov1.day = 1;
     datetime t1 = StructToTime(nov1);
     TimeToStruct(t1, nov1);
     int firstSun = 1 + ((7 - nov1.day_of_week) % 7);
     return (d < firstSun) ? 4 : 5;
   }
   return 5;
}

The time parser converts "8:30am" or "2:15pm" formatted strings to minutes past midnight, handling AM/PM and edge cases like 12:00 AM (midnight = 0) and 12:00 PM (noon = 720):

// Parse "8:30am" or "2:15pm" to minutes from midnight
int ParseNewsTime(string t) {
   bool isPM = (StringFind(t, "pm") >= 0) || (StringFind(t, "PM") >= 0);
   int l = StringLen(t);
   if(l < 6) return 0;
   string hhmm = StringSubstr(t, 0, l - 2);
   StringTrimRight(hhmm);
   int colon = StringFind(hhmm, ":");
   if(colon < 0) return 0;
   int h = (int)StringToInteger(StringSubstr(hhmm, 0, colon));
   int m = (int)StringToInteger(StringSubstr(hhmm, colon + 1));
   if(isPM && h < 12) h += 12;
   if(!isPM && h == 12) h = 0;
   return h * 60 + m;
}

Layer 4: Impact Classification and Filter Check

The filter supports two dimensions of filtering: impact level and currency relevance. High and Medium impact events are blocked by default. Low impact events (such as secondary housing data) are allowed through. The country relevance function maps economic regions to traded pairs:

// Check if news event country affects a traded pair
bool NewsAffectsPair(string nc, string sym) {
   if(nc == "All") return true;
   // China data affects AUD, NZD (commodity), JPY
   if(nc == "CNY")
     return (StringFind(sym, "AUD") >= 0) ||
           (StringFind(sym, "NZD") >= 0) ||
           (StringFind(sym, "JPY") >= 0);
   return (StringFind(sym, nc) >= 0);
}

The main filter check iterates all stored events and blocks trading if a high or medium impact event falls within the buffer window (default: 30 minutes before and after):

// Input: buffer in minutes
input int InpNewsBufferMin = 30;

// Returns true if trading should be blocked
bool CheckNewsFilter(int pi) {
   if(!InpNewsFilter) return false;
   datetime nowGMT = TimeGMT();
   int bufMin = (InpNewsBufferMin > 0) ? InpNewsBufferMin : 30;

   for(int i = 0; i < gNewsCount; i++) {
     if(!NewsAffectsPair(gNewsEvents[i].country, gSymbols[pi]))
       continue;
     string imp = gNewsEvents[i].impact;
     if(imp != "High" && imp != "Medium") continue;
     datetime evDt = gNewsEvents[i].dt;
     if(nowGMT >= (evDt - bufMin * 60) &&
       nowGMT <= (evDt + bufMin * 60))
       return true;
   }
   return false;
}

⏱ Prop Firm Compliance Tip

FTMO, Apex, and Topstep require a minimum 3-minute no-trade window before and after high-impact news. Set InpNewsBufferMin = 5 for a comfortable safety margin. Some prop firms also prohibit trading 15 minutes before NFP and FOMC — set the buffer to 20 for full compliance.

Integration into OnTick

The news fetch is called from OnTick() with a throttled interval, and the filter check runs before every strategy evaluation. This ensures the EA never opens a position during a news event while keeping the fetch operation efficient:

// In OnTick, for each symbol:
void ProcessSymbol(int pi) {
   if(gCooldownActive) return;
   if(RangeExhausted(pi)) return;
   if(InpMaxSpreadPts > 0 && spread > InpMaxSpreadPts) return;
  
   // News filter blocks trading during events
   if(CheckNewsFilter(pi)) return;
  
   CheckStrategy1(pi);
   CheckStrategy2(pi);
   // ... other strategies
}

// In OnInit / OnTick, fetch news periodically:
void OnTick() {
   // Re-fetch news every 6 hours
   if(InpNewsFilter && TimeCurrent() - gLastNewsFetch > NEWS_FETCH_INTERVAL)
     FetchNews();
  
   // Process each pair
   for(int i = 0; i < gSymbolCount; i++)
     ProcessSymbol(i);
}

Advanced: NLP-Based Event Classification

Beyond simple impact-level filtering, production EAs can implement natural language processing (NLP) to classify event titles by their expected market impact. The <title> field from the calendar feed contains event names like "Non-Farm Employment Change" or "FOMC Statement". Using keyword-based NLP, you can categorize events into volatility groups:

// NLP-style keyword classification
enum NewsVolatility { VOL_LOW, VOL_MEDIUM, VOL_HIGH, VOL_EXTREME };

NewsVolatility ClassifyEventTitle(string &title) {
   // Extreme volatility events
   if(StringFind(title, "Non-Farm") >= 0 ||
     StringFind(title, "FOMC") >= 0 ||
     StringFind(title, "CPI") >= 0 ||
     StringFind(title, "Interest Rate") >= 0)
     return VOL_EXTREME;

   // High volatility events
   if(StringFind(title, "GDP") >= 0 ||
     StringFind(title, "Employment") >= 0 ||
     StringFind(title, "Retail Sales") >= 0 ||
     StringFind(title, "ISM") >= 0)
     return VOL_HIGH;

   // Medium volatility
   if(StringFind(title, "Housing") >= 0 ||
     StringFind(title, "Trade Balance") >= 0 ||
     StringFind(title, "PPI") >= 0)
     return VOL_MEDIUM;

   return VOL_LOW;
}

With classification, you can implement tiered buffer zones — 60 minutes for extreme events (NFP, FOMC, CPI), 30 minutes for high-impact data, and 15 minutes for medium releases. This prevents over-blocking during minor events while ensuring full protection during market-moving releases.

// Tiered buffer based on NLP classification
int GetBufferForVolatility(NewsVolatility vol) {
   switch(vol) {
     case VOL_EXTREME: return 60;
     case VOL_HIGH: return 30;
     case VOL_MEDIUM: return 15;
     default: return 0;
   }
}

Pair-to-Country Relevance Mapping

A common mistake in news filters is blocking ALL pairs when ANY event fires. Smart filters only block pairs whose currencies are affected by the event. The EA implements a relevance mapping system:

  • USD events: Block all pairs containing USD (EURUSD, GBPUSD, USDJPY, USDCAD, USDCHF, etc.)
  • EUR events: Block only EUR pairs (EURUSD, EURGBP, EURJPY, EURAUD, EURCHF, EURNZD)
  • CNY events: Block AUD, NZD, and JPY pairs (commodity currency correlation)
  • GBP events: Block GBP pairs — GBPs volatility is known to spill across all GBP crosses
  • All / Global events: Block every open position regardless of pair

Performance Considerations

The news filter runs 21 times per tick in a multi-pair EA (once per symbol). The search is O(n) where n = number of parsed events (typically 50-150). With modern MQL5 execution speeds, the loop completes in under 0.1ms. However, three optimizations are essential:

  1. Early break on impact: Check "High" impact events first — if one matches the buffer, return immediately without checking Medium events.
  2. Sorted event array: Sort events by datetime on parse. Use binary search to find the buffer window instead of linear iteration.
  3. Cache the buffer window: Pre-calculate the earliest and latest event in the current buffer window so you only need a single comparison.

Complete Integration Checklist

  • ✅ Add nfs.faireconomy.media to MT5 WebRequest whitelist
  • ✅ Define NewsEvent struct with datetime, country, impact
  • ✅ Implement ExtractCDATA() for manual XML parsing
  • ✅ Implement ParseNewsDate() and ParseNewsTime()
  • ✅ Implement ETOffsetFromGMT() with full US DST logic
  • ✅ Implement NewsAffectsPair() for currency relevance
  • ✅ Add CheckNewsFilter() before every trade entry
  • ✅ Set InpNewsBufferMin to 5+ for prop firm compliance
  • ✅ Add NLP title classification for tiered buffer zones
  • ✅ Test with Print() logging before enabling live trading

Frequently Asked Questions

What URL does the EA use for economic calendar data?

The EA uses https://nfs.faireconomy.media/ff_calendar_thisweek.xml. This is a free, no-authentication XML feed mirroring the Forex Factory economic calendar. It requires WebRequest access to be enabled in MT5 settings.

How often should an EA fetch news data?

Every 6 hours is optimal. The calendar updates once daily for the next day's events. Fetching more frequently wastes bandwidth and risks rate limiting. The NEWS_FETCH_INTERVAL constant is set to 21600 seconds (6 hours).

Does this work on MT4?

MT4 also supports WebRequest but has stricter memory limits for response size. The XML feed is approximately 50-100KB which fits within MT4's limits. The same code structure works on both platforms with minor syntax adjustments.

How to handle multiple timezones in news events?

Always convert everything to GMT. The EA stores events in GMT internally and compares against TimeGMT(). Never use local time or broker server time — these vary by broker and DST policy.

What is the best buffer time for prop firm challenges?

FTMO requires 3 minutes before and after. Set InpNewsBufferMin = 5 for safety. For challenge accounts with strict rules, use 10 minutes before and 15 minutes after high-impact news to account for volatility decay.