Problem Description
Solution
To make your trading robot avoid flat (ranging) markets and trade only during volatile periods, the most robust method is to implement a Volatility Filter.
The most common and effective indicators for this in MQL4 are:
- ATR (Average True Range): Measures the size of price movement. If ATR is low, the market is flat.
- ADX (Average Directional Movement Index): Measures trend strength. If ADX is below 20-25, the market is usually directionless.
- Bollinger Band Width: Measures the distance between bands. A "squeeze" indicates low volatility.
Below is a complete, functional MQL4 Expert Advisor template. It uses ATR to detect price range expansion and ADX to ensure there is enough directional momentum before allowing a trade.
Volatility Filter EA Template
//+------------------------------------------------------------------+
//| Volatility_Filter_EA.mq4 |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- INPUT PARAMETERS ---
input string Filter_Settings = "--- Volatility Settings ---";
input bool Use_ATR_Filter = true; // Enable ATR Filter
input int ATR_Period = 14; // ATR Period
input double Min_ATR_Level = 0.0010; // Minimum ATR Value (e.g. 10 pips on EURUSD)
input bool Use_ADX_Filter = true; // Enable ADX Filter
input int ADX_Period = 14; // ADX Period
input double Min_ADX_Level = 25.0; // Minimum ADX Level (Trend Strength)
input string Trade_Settings = "--- Trading Settings ---";
input double LotSize = 0.1;
input int MagicNumber = 123456;
input int Slippage = 3;
//--- GLOBAL VARIABLES ---
int Ticket = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check if we have open orders to avoid opening multiple trades in this example
if(OrdersTotal() > 0) return;
// 2. CHECK VOLATILITY CONDITIONS
if(!IsMarketVolatile())
{
// Optional: Print comment to chart to see why it's not trading
Comment("Market is Flat. Trading Paused.");
return;
}
Comment("Market is Volatile. Searching for signals...");
// 3. YOUR STRATEGY LOGIC HERE (Example: Simple MA Crossover)
// This is just a placeholder strategy to demonstrate the filter
double maFast = iMA(NULL, 0, 10, 0, MODE_SMA, PRICE_CLOSE, 1);
double maSlow = iMA(NULL, 0, 50, 0, MODE_SMA, PRICE_CLOSE, 1);
double maFastPrev = iMA(NULL, 0, 10, 0, MODE_SMA, PRICE_CLOSE, 2);
double maSlowPrev = iMA(NULL, 0, 50, 0, MODE_SMA, PRICE_CLOSE, 2);
// Buy Signal
if(maFast > maSlow && maFastPrev <= maSlowPrev)
{
Ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, Slippage, 0, 0, "Volatile Buy", MagicNumber, 0, clrGreen);
}
// Sell Signal
if(maFast < maSlow && maFastPrev >= maSlowPrev)
{
Ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, Slippage, 0, 0, "Volatile Sell", MagicNumber, 0, clrRed);
}
}
//+------------------------------------------------------------------+
//| Function to check if market meets volatility requirements |
//+------------------------------------------------------------------+
bool IsMarketVolatile()
{
// --- Check ATR Filter ---
if(Use_ATR_Filter)
{
// Get current ATR value
double currentATR = iATR(NULL, 0, ATR_Period, 1);
// If current volatility is less than the threshold, return false
if(currentATR < Min_ATR_Level) return(false);
}
// --- Check ADX Filter ---
if(Use_ADX_Filter)
{
// Get current ADX Main line value
double currentADX = iADX(NULL, 0, ADX_Period, PRICE_CLOSE, MODE_MAIN, 1);
// If trend strength is weak (below threshold), return false
if(currentADX < Min_ADX_Level) return(false);
}
// If we passed all enabled checks, the market is volatile
return(true);
}
//+------------------------------------------------------------------+
How to Configure the Filters
1. The ATR Filter (Range Filter)
The ATR measures the average size of the candles.
- Logic:
iATR(...) < Min_ATR_Level - Configuration: You need to set
Min_ATR_Levelbased on the timeframe and symbol you are trading.- Example: On EURUSD H1, an ATR of
0.0005(5 pips) is very flat. An ATR of0.0020(20 pips) is volatile. - How to find the number: Attach the standard ATR indicator to your chart. Look at the values during the night (flat) vs. the day (volatile). Set your input slightly above the "night" value.
- Example: On EURUSD H1, an ATR of
2. The ADX Filter (Trend Filter)
The ADX measures the strength of the trend, regardless of direction.
- Logic:
iADX(...) < Min_ADX_Level - Configuration:
- < 20: The market is ranging/flat.
- > 25: A trend is forming.
- > 40: Strong trend.
- Recommendation: Set
Min_ADX_Levelto 20 or 25. This effectively blocks trades when the market is chopping sideways.
Integration into Your Existing Robot
If you already have a robot, copy the IsMarketVolatile() function from the code above to the bottom of your file. Then, inside your OnTick() function, wrap your trade logic like this:
void OnTick()
{
// ... existing code ...
// Only proceed to trade logic if volatility is high enough
if(IsMarketVolatile() == false) return;
// ... your entry logic (OrderSend) ...
}
Q&A: Volatility Filtering
Q: Why does my robot still trade in flat markets even with ADX?
A: ADX is a lagging indicator. If a sudden spike occurs, ADX might still be rising from a low number. Combining ADX with ATR helps because ATR reacts faster to immediate price expansion.
Q: Can I use time filters instead?
A: Yes. Often, "flat" markets are simply specific times of day (e.g., Asian Session for GBPUSD). You can add a simple time check: if(Hour() < 8 || Hour() > 20) return;. However, technical indicators (ATR/ADX) are safer because they handle holidays or unusual calm periods during active hours.
Q: How do I handle different currency pairs with ATR?
A: ATR is an absolute value. 0.0010 on EURUSD is different from 0.50 on Gold (XAUUSD). If you trade multiple pairs, you must adjust the Min_ATR_Level input for each specific chart.