Fair Value Gap EA MQL5: Code & Trade Smart Money

Build a Fair Value Gap EA in MQL5: detect imbalances, filter by trend, set entries and stops, and avoid the overfitting trap.

fair-value-gap-ea-mql5-code-trade-smart-money

Why Fair Value Gaps Are Everywhere in 2025

If you've watched any trading content in the last two years, you've seen the term Fair Value Gap (FVG) thrown around. It's the darling of the ICT and Smart Money Concepts crowd, and for good reason: it's a clean, visual pattern that maps directly to institutional order flow. But here's the thing most traders miss — spotting an FVG on a chart is easy. Trading it consistently, with a defined edge, is a completely different animal.

That's where this article comes in. I'm going to walk you through building a proper Fair Value Gap EA MQL5 from scratch. Not a half-baked script that draws rectangles, but a full trading robot with imbalance detection, trend filtering, entry logic, and risk management you can actually backtest. You'll get the MQL5 FVG code, the reasoning behind each decision, and the honest limitations you'll hit in the Strategy Tester.

By the end, you'll have a working foundation you can adapt to your own style — and you'll know exactly why most FVG EAs fail, so you can avoid the same traps.

What an FVG Actually Is (And What It Isn't)

An FVG forms when a three-candle sequence leaves a price zone untouched. Candle one moves aggressively, candle two gaps (opens beyond candle one's high or low), and candle three fails to trade back through that entire gap. The result is a one-sided imbalance — a zone where price moved so fast that not enough orders were filled to create liquidity.

In plain terms: it's a vacuum. Price often returns to fill it before continuing, which is why traders watch for the retracement into the zone as a potential entry.

Here's the critical distinction most tutorials gloss over: an FVG is not a reversal signal. It's a continuation pattern. You want to buy when price pulls back into a bullish FVG within an uptrend, not when price is breaking down into a bearish FVG. Context matters more than the pattern itself.

For the EA, we need to define this mathematically. A bullish FVG on bar i exists when:

  • Bar 1 (momentum): the bar before the gap — it should be large and directional.
  • Bar 2 (gap): its low is higher than bar 1's high, creating the gap.
  • Bar 3 (confirmation): its low stays above bar 1's high, leaving the gap unfilled.

The FVG zone itself spans from bar 1's high to bar 2's low. That's your imbalance zone. Price will often revisit it, and that's your entry trigger.

Designing the FVG Indicator MQL5 Logic

Before we write a single line of EA code, we need the detection engine. I prefer to keep the FVG detection inside the EA itself rather than calling a separate indicator, because it gives us full control over repainting behavior and lets us backtest cleanly.

Here's the core detection function. It scans historical bars and returns the most recent unfilled FVG that meets your minimum size requirement:

//+------------------------------------------------------------------+
//| Check for a bullish FVG at bar index 'shift'                     |
//+------------------------------------------------------------------+
bool IsBullishFVG(int shift, double minSizePips, double &fvgHigh, double &fvgLow)
{
   // Bar 3 = shift, Bar 2 = shift+1, Bar 1 = shift+2
   double bar1High  = iHigh(_Symbol, PERIOD_CURRENT, shift + 2);
   double bar2Low   = iLow(_Symbol,  PERIOD_CURRENT, shift + 1);
   double bar3Low   = iLow(_Symbol,  PERIOD_CURRENT, shift);

   // Gap condition: bar2's low must be above bar1's high
   if(bar2Low <= bar1High)
      return(false);

   // Confirmation: bar3's low stays above bar1's high (gap unfilled)
   if(bar3Low <= bar1High)
      return(false);

   // Minimum size filter to avoid noise
   double gapSize = bar2Low - bar1High;
   double minSize = minSizePips * _Point * 10;
   if(gapSize < minSize)
      return(false);

   fvgHigh = bar2Low;
   fvgLow  = bar1High;
   return(true);
}

Notice I'm using the shift parameter so we can check any historical bar, not just the current one. This is essential for backtesting — you need to know when the FVG formed relative to your entry, not just whether one exists right now.

A few things I've learned the hard way:

  • Minimum size matters. A 1-pip gap on EURUSD is noise. I typically use 8–15 pips as a filter, depending on the timeframe.
  • Don't use tick volume as a proxy for imbalance. It's tempting, but tick volume on a demo feed is unreliable. Price-based size is more robust.
  • Repainting is your enemy. An FVG that forms on the current bar can vanish by the next. Always wait for bar 3 to close before confirming the pattern.

Entry Logic: Filling the Gap vs. Bouncing Off It

Once you've detected an FVG, you have two main entry philosophies. This choice defines your entire EA, so think carefully.

Option 1: Limit order at the FVG zone. Place a buy limit at the FVG low, hoping price retraces into the zone and bounces. This gives you a great risk-to-reward ratio, but you'll get stopped out often when price blows through the gap. It's the classic "catching a falling knife" scenario if you don't filter by trend.

Option 2: Breakout confirmation. Wait for price to enter the FVG and then produce a bullish reversal candle (a close above the previous bar's high). This confirms buyers are stepping in, but you sacrifice some of your reward-to-risk ratio.

I prefer a hybrid: place a limit order at the FVG low, but require a trend filter and a minimum distance from the current price. If price is more than 50 pips away from the FVG, the trade is too far from the action — skip it and wait for the next setup.

Here's the entry logic in MQL5:

//+------------------------------------------------------------------+
//| Check for entry conditions on the current bar                    |
//+------------------------------------------------------------------+
void CheckForEntry()
{
   double fvgHigh, fvgLow;
   int fvgShift = FindLatestFVG(fvgHigh, fvgLow);

   if(fvgShift < 0)
      return; // No valid FVG found

   // Trend filter: price must be above the 200 EMA for longs
   double ema200 = iMA(_Symbol, PERIOD_CURRENT, 200, 0, MODE_EMA, PRICE_CLOSE, 0);
   if(Close(0) < ema200)
      return; // Only trade in the direction of the trend

   // Distance filter: don't chase price far from the zone
   double distancePips = (Close(0) - fvgLow) / _Point / 10;
   if(distancePips > 50)
      return;

   // Entry: limit order at the FVG low
   double entryPrice = fvgLow;
   double sl = entryPrice - (10 * _Point * 10); // 10 pips below the zone
   double tp = entryPrice + (30 * _Point * 10); // 3R target

   Trade.Buy(0.1, _Symbol, entryPrice, sl, tp, "FVG Buy");
}

That's the skeleton. In practice, you'll want to add position sizing, a max spread check, and a session filter (FVG setups work best in London and New York sessions, not the Asian chop).

Risk Management That Keeps You Alive

Here's where most retail EAs die. They have brilliant entry logic and zero risk management. A Fair Value Gap EA MQL5 build needs the same discipline you'd apply to manual trading.

My non-negotiable rules:

  • Risk per trade: 0.5–1% of account equity. Never more.
  • Stop loss placement: Always beyond the FVG zone, not inside it. If you're buying at the FVG low, your stop goes 5–10 pips below that low. Price will often wick through the zone before reversing.
  • Take profit: Use a fixed risk-to-reward of 2R to 3R, or a structure-based target (previous swing high). Fixed R:R is simpler to backtest.
  • Max daily loss: A circuit breaker. If the EA loses 2% in a day, it stops trading until the next session. This protects you from correlated losses during news events.
  • Correlation check: If you run this on multiple pairs, don't let it take trades on EURUSD and GBPUSD simultaneously — they're highly correlated and you'll double your risk without realizing it.

Let me show you the position sizing calculation:

//+------------------------------------------------------------------+
//| Calculate lot size based on risk percentage                      |
//+------------------------------------------------------------------+
double CalculateLotSize(double slDistancePips)
{
   double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * 0.01; // 1% risk
   double tickValue  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);

   double lotStep    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double minLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

   double pipValue   = tickValue * (10 * _Point / tickSize);
   double rawLot     = riskAmount / (slDistancePips * pipValue);

   // Round down to the nearest lot step
   double lot = MathFloor(rawLot / lotStep) * lotStep;
   lot = MathMax(minLot, MathMin(maxLot, lot));
   return(lot);
}

That function alone separates a professional EA from a gambler. It ensures your risk is constant regardless of the pair's volatility or your account balance.

Backtesting: The Honest Truth About FVG EAs

I need to be straight with you. Backtesting an FVG strategy in MetaTrader 5 is fraught with peril, and most people who claim 90% win rates are either lying or curve-fitted.

The core problem is repainting. An FVG that forms on bar 3 might be invalidated by bar 4 if price fills the gap. Your EA must handle this correctly: it should only act on confirmed FVGs (bar 3 closed) and should never look ahead at future data.

Here's my recommended backtest workflow:

  1. Use "Every tick based on real ticks" mode. It's slower, but it's the only way to get realistic fill behavior near the FVG zone. "1 minute OHLC" will give you misleading results because it doesn't model intra-bar wicks.
  2. Test on at least 5 years of data. FVG patterns appeared in 2019 just as they do in 2025, but market microstructure changes. If your EA only works on the last 6 months, it's overfitted.
  3. Walk-forward optimization. Optimize on 2019–2022, then test on 2023–2025 without re-optimizing. If the results degrade significantly, your parameters are too fragile.
  4. Watch the max drawdown, not the profit factor. A 3.0 profit factor with a 40% drawdown will blow your account eventually. I want to see under 15% drawdown, ideally under 10%.

One more thing: the spread and slippage settings in the tester. Set them realistically. A 0.1 pip spread on EURUSD with zero slippage is fantasy. Use a spread of 1.0–1.5 pips and slippage of 2–3 points. Your live results will be worse, but at least you'll have a realistic baseline.

Pros, Cons, and the Overfitting Trap

Let's be honest about what this strategy does well and where it struggles.

AspectStrengthWeakness
Pattern FrequencyFVG appear regularly on M15 and H1, giving decent trade frequency.Too many false signals on M5 and lower; noise dominates.
Risk-to-RewardEntry at zone edge allows tight stops and 2R+ targets.Zone breaks often; stop placement is a moving target without structure.
Trend ContextWorks beautifully with a 200 EMA or market structure filter.Counter-trend FVG entries are statistically poor; avoid them.
Backtest ReliabilityClearly defined rules are easy to code and verify.Repainting and tick data quality can skew results significantly.

The biggest risk isn't the strategy itself — it's the overfitting trap. You'll be tempted to add filters: minimum FVG size, maximum distance from EMA, session time, day of week, volatility threshold. Each filter improves your backtest and makes your live results

Want to build your own version?

Recreate similar entry logic, risk rules, and filters in TradingBotMaker—no MQL coding. Start free.

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles