How to Add a Trading Hours (Session) Filter to an MQL5 EA (Code + Real Backtest)
To restrict an MQL5 Expert Advisor to certain trading hours, read the broker server time with TimeCurrent(t) into an MqlDateTime struct, compare t.hour against your start and end hour, and return from OnTick() before any entry logic when the time is outside that window. Handle windows that cross midnight (e.g. 22:00 to 06:00) with an OR instead of an AND, and decide whether open positions should be closed when the session ends.
Why a time filter matters
Most strategies behave very differently in the London/New York overlap than in the thin Asian hours or around the daily rollover, when spreads widen. A session filter is usually the cheapest improvement you can try: it doesn't change the signal, it only changes *when* the signal is allowed to fire.
Three things trip people up:
- Whose clock?
TimeCurrent()returns the broker's server time, not your PC's time and not GMT. In the Strategy Tester it is simulated from history, so the same code behaves the same way live and in a backtest. If you think in GMT, convert using your broker's offset (and remember it changes with daylight saving). - Midnight-crossing windows.
hour >= 22 && hour < 6is never true. You needhour >= 22 || hour < 6. - What happens to open trades when the window closes — leave them to SL/TP, or flatten them? Make it an input.
The session check in MQL5
TimeCurrent() has an overload that fills an MqlDateTime structure directly. The struct holds year, mon, day, hour, min, sec, day_of_week (0 = Sunday … 6 = Saturday) and day_of_year, all int.
input int InpStartHour = 8; // first hour allowed to open trades
input int InpEndHour = 17; // trading stops at this hour (exclusive)
input bool InpSkipFriday = false; // no new trades on Fridays
// true if server time is inside [start, end); handles windows that wrap midnight
bool InSession()
{
MqlDateTime t;
TimeCurrent(t);
if(InpSkipFriday && t.day_of_week == 5)
return(false);
if(t.day_of_week == 0 || t.day_of_week == 6)
return(false);
if(InpStartHour == InpEndHour)
return(true); // equal hours = trade all day
if(InpStartHour < InpEndHour)
return(t.hour >= InpStartHour && t.hour < InpEndHour);
return(t.hour >= InpStartHour || t.hour < InpEndHour); // overnight window, e.g. 22 -> 6
}
If you already have a datetime (for example a bar's open time from iTime), TimeToStruct(dt, t) does the same conversion and returns false on failure. Need minute precision ("08:30 to 16:45")? Compare t.hour * 60 + t.min against start/end values in minutes instead of hours.
Wiring it into OnTick()
Check the session first, optionally flatten outside it, then run the normal new-bar + signal logic. Here the signal is a plain EMA(20)/EMA(50) cross on the last closed bar:
void OnTick()
{
bool inSession = InSession();
if(!inSession && InpCloseOutside && MyPositionType() != -1)
CloseMine(); // flatten when the window ends
if(!IsNewBar() || !inSession)
return; // no new entries outside the window
if(CopyBuffer(h_Fast, 0, 1, 2, g_fast) < 2 || CopyBuffer(h_Slow, 0, 1, 2, g_slow) < 2)
return;
// series arrays: index 0 = last closed bar, 1 = the bar before it
bool crossUp = g_fast[1] <= g_slow[1] && g_fast[0] > g_slow[0];
bool crossDown = g_fast[1] >= g_slow[1] && g_fast[0] < g_slow[0];
if(MyPositionType() != -1 || (!crossUp && !crossDown))
return;
double pt = _Point;
if(crossUp)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
c_Trade.Buy(InpLots, _Symbol, ask,
NormalizeDouble(ask - InpStopLossPts * pt, _Digits),
NormalizeDouble(ask + InpTakeProfPts * pt, _Digits));
}
else
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
c_Trade.Sell(InpLots, _Symbol, bid,
NormalizeDouble(bid + InpStopLossPts * pt, _Digits),
NormalizeDouble(bid - InpTakeProfPts * pt, _Digits));
}
}
The helpers are standard: IsNewBar() compares iTime(_Symbol, _Period, 0) with the last stored bar time; MyPositionType() loops PositionsTotal() backwards, selects each ticket with PositionGetTicket(i), and matches POSITION_SYMBOL + POSITION_MAGIC; CloseMine() calls c_Trade.PositionClose(ticket) on matching positions. The handles come from iMA(_Symbol, _Period, 20, 0, MODE_EMA, PRICE_CLOSE) in OnInit(), with ArraySetAsSeries(g_fast, true) so index 0 is the newest value, and are released with IndicatorRelease() in OnDeinit(). Validate the hour inputs in OnInit() and return INIT_PARAMETERS_INCORRECT if they are outside 0–23.
Note that the flatten check runs on every tick, not only on a new bar — otherwise a position could survive well past the session end on higher timeframes.
A real backtest: does the filter change anything?
We compiled this exact EA and ran it in the MetaTrader 5 Strategy Tester on EURUSD H1, 2024.01.01 to 2025.01.01, $10,000 deposit, 0.10 lots, 300-point SL / 600-point TP. Same code twice — once with the 08:00–17:00 server-time window, once with the filter disabled (start = end = 0, i.e. trade all day):
- 08:00–17:00 window: net P/L -$82, win rate 46%, profit factor 0.77, max drawdown 1.2%, 37 trades.
- No time filter: net P/L -$163, win rate 31%, profit factor 0.91, max drawdown 3.4%, 89 trades.
Neither version is profitable, and we're not presenting it as a strategy. What the numbers do show is what a session filter actually does: it cut trade count by more than half, cut the drawdown from 3.4% to 1.2% and lifted the win rate, while the profit factor did not improve. That's the honest pattern — a time filter reduces exposure to bad hours, but it can't rescue a weak signal. Test your own window per symbol; the "right" hours for EURUSD are not the right hours for gold or an index. Past performance does not guarantee future results.
Generate and backtest it from plain English
Writing the plumbing — handles, new-bar detection, the midnight edge case, the close-outside-session loop — is where most time goes. Ranar Algo lets you describe the rule in plain English ("EMA 20/50 cross on EURUSD H1, only trade 08:00–17:00 server time, close everything outside the window"), then generates the MQL5, compiles it, and runs a real MT5 backtest so you see actual numbers before using it. You get the source code and keep your IP. It's still improving, so if a generated EA misses something in your spec, feedback is genuinely welcome.
Summary
- Use
TimeCurrent(t)with anMqlDateTimeand comparet.hour(or minutes) to your window. - It's broker server time; the tester simulates it from history.
- Use OR for windows that cross midnight; skip weekends and optionally Fridays.
- Check the session before entry logic, and decide explicitly whether to flatten outside it.
- Backtest with and without the filter — expect lower exposure, not magic profit.