Why Another MA Crossover EA?
Let's be honest – the internet is flooded with moving average crossover Expert Advisors. Most of them are garbage. They look great on a clean chart, then blow up the first time price whipsaws sideways. I've tested dozens over the years, and the common failure point is simple: they trade every crossover, regardless of the broader market direction.
You've seen it happen. A 50/200 EMA crossover fires long on the 15-minute chart, you're in profit for twenty minutes, then the daily trend reverses and you're stuck holding a bag. The fix isn't a magic indicator or some neural network nonsense. It's a simple concept called multi-timeframe confirmation.
In this post, I'll walk you through building a Multi-Timeframe MA Crossover EA in MQL5 that uses a higher timeframe moving average to filter your entry signals. You'll get working code, practical parameter advice, and the honest trade-offs you need to know before running it on a live account. No hype, no guaranteed profits – just a solid strategy framework you can adapt.
The Strategy: How It Works
The core idea is boringly simple but effective. We take a standard MA crossover on your execution timeframe (say, the 5-minute chart) and only act on it when the higher timeframe trend aligns with the signal direction.
Here's the breakdown:
- Execution TF: The chart where the EA runs. We calculate a fast MA and a slow MA. A buy signal occurs when the fast MA crosses above the slow MA. A sell signal when it crosses below.
- Filter TF: A higher timeframe, typically 4-6 times larger than the execution TF. We calculate a single MA (often the same period as the slow MA or a slightly longer one). The trend is bullish if price is above this MA, bearish if below.
- Rule: Only take a buy signal if the higher timeframe trend is bullish. Only take a sell signal if it's bearish. If the higher timeframe trend is neutral (price chopping around the MA), the EA does nothing.
That's it. No magic, no extra indicators. The filter prevents you from buying into a downtrend on a small timeframe pop, which is exactly where most retail traders lose money.
Why MQL5 Over MQL4?
You might be tempted to port this to MQL4. Don't. MQL5 gives you proper multi-timeframe handling without the ugly iClose(NULL, PERIOD_H1, 1) hacks. The CopyBuffer() function is cleaner, and you can run the EA on any chart while accessing any other timeframe's data directly. Plus, the Strategy Tester in MT5 is miles ahead of MT4 for multi-timeframe backtesting – it actually simulates the higher timeframe correctly.
Building the EA: Step by Step
I'll assume you know basic MQL5 syntax. If you're new, start with a simple moving average EA first, then add the filter. Let's structure the code.
Input Parameters
These are the knobs you'll tweak. I've kept it lean – too many inputs lead to overfitting.
| Parameter | Type | Default | Description |
|---|---|---|---|
| InpFastMAPeriod | int | 10 | Fast MA period on execution timeframe |
| InpSlowMAPeriod | int | 30 | Slow MA period on execution timeframe |
| InpFilterTF | ENUM_TIMEFRAMES | PERIOD_H1 | Higher timeframe for trend filter |
| InpFilterMAPeriod | int | 50 | MA period on filter timeframe |
| InpMAMethod | ENUM_MA_METHOD | MODE_EMA | MA type: EMA, SMA, SMMA, LWMA |
| InpLotSize | double | 0.1 | Fixed lot size for trades |
| InpMagicNumber | int | 12345 | Unique ID for EA orders |
| InpStopLoss | int | 200 | Stop loss in points (0 = none) |
| InpTakeProfit | int | 400 | Take profit in points (0 = none) |
Notice the InpFilterTF is an ENUM_TIMEFRAMES. In MQL5, you can pass PERIOD_H1, PERIOD_H4, etc., directly. No more converting integers to timeframes. I've added stop loss and take profit parameters too – you'll want those for real trading.
The Core Logic in OnTick()
Here's the skeleton. I've stripped error handling for readability, but you'll want to add it.
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Check if we have a new bar on execution timeframe
if(!IsNewBar())
return;
//--- Get filter trend
double filterMA[];
ArraySetAsSeries(filterMA, true);
int filterHandle = iMA(_Symbol, InpFilterTF, InpFilterMAPeriod, 0, InpMAMethod, PRICE_CLOSE);
if(filterHandle == INVALID_HANDLE)
return;
CopyBuffer(filterHandle, 0, 0, 2, filterMA);
double filterPrice = iClose(_Symbol, InpFilterTF, 1);
bool isBullishFilter = (filterPrice > filterMA[0]);
//--- Get execution MA values
double fastMA[], slowMA[];
ArraySetAsSeries(fastMA, true);
ArraySetAsSeries(slowMA, true);
int fastHandle = iMA(_Symbol, PERIOD_CURRENT, InpFastMAPeriod, 0, InpMAMethod, PRICE_CLOSE);
int slowHandle = iMA(_Symbol, PERIOD_CURRENT, InpSlowMAPeriod, 0, InpMAMethod, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return;
CopyBuffer(fastHandle, 0, 0, 3, fastMA);
CopyBuffer(slowHandle, 0, 0, 3, slowMA);
//--- Detect crossover (bar 1 vs bar 2)
bool buySignal = (fastMA[1] > slowMA[1] && fastMA[2] <= slowMA[2]);
bool sellSignal = (fastMA[1] < slowMA[1] && fastMA[2] >= slowMA[2]);
//--- Apply filter
if(buySignal && isBullishFilter)
OpenTrade(ORDER_TYPE_BUY);
if(sellSignal && !isBullishFilter)
OpenTrade(ORDER_TYPE_SELL);
}A few things to note. First, the IsNewBar() function prevents firing multiple signals on the same bar – essential for backtesting accuracy. Second, I'm using iClose(_Symbol, InpFilterTF, 1) for the filter price, not the MA itself. Some traders use price relative to the MA, others use MA slope. I prefer price vs MA because it's less laggy. Your call.
Third, notice I copy 3 bars of MA data. The crossover is detected between bar 1 (the most recent completed bar) and bar 2 (the one before it). Bar 0 is the current forming bar – don't use it for signals in a real EA. If you use bar 0, you'll get repainting in backtests and unreliable signals live.
One edge case I've seen trip people up: what if the filter timeframe hasn't formed a new bar yet? The CopyBuffer will return the last known values, which is fine. But if you're running the EA on a very fast execution timeframe like M1 with an H4 filter, you might get stale filter data for hours. That's why the timeframe ratio matters – more on that below.
The IsNewBar() Helper
bool IsNewBar()
{
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime;
return true;
}
return false;
}Simple and essential. Without this, your EA would fire multiple signals within the same bar in backtesting and live trading. The static variable persists between ticks, so it remembers the last bar time. One gotcha: if you restart the EA or change timeframes, the static variable resets to zero, and the first tick will trigger a false new bar.






