The rule restricts actions inside a window, not exposure: a position that simply rides through a release is usually fine, while a pending order the broker fills for you is not.
The window is a parameter, not a constant - it changes between firms and often between the evaluation and funded phases, so it belongs in an input you re-read from the rulebook.
Ask the filter about two timestamps: a moment in the near future, to retire resting orders before the window opens, and now, to gate the entry.
A filter that cannot read the calendar must fail closed; treating a failed lookup as "no news today" is worse than having no filter at all.
Table of Contents (23 min read)Contents
What a News-Trading Rule Actually Restricts
Almost every trader reads the news clause the same wrong way: don't be in the market when the number prints. That is not what the clause says. What it restricts is an action taken inside a time window around a scheduled high-impact release - opening a position, closing one, and at some firms modifying one. Simply being exposed while the release crosses the wire is, at most firms, nothing at all. It is the click that gets flagged, not the exposure.
For a discretionary trader that distinction is academic, because they were not going to click anyway. For an expert advisor it is the whole problem. Your EA does not know a release is coming. It knows a sweep printed, that displacement followed, and that its entry conditions are true. Whether that happened at 08:29:58 or 08:32:14 is invisible to it and decisive to the firm.
Three properties of the rule follow from that, and each one changes how you write the code:
The trigger is a timestamp, not an intention. Nobody at the firm decides whether you were "trading the news". A script reads your trade log against a release schedule. There is no grace period and no explaining yourself afterwards.
The window is two-sided. It opens some minutes before the release and closes some minutes after it. The pre-release side is the one automated systems fall into most, because an ICT entry that fires on the pre-release liquidity grab feels like the cleanest setup of the day.
The restricted verbs are firm-specific. Some clauses name only opening. Others name opening and closing. A few name modification too. That single word choice decides how you handle a stop that is already sitting on the server.
The filter has to look ahead, because the last legal action happens before the window it is protecting you from.
Everything else in this article is downstream of that first paragraph. If the rule restricted exposure, the answer would be simple - flatten before news and stand aside. Because it restricts actions, the answer is a filter that knows what your EA is about to do, and what the broker might do on your behalf while your code is asleep.
Why an ICT Model Runs Into This Rule More Than You Do by Hand
An ICT-based EA is not merely sometimes near a release. Its entry logic is structurally drawn toward one.
The model is built on liquidity: resting orders above an old high or below an old low, swept, followed by displacement in the opposite direction, and then an entry on the retracement into the imbalance that displacement left behind. That sequence is a description of what a high-impact release does to an order book. The release is the liquidity event. So the pattern your EA scores highest - a clean sweep, violent displacement, a tidy imbalance to trade back into - is the pattern a scheduled number manufactures on purpose.
The timing compounds it. The New York morning killzone that most ICT models trade opens at the same hour the major US releases are published. A trading session filter that keeps your EA in its best window is, without a news filter beside it, also keeping it inside the densest release slot of the day.
Then there is the mechanical difference. You would hesitate. You would glance at the calendar tab, decide the setup was not worth the risk, and let it go. Your EA evaluates the displacement candle the moment it closes and sends the order in the same tick. Any rule that depends on somebody noticing is a rule an automated system will break on the first day nobody is watching.
And there is one more, specific to how ICT entries are usually implemented: resting limit orders. A model that wants to buy the retracement into an unmitigated imbalance does not sit and watch - it places a pending order at the level and waits, sometimes for hours. That order is now the broker's to execute. When the window opens, your code can be perfectly disciplined and still be filled, because the fill does not come from your code at all. This is the single most common accidental violation in automated prop trading, and it is entirely a design problem rather than a discipline problem.
Does Holding a Position Through the News Count?
Usually not. The hold itself is not an action, and a position opened well before the window that simply rides through the print is, at most firms, unremarkable.
What matters is everything that executes while the window is open - and some of that is not yours to control. Read the middle column below with your own rulebook next to you, because two of these six rows are things the server does on your behalf.
What happens
Before the window
Inside the window
After it closes
Your EA opens a new position
Fine
The textbook violation
Fine
A resting pending order gets filled
Fine
Counts as an open - and your code never ran
Fine
Your EA closes a position itself
Fine
A violation wherever closes are named
Fine
The broker fills your resting stop or target
Fine
Reads as a close, and you cannot cancel it in time
Fine
Your EA moves a stop or trails it
Fine
Restricted wherever the clause says modify
Fine
A position simply stays open through the print
Fine
Not an action - normally nothing to flag
Fine
Read down the middle column. Two of the six rows are things the broker does on your behalf, which is why a filter that only guards the entry call is incomplete.
The fourth row is the uncomfortable one. Your stop-loss and your target are orders resting on the broker's server. If the firm's clause names closing as well as opening, a stop filled two seconds after the release reads exactly like a close inside the window, and no amount of care in your OnTick() handler prevents it.
There are only two honest answers, and neither is free. You can flatten the position before the window opens - an EA-initiated close, executed legally outside the window, at the cost of giving up a trade you may have wanted to keep. Or you can leave the position with its protection intact and accept that a fill inside the window is possible. What you must not do is the clever-looking third option: pulling the stop off the position to avoid a close, then putting it back afterwards. That trades a rule risk for an uncapped market risk during the most violent minutes of the day, and volatility spikes and slippage around a release are exactly why the stop was there.
How Wide Is the Window - and Why Your Number Will Change
There is no universal blackout window, and this is where most compliant EAs quietly stop being compliant. The clause varies along four axes at once:
Length. Windows are typically expressed as a few minutes on either side of the release, but the number is set by the firm and not by the market.
Symmetry. Some clauses use the same count before and after. Others use a wider post-release side, because that is where the spread damage lands.
Coverage. Which events count as high-impact is the firm's definition, not your calendar's. Some clauses name a tier of events explicitly; others point at a specific third-party calendar and adopt its colour coding wholesale.
Account phase. This is the one that breaks accounts. A firm may leave the evaluation phase unrestricted and impose a hard window only once the account is funded - or tighten the count at the same moment.
That fourth axis deserves its own warning, because it is the only one that changes while nothing about your EA changes. You pass an evaluation with a filter that was correct for the evaluation, get the funded credentials, attach the same .ex5 to the new terminal, and the EA is now enforcing a rule that no longer exists in the form it was coded against. You will see this discussed most often around FTMO, whose evaluation and funded wording have not always matched - and whose specifics have moved more than once, which is precisely the argument for reading the number out of the rulebook rather than out of a constant you set last year. Nothing in the code moves at the challenge-to-funded transition, which is exactly why the breach is so easy to miss.
The practical consequence is that the window belongs in an input parameter, never a #define, and that your own safety margin sits on top of the firm's number rather than replacing it. Server clocks disagree, releases are occasionally published a beat early, and the firm's timestamp is the one that counts. A margin of a minute either side costs you very little; the calculator below lets you see exactly how little, or how much, on your own session.
Set your own numbers
Blackout budget: what the filter costs your session
Set your firm's window, add the margin you want on top, and see how much of your trading day the filter mutes.
Firm's window before release
Firm's window after release
Your own margin, each side
High-impact events on your pair
Length of your trading session
Firm's raw blackout per event
—
Window you should actually code
—
Minutes muted across the day
—
Share of your session muted
—
Every minute of safety margin is bought with trading time. Push the margin up and watch how fast an ICT session that lives in the New York morning disappears.
Push the margin slider and watch the last figure. A generous margin on a day carrying several high-impact events can mute a meaningful slice of a four-hour ICT session - which is the real trade-off, and the reason the answer is a tuned number rather than "block the whole day to be safe".
Building the Filter Into the EA
A rule you keep in your head is not a filter. It is a plan to remember something at 08:29 on a Friday. The filter has to live where the order is sent.
Reading the Economic Calendar Programmatically
MetaTrader 5 ships an economic calendar the terminal maintains for you, and MQL5 exposes it directly - no scraping, no external feed, no subscription. Three facts about that API shape the whole implementation:
Events and occurrences are separate objects.CalendarValueHistory() returns occurrences: an event id, a release time, and the actual, forecast and previous figures. The importance of an event does not live there. You have to resolve the event with CalendarEventById() before you can ask whether it is high-impact. Filters that skip this step end up gating on nothing.
You can filter server-side by currency. The call takes a currency code, so you ask for USD events for a USD-quoted symbol rather than pulling the world and sorting it yourself.
Times come back in trade-server time. The calendar and your terminal already agree on the clock, which removes one whole class of bug - but it also means the firm's published window, which is usually quoted in a different zone, needs converting once, by you, on paper.
mql5NewsBlackout.mqh
input int InpWindowBefore = 2; // firm's blackout, minutes BEFORE the release
input int InpWindowAfter = 2; // firm's blackout, minutes AFTER the release
input int InpSafetyMargin = 1; // your own margin, added to BOTH sides
input ENUM_CALENDAR_EVENT_IMPORTANCE InpMinImportance = CALENDAR_IMPORTANCE_HIGH;
// True when `when` (trade-server time) falls inside a blackout window for `symbol`.
// Fails CLOSED: if the calendar cannot be read, the answer is "blackout".
bool InNewsBlackout(const string symbol, const datetime when, string &reason)
{
string ccy[2];
ccy[0] = SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE);
ccy[1] = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
int before = (InpWindowBefore + InpSafetyMargin) * 60;
int after = (InpWindowAfter + InpSafetyMargin) * 60;
for(int c = 0; c < 2; c++)
{
if(ccy[c] == "" || (c == 1 && ccy[1] == ccy[0]))
continue;
MqlCalendarValue values[];
// Scan a day either side. You need the release that already fired,
// not just the next one on the schedule.
int n = CalendarValueHistory(values, when - 86400, when + 86400, NULL, ccy[c]);
if(n < 0) // read failed - never assume "all clear"
{
reason = "calendar unreadable for " + ccy[c];
return true;
}
for(int i = 0; i < n; i++)
{
MqlCalendarEvent ev;
if(!CalendarEventById(values[i].event_id, ev))
continue;
if(ev.importance < InpMinImportance) // importance lives HERE,
continue; // not on the value struct
if(ev.time_mode != CALENDAR_TIMEMODE_DATETIME) // no exact time to fence
continue;
datetime t = values[i].time; // already trade-server time
if(when >= t - before && when <= t + after)
{
reason = ev.name + " / " + ccy[c] + " at " +
TimeToString(t, TIME_DATE | TIME_MINUTES);
return true;
}
}
}
return false;
}
The importance filter is the line most people get wrong: MqlCalendarValue carries no importance field, so you must resolve the event before you can tell whether it is high-impact.
Two details in that snippet are worth calling out. The n < 0 branch fails closed: if the calendar cannot be read - a fresh terminal that has not synced, a VPS that lost its connection, a broker server that serves no calendar - the function answers "blackout" rather than "clear". A news filter that treats a failed lookup as an empty schedule is worse than no filter, because it produces confidence instead of protection.
The second is the time_mode check. Events whose source publishes only a day, not a time, cannot be fenced by a window at all - there is no timestamp to build one around. Skipping them is correct for the window logic, but log them: if your firm's wording extends to unscheduled or tentative releases, the compliant response is to sit out that currency for the day, and you want to know that the day contains one.
Mapping the calendar's importance levels onto your firm's definition is a judgement call you make once and write down. The enumeration runs from none through low and moderate to high. Most clauses that say "high impact" mean the top level, but a clause that points at a third-party calendar's red-flag list will not always line up with MetaQuotes' classification event for event. Where the two disagree, gate on the looser of the two - blocking a moderate event you did not have to block costs one setup, and trading a high-impact one you should have blocked can cost the account. If a specific event matters enough, pin it by its event id rather than by its importance band.
A note for MT4. There is no calendar API in MQL4. The workable pattern is a scheduled export - a CSV the terminal reads from MQL4/Files, or a fetch guarded by the WebRequest whitelist - with the same fail-closed rule applied to a file that is stale or missing. The logic below is identical; only the source of the events changes.
Gating the Order-Send Call
Now the part that actually enforces the rule. The instinct is to wrap the entry in if(!InNewsBlackout(...)) and call it done. That handles the textbook case and leaves the resting-order case wide open.
The pattern that closes both is to ask the same function about two different timestamps: one in the near future, and one now.
mql5OnTick.mq5
input int InpPreClearSeconds = 90; // clear resting orders this early
input bool InpFlattenBeforeNews = false; // close positions before the window too
void OnTick()
{
datetime now = TimeTradeServer(); // NOT TimeCurrent(): that clock stops
string why = ""; // moving when the quotes thin out
// 1. Ask about the NEAR FUTURE. If the window opens within
// InpPreClearSeconds, retire everything the broker could fill
// for us while the EA is muted.
if(InNewsBlackout(_Symbol, now + InpPreClearSeconds, why))
{
CancelOwnPendingOrders(); // by magic number, this symbol only
if(InpFlattenBeforeNews)
CloseOwnPositions(); // an EA close, still outside the window
}
// 2. Ask about NOW. Inside the window the EA touches nothing at all.
if(InNewsBlackout(_Symbol, now, why))
{
LogBlockOnce(why); // one line per event, for the audit
return;
}
// 3. Only from here down does the ICT model get to look at price.
ScanForSweepAndDisplacement();
}
One function, asked at two timestamps. That second question - about a moment that has not arrived yet - is what turns a filter that blocks entries into a filter that also survives resting orders.
The first question - will the window be open in ninety seconds? - is the one that saves accounts. It gives the EA time to cancel its own pending orders while it is still legal to do so, which is the only defence against a limit order the broker fills for you while your code is muted. Cancel by magic number and by symbol, so a manual trade or a second EA on the same terminal is left alone.
The second question gates every entry. Note that the whole ICT scanning routine sits below the gate, not above it: the model never gets to look at price during the window, so there is no path from a valid setup to a send call, and no clever early-return to forget later.
If you already run a kill switch for daily-loss protection, reuse its plumbing here. The two are the same shape - a condition that suppresses all trading, computed from something outside the strategy - and one implementation is easier to trust than two.
The Edge Cases That Cause Real Violations
The obvious case is handled by ten lines of code. These are the ones that turn up in support tickets:
The pending order nobody cancelled. Covered above, and worth repeating because it is the leading cause. Any pending order news trading rule applies to the fill, and the fill is the broker's action, not yours.
The order sent one second before the boundary. You send at T-margin minus a tick; the broker fills it a moment later, inside the window. The execution timestamp is what the firm reads. Your margin exists precisely to absorb this, which is why it should be measured in tens of seconds rather than milliseconds.
The clock that is not quite right. The calendar and your terminal share the trade server's clock, but your firm's rulebook is published in some other zone and your VPS keeps its own time. Clock drift of even a few seconds sits directly on top of a two-minute window. Convert once, write down the offset, and check it after every daylight-saving shift.
The second currency. A filter written for EUR/USD that only checks the quote currency will miss a European release entirely. Both sides of the pair need gating - and metals and indices need explicit handling, since the base symbol of a gold or index CFD is not a currency the calendar knows.
The restart with an empty calendar. A terminal freshly installed on a new VPS may have no calendar data until it syncs. This is exactly the case the fail-closed branch exists for, and it is also the case that happens the day you move to the funded server.
Two EAs, one account. The firm audits the account, not the EA. A second strategy on another chart, with no filter, breaches the same account. Compliance is an account-level property, so the filter belongs in a shared include that every EA on that login calls.
Verifying the Filter Before You Go Live
An unverified filter is a belief, not a control. The awkward part is that you cannot simply run the EA through the strategy tester and watch it behave: the calendar functions want a live terminal behind them, so a plain call inside a backtest tends to return nothing at all - which looks exactly like a clean schedule and lets every trade through. That is the first thing to check, and the first thing to work around.
Proving the filter works before it decides your account
1
Log every block, not just every trade
Write the event name, its currency, the release time and the decision to a CSV, so each block can be audited long after it happened.
2
Cache the calendar before you test
Calendar calls want a live terminal. Dump the events your test period needs to a file or a binary resource, then read the filter from that.
3
Replay the two boundaries
Assert the filter flips exactly at the window's open and close, not one bar late, and that it covers both of the symbol's currencies.
4
Dry-run a real news week on demo
Disable sending for a week that holds a rate decision and a jobs report. Read the block log, not the equity curve.
5
Reconcile against the primary schedule
Spot-check the release times in your log against the issuing institution's own published calendar rather than a third-party feed.
6
Break it on purpose
Force the calendar call to fail. A compliant filter refuses to trade; a dangerous one sails through as though the day were clear.
Steps two and six are the ones that get skipped, and they are the two that decide whether the filter holds on the day it matters.
Cache first, then replay. Pull the events for your test period while the terminal is connected, write them to a file or embed them as a binary resource, and have the filter read from that source when it is running under the tester. Now the backtest actually exercises the window logic, and you can point at the trades that were not taken and say why.
Then do a live forward test with sending disabled, over a week that contains at least one central-bank decision and one employment release. You are not reading the equity curve here - you are reading the log. Every blocked signal should name an event, a currency and a release time you can verify against the issuing institution's own published schedule. Any block you cannot explain is a bug, and so is any release that passed without a block.
Backtested and forward-tested results describe how a filter behaved on past data; they say nothing certain about future trading outcomes, and the same caution that applies to any strategy result applies here - our risk warning sets out the boundaries.
Re-check the Filter Every Time the Account Changes
The filter is not a thing you build once. It is a parameter set with an expiry date, and the expiry is any moment the account underneath it changes: evaluation to funded, firm to firm, one symbol to three, one VPS to another.
Re-check the news filter whenever the account changes
0 / 7
Open the live rulebook in your dashboard, not a saved copy, and paste the news clause verbatim into a comment at the top of the EA.
Confirm whether the clause names opening only, or opening and closing - the second wording changes what you do with resting stops.
Reset the before and after inputs from that clause, and never let evaluation-phase numbers ride into a funded account untouched.
Convert the firm's published times to your trade server's clock, and write the offset down next to the clause.
Walk every symbol the EA is attached to, including metals and indices whose base symbol is not a currency the calendar knows.
Run one dry-run session on the new server before you enable live sending, and read the block log line by line.
Repeat all of the above when you switch firms, add a symbol, move the terminal to a new VPS, or the firm emails a rule update.
★
Checklist complete — you’re cleared to proceed.
Seven minutes of checklist against the one transition that quietly turns a compliant EA into a non-compliant one.
News compliance is one rule among several, and the discipline that makes it work - putting the constraint into code rather than into memory - is the same discipline behind staying inside the daily drawdown limit and every other line in the rulebook. The broader sequence of passing a prop firm challenge with an ICT EA is really just this pattern applied rule by rule, until nothing about the account's survival depends on somebody being awake.
FAQ
What if my EA sends the order legally but the fill lands inside the window?
The firm reads execution timestamps, so a late fill can be flagged even though the send was clean. This is not a rare edge - it is a normal consequence of latency thickening right before a release. The defence is the safety margin: make it wide enough that a slow fill still lands outside the firm's own boundary, and treat any margin measured in milliseconds as no margin at all.
Do I need a news filter during the evaluation if my firm doesn't restrict it?
Build it anyway, and ship it switched on with the evaluation's own numbers - even if those numbers are zero. The point is not the evaluation; it is the transition. A filter that already exists, already logs, and already has an input parameter waiting for a new number takes one minute to update when the funded rules arrive. A filter that does not exist yet takes a rewrite, at exactly the moment you are least inclined to do one.
Does the rule apply to every symbol, or only ones matching the news currency?
The clause is usually written around events relevant to the instrument you are trading, which is why gating on the symbol's own currencies is the right default. But the wording is the authority, not the convention - some clauses restrict trading account-wide during a tier-one release regardless of instrument. Read yours, and if it is account-wide, hoist the check above the per-symbol loop.
Can I just turn the EA off around news instead of coding a filter?
You can, and it works right up until the morning you forget, or the release you did not have on your list, or the day the terminal restarts itself and the EA comes back with algorithmic trading enabled. Manual suppression also cannot cancel a pending order that is already resting at an imbalance. The filter is not there because switching off is hard; it is there because switching off is a thing a person has to remember.
Does MetaTrader 4 have the same calendar functions?
No. MQL4 has no calendar API, so the event data has to arrive some other way - a scheduled CSV export the EA reads from disk, or a whitelisted request to a schedule you control. The window logic, the two-timestamp pattern and the fail-closed rule are unchanged; only the source of the events differs. Treat a missing or stale file exactly as you would treat a failed calendar call: as a blackout.
Sources & Further Reading
Want to go deeper? These independent, authoritative sources shaped this guide — each one is worth reading in full:
The Funded Desk is the SignalBots editorial team covering prop-firm challenges and funded-account trading. We research and write the guides on evaluation rules, drawdown limits, payout structures and the discipline funded trading demands.
Discussions 0
Leave a comment