Advanced EA: Multi-Timeframe & Filters — Fewer False Signals, Better Performance
Your EA has entry logic and risk management, but it still takes trades in quiet Asian sessions, during news spikes, or against the higher-timeframe trend. Filters reduce noise: you only trade when conditions align across multiple timeframes and market states. According to the MQL5 Timeseries documentation, you can read any symbol-period combination with CopyRates, iTime, and iBarShift. This guide teaches you multi-timeframe analysis, session filters, time windows, ATR and spread filters — so your EA trades when it matters most.
Related guides: MQL5 Programming Reference · EA Risk Management · Scalping EA in MT5 · Backtest EA in MT5 Strategy Tester
Why MTF & Filters Matter
What is the problem? A simple MA crossover EA trades on every signal. Many of those signals occur when:
- The higher timeframe trend is against you (counter-trend scalp)
- The market is thin (Asian session — wide spreads, choppy price)
- Volatility is extreme (news — false breakouts, slippage)
- Spread is high (cost eats your profit)
What do filters do? Filters block trades when conditions are bad. They don't change your entry logic — they add a simple yes/no check: "Is it London or NY?" "Is H1 trend in our favor?" "Is spread below 30 points?" If any check fails, you skip the trade. Result: fewer trades, but higher quality.
| Filter | Purpose |
|---|---|
| Multi-timeframe (MTF) | Trade only when higher TF (H1/H4) agrees with entry direction |
| Session (London/NY) | Trade only during high-volume hours — less spread, clearer trends |
| Time window | Restrict trading to specific hours (e.g. 9:00–17:00 server) |
| ATR filter | Skip when volatility too high (news) or too low (dead market) |
| Spread filter | Skip when spread exceeds your limit — protects profit targets |
Step 1: Multi-Timeframe Analysis
What is this? Multi-timeframe analysis means checking a higher timeframe (e.g. H1 or H4) before taking a signal on the chart timeframe (e.g. M15). Example: "Only buy on M15 if the H1 trend is bullish." You use the higher timeframe as a filter — if H1 says down, you don't buy on M15 even if M15 gives a buy signal.
Why use it? Trading with the higher timeframe trend increases the chance that your M15 signal leads to a sustained move. Counter-trend trades often fail when the bigger picture is against you.
How it works: Read higher timeframe OHLC with CopyRates. The function copies MqlRates (time, open, high, low, close, volume). Use iBarShift to align the current bar time with the higher timeframe — it returns the bar index on the higher TF for a given time.
Example — H1 trend filter (only buy if H1 close is above H1 MA):
// In OnInit: create H1 MA handle
int g_maH1Handle = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
bool IsH1TrendBullish()
{
datetime barTime = iTime(_Symbol, PERIOD_CURRENT, 0); // Current bar on entry TF
int h1Bar = iBarShift(_Symbol, PERIOD_H1, barTime, false);
if(h1Bar < 0) return false;
double maBuffer[];
ArraySetAsSeries(maBuffer, true);
if(CopyBuffer(g_maH1Handle, 0, h1Bar, 1, maBuffer) < 1) return false;
double closeH1 = iClose(_Symbol, PERIOD_H1, h1Bar);
return (closeH1 > maBuffer[0]); // Price above MA = bullish
}
Use before opening a Buy: if(!IsH1TrendBullish()) return;
Step 2: Session Filter (London & New York)
What is this? London (approx. 8:00–16:00 GMT) and New York (13:00–21:00 GMT) are the most liquid sessions. Spreads are usually tighter, trends clearer. The overlap (13:00–16:00 GMT) is often the best time for Forex. A session filter blocks trades outside these hours.
Why use it? Asian session (00:00–08:00 GMT) often has wider spreads and choppy price. News at session opens can spike volatility. Restricting to London/NY improves execution and trend quality.
How it works: Use TimeToStruct(TimeCurrent(), dt) to get server hour (dt.hour) and day (dt.day_of_week). Check if the hour falls within London (8–16) or NY (13–21). For overlap, allow 8–21. Exclude weekends (dt.day_of_week == SUNDAY || dt.day_of_week == SATURDAY).
Example — Allow trading 8:00–21:00 GMT (London + NY):
bool IsLondonOrNySession()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.day_of_week == SUNDAY || dt.day_of_week == SATURDAY) return false;
return (dt.hour >= 8 && dt.hour < 21);
}
Note: TimeCurrent() returns broker server time. If your broker uses GMT+2, adjust your hour checks. SymbolInfoSessionTrade returns broker-defined session times if you prefer that over fixed hours.
Step 3: Time Window Filter
What is this? A time window restricts trading to a specific range of hours (e.g. 9:00–17:00 server time). Unlike the session filter (London/NY), this is fully customizable — you set start and end hours as inputs.
Why use it? You might want to avoid the first hour after London open (news spikes) or the last hour before NY close. A time window gives you precise control.
How it works: Same as session filter — TimeToStruct(TimeCurrent(), dt), then check dt.hour >= InpStartHour && dt.hour < InpEndHour.
Example — Trade only 10:00–18:00 server:
input int InpStartHour = 10; // Start hour (server)
input int InpEndHour = 18; // End hour (server)
bool IsWithinTimeWindow()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.day_of_week == SUNDAY || dt.day_of_week == SATURDAY) return false;
if(InpStartHour <= InpEndHour)
return (dt.hour >= InpStartHour && dt.hour < InpEndHour);
return (dt.hour >= InpStartHour || dt.hour < InpEndHour); // Overnight window
}
Step 4: ATR Filter
What is this? ATR (Average True Range) measures volatility. An ATR filter skips trades when volatility is too high (news, panic) or too low (dead market, ranging). Example: don't trade if current ATR > 1.5× the 20-period average ATR (too volatile) or < 0.5× (too quiet).
Why use it? During news, price can spike and hit your SL quickly — or whipsaw. During very quiet periods, your strategy might generate false signals. The ATR filter avoids both extremes.
How it works: Get current ATR and average ATR (e.g. 20-period SMA of ATR). Compare: if atrNow > atrAvg * 1.5 → too volatile, skip. If atrNow < atrAvg * 0.5 → too quiet, skip.
Example — Skip when ATR > 1.5× or < 0.5× 20-bar average:
int g_atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
bool IsAtrAcceptable()
{
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
if(CopyBuffer(g_atrHandle, 0, 0, 21, atrBuffer) < 21) return false;
double atrNow = atrBuffer[0];
double atrSum = 0;
for(int i = 1; i <= 20; i++) atrSum += atrBuffer[i];
double atrAvg = atrSum / 20.0;
if(atrNow > atrAvg * 1.5) return false; // Too volatile
if(atrNow < atrAvg * 0.5) return false; // Too quiet
return true;
}
Step 5: Spread Filter
What is this? Spread is the difference between Ask and Bid. When spread is high (e.g. during news or low liquidity), your fill price is worse and the cost per round-trip increases. A spread filter skips opening when spread exceeds your limit (e.g. 30 points).
Why use it? If your target is 50 pips and spread is 20 pips, you need 70 pips of move to break even. High spread eats into small targets and scalping strategies.
How it works: Get current spread with SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) (in points) or calculate (Ask - Bid) / Point. Compare with your max spread input.
Example — Skip when spread > 30 points:
input int InpMaxSpread = 30; // Max spread (points)
bool IsSpreadAcceptable()
{
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
return (spread <= InpMaxSpread);
}
Summary — Putting It All Together
You added: MTF trend filter (H1/H4 alignment), session filter (London/NY), time window (custom hours), ATR filter (volatility range), and spread filter (max spread). Apply all filters before your entry logic — if any fails, skip the trade. Your EA now trades only when conditions align.
Bonus Tip: Prefer to add MTF analysis and filters visually? Try AlfaTactix Strategy Builder free — configure multi-timeframe signals, London/NY session filters, time windows, ATR filters, and spread limits with a no-code interface. Export production-ready MQL5 with all filters built in. Fewer false signals, same result.
Full Code
// In OnInit
int g_maH1Handle = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
int g_atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
bool PassesAllFilters()
{
if(!IsH1TrendBullish()) return false;
if(!IsLondonOrNySession()) return false;
if(!IsWithinTimeWindow()) return false;
if(!IsAtrAcceptable()) return false;
if(!IsSpreadAcceptable()) return false;
return true;
}
void OnTick()
{
// ... your signal logic (e.g. MA crossover) ...
if(!PassesAllFilters()) return; // Apply filters before opening
// ... open trade ...
}
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No trades with MTF filter | H1 trend never matches, or iBarShift returns -1 | Log H1 bar index; ensure history is loaded for PERIOD_H1. Check symbol in Market Watch. |
| Session filter blocks all | Server timezone differs from GMT | Log TimeCurrent() and dt.hour; adjust hour range for your broker (e.g. GMT+2 → use 10–22). |
| ATR filter too strict | 1.5× / 0.5× thresholds too tight | Widen range (e.g. 2.0× and 0.3×) or use different ATR period. |
| CopyRates returns -1 | No history for symbol/period | Ensure symbol in Market Watch; wait for history download. Use Bars() to check available bars. |
| Spread filter never passes | Max spread too low for symbol | Increase InpMaxSpread or log current spread to set a realistic value. |
See MQL5 Series Functions and Market Info for full reference.