Best way to code a Consolidation/Noise Filter in MQL5

 

Hi everyone,

I'm currently working on a trend-following EA, and like many of you, I'm struggling with false breakouts during market consolidation (flat markets).

To solve this, I coded a simple filter that checks the dynamic spread between two Moving Averages and ensures the current candle body size is larger than the average body size of the last 10 candles.

Here is the basic logic I am using to filter out the noise:

// Simple check to ensure we are not in a tight flat zone
bool IsMarketActive(string symbol, ENUM_TIMEFRAMES period)
{
    double maFast = iMA(symbol, period, 10, 0, MODE_EMA, PRICE_CLOSE);
    double maSlow = iMA(symbol, period, 50, 0, MODE_EMA, PRICE_CLOSE);
    
    // Calculate the distance between MAs in points
    double maDistance = MathAbs(maFast - maSlow) / SymbolInfoDouble(symbol, POINT);
    
    // Minimum distance required to consider it a trend (e.g., 150 points)
    if(maDistance < 150) 
    {
        return false; // Market is flat
    }
    
    return true; // Trend is active
}


  • My questions for the experts here:

    1. Do you think relying on MA distance is reliable enough for higher timeframes (like H1/H4)?

    2. Would it be more efficient to replace this with an ATR-based filter or candle body ratios (like looking for strong solid candles instead of dojis)?

    Looking forward to your suggestions and code improvements!


 
Sami Eid Fahid Almashaqbeh:

Hi everyone,


IMO once you have an ok system to detect false breakouts, then it is best to decide after the trade is opened -- to close with small loss OR to run with the profits. If your breakout system is reliable, then it will always recover the losses from those false breakouts.

Once you question the strategy and start to add filter after filter, you are heading down the path which leads to curve fitting.

 
I would use ATR here rather than a fixed 150-point distance
That fixed value will behave very differently on forex, gold and indices. Something like this is easier to compare between symbols..
double ratio = MathAbs(fastMA - slowMA) / atrValue;
bool marketActive = (ratio > 0.20);
The 0.20 is only a starting value and needs testing.
One small MQL5 detail in your example: iMA() returns a handle, not the MA value. Create the MA and ATR handles in OnInit(), then read the values with CopyBuffer().
I would test the ATR filter first before adding the candle-body condition. Otherwise it becomes harder to see which filter is actually improving the result.
 
Use the CODE button (Alt-S) when inserting code.

A moderator corrected the formatting this time. Please format code properly in future; posts with improperly formatted code may be removed.

Talal's ATR ratio approach is the right direction. To complete the picture on the iMA issue: in MQL5, iMA() returns an integer handle, not a price value, so the code as written assigns the handle number to a double. You need handles in OnInit() and CopyBuffer() calls in your filter function.

On your actual questions — for H1/H4, adding ADX as a second gate strengthens the filter considerably. ADX measures trend strength directly, independent of price direction, which is exactly what you want when screening out flat periods. A working pattern:

// OnInit():
int fastH = iMA(_Symbol, _Period, 10, 0, MODE_EMA, PRICE_CLOSE);
int slowH = iMA(_Symbol, _Period, 50, 0, MODE_EMA, PRICE_CLOSE);
int adxH = iADX(_Symbol, _Period, 14);
int atrH = iATR(_Symbol, _Period, 14);
// Filter function:
double fast[1], slow[1], adx[1], atr[1];
if(CopyBuffer(fastH,0,0,1,fast)<0)
   return false;
if(CopyBuffer(slowH,0,0,1,slow)<0)
   return false;
if(CopyBuffer(adxH, 0,0,1,adx) <0)
   return false;
if(CopyBuffer(atrH, 0,0,1,atr) <0)
   return false;
double maDist = MathAbs(fast[0]-slow[0]);
double ratio = (atr[0]>0) ? maDist/atr[0] : 0;
return (ratio > 0.3 && adx[0] > 20.0);

ADX > 20 means a trend is present; > 25 is stronger confirmation. The ATR-normalized ratio (0.3 is a starting threshold to tune per instrument and timeframe) makes the distance check instrument-agnostic, which solves the fixed-150-points limitation you noted. On H4 this combination removes most ranging false entries without losing many valid trend moves.

 
FYI it helps when you can visually see the consolidation marked with rectangles. I did something before, but the code needs to be cleaned up
 

Two things I'd add, coming at this as someone who builds structure-based tools.


1. MA-distance, ATR-ratio and ADX are all derived from the same price series, so they are heavily correlated - stacking them feels like three filters but carries close to one filter's worth of independent information. That is exactly the "filter after filter" path Michael warned about. The one signal genuinely orthogonal to your MA entry is market structure itself: are swings making higher highs / lower lows, or are consecutive swings overlapping around the same level? A structural range check is independent of the MA system generating the entry, which is what you actually want from a gate. Cheap version: track the last few confirmed swing points; if recent highs and lows keep landing inside the prior swing's range instead of extending it, you are consolidating regardless of what ATR says.


2. On validation - this matters more than the filter choice. Do not tune the filter by watching total backtest profit go up; that is the fastest road to the curve-fitting Michael mentioned. Test the filter in isolation: label each historical bar trend vs range with your filter, then compare the win rate of your raw entry inside each regime. If entries do not have a meaningfully higher win rate in the "trend" bucket than the "range" bucket, the filter carries no information - no matter what the equity curve does once you bolt it on. A filter that cleanly separates the two win-rates survives out of sample; one tuned to maximize P&L usually will not.


And +1 to Talal's iMA/handle correction - as written it assigns the handle integer to a double, not a price.

 
Before you write the filter, it is worth deciding how you will know whether it helped - because in my own tests, consolidation filters usually made trend systems worse, not better, and the backtest said so clearly only when I looked at the right number.

What happens is this. A trend system already loses most of its trades; it lives on a small number of large winners. A noise or consolidation filter reliably removes losing trades - that is what it is for - so the win rate goes up and the equity curve looks smoother. But it also removes some of the breakouts that turned into the big winners, because the biggest moves often start from exactly the quiet, compressed conditions the filter is designed to skip. Net profit falls even though every superficial statistic looks better.

So when you test your filter, do not judge it on win rate or on profit factor alone. Compare net profit and the size of the largest winners with and without the filter, on the same period and the same tick model. If the filter kills the top few trades, it is not a filter, it is a tax.

If you still want one, the two implementations that behaved most sanely for me were: (1) ATR relative to its own moving average - skip entries when ATR is below, say, 0.7 times its 50-period average, which is a pure volatility statement and has no fitted thresholds per symbol; and (2) the width of the Donchian channel relative to ATR - if the last N bars span less than k * ATR, the market is coiled. Both are one line and neither adds a parameter you will be tempted to over-optimise.

The one filter that did earn its place was a spread ceiling. That is not really a market filter, it is a cost filter, and it removes trades that were never profitable to begin with.