
The Inverse Fair Value Gap Trading Strategy
Introduction
An inverse fair value gap(IFVG) occurs when price returns to a previously identified fair value gap and, instead of showing the expected supportive or resistive reaction, fails to respect it. This failure can signal a potential shift in market direction and offer a contrarian trading edge. In this article, I'm going to introduce my self-developed approach to quantifying and utilizing inverse fair value gap as a strategy for MetaTrader 5 expert advisors.
Strategy Motivation
Understanding Fair Value Gaps (FVGs) First
To fully appreciate the intuition behind an "inverse fair value gap," it helps to start with what a standard fair value gap (FVG) represents. A fair value gap is typically defined within a three-candle price pattern.
A FVG occurs when Candle B’s body (and often wicks) launches the market price abruptly upward or downward in such a way that there’s a “gap” left behind. More concretely, if the low of Candle C is higher than the high of Candle A in a strong upward move, the space between these two price points is considered a fair value gap. This gap reflects a zone of inefficiency or imbalance in the market—an area where trades didn’t get properly two-sided participation because price moved too quickly in one direction. Traders often assume that institutional order flow caused this displacement, leaving “footprints” of big money activity.
The common logic is that price, at some point, often returns to these gaps to “fill” them. Filling the gap can be seen as the market’s way of balancing out the order flow that was previously left one-sided. Traders who follow this principle often wait for price to revisit this gap, looking for a reaction that confirms continuation in the original direction, or sometimes a reversal.
What is an Inverse Fair Value Gap?
The concept of an “inverse fair value gap” builds upon this idea but approaches it from a contrarian or reverse-engineered perspective. Rather than using the fair value gap as a zone to confirm continuation in the original direction, an inverse FVG strategy might use that very gap to anticipate where the market could fail to follow through and possibly reverse.
For example, to distinguish a bearish inverse fair value gap, one can follow these steps:
- Identify a bullish FVG.
- Price returns to the FVG zone.
- Instead of respecting it as support, observe how price behaves. If it fails to launch upward and instead trades through the gap as if it’s not providing meaningful support, that failure could signal a momentum shift.
- Go short, anticipating that the inability to use the FVG as a stepping-stone for higher prices means the market might now head lower.
The Intuition Behind Inverse Fair Value Gaps
- Institutional Footprints and Failure Points: The underlying assumption behind fair value gaps is that large, sophisticated players created the initial imbalance. When price returns to these zones, it’s often a test: if the large players still see value at these prices, their lingering orders may support or resist price, causing a reaction. If price instead slices right through the FVG without a strong rebound or continuation, it suggests those big orders might have been filled, canceled, or are no longer defending that zone. This can indicate a shift in market intent.
- Detecting Weakness or Strength Early: By focusing on what doesn’t happen when price returns to the gap, traders can glean subtle clues about underlying strength or weakness. If a bullish market can’t get a lift from a known inefficiency zone (the bullish FVG), it may be providing early warning that the bullish narrative could be losing steam.
- Complements Traditional FVG Strategies: Traditional FVG strategies rely on the assumption of a rebalancing followed by a continuation in the original direction. However, markets are dynamic, and not every gap fill leads to a resumption of the previous trend. The inverse FVG approach can give a trader an additional “edge” by identifying situations where the normal playbook fails, and thus a contrarian move may have higher probability and better risk/reward.
The concept of inverse fair value gaps is grounded in the recognition that markets are constantly testing and re-testing areas of prior imbalance. While traditional FVG trading focuses on successful rebalancing and continuation, the inverse approach gains an edge by identifying when this rebalancing process fails to yield the expected outcome. This shift in perspective turns what could have been a missed opportunity—or even a losing proposition—into a contrarian setup with a potentially high edge. In a market environment where anticipating the unexpected is often rewarded, the inverse FVG concept adds an extra tool to a trader’s arsenal of technical analysis techniques.
Strategy Development
Similar to how discretionary traders utilize fair value gaps, inverse fair value gaps are also actively traded due to the sophisticated criteria required to identify valid patterns. Trading every single inverse fair value gap without discrimination would likely result in random walk performance, as most gaps do not align with the strategic intuition I previously discussed. In an effort to quantify the feature setups that discretionary traders consider, I conducted extensive feature testing and established the following rules:
-
Alignment with the Macro Trend: The price should follow the overarching macro trend, which is determined by its position relative to the 400-period moving average.
-
Appropriate Timeframe Selection: A low timeframe, such as 1 to 5 minutes, should be used because the concept of "filling orders" occurs within a short duration. For the purposes of this article, a 3-minute timeframe is utilized.
-
Focus on the Most Recent Fair Value Gap: Only the most recent fair value gap (FVG) is considered, as it holds the highest significance in reflecting current market conditions.
-
Fair Value Gap Size Validation: The FVG must neither be too large nor too small compared to surrounding candles. A gap that is too small lacks the significance to act as a reliable support or resistance level, while a gap that is too large is likely caused by a news event, which can delay the reversal signal. To ensure the FVG is meaningful, specific thresholds are set to validate each gap.
-
Controlled Breakout Candle Size: Similarly, the breakout candle should not be excessively large since entries are based on candle closes. Large breakout candles can lead to late signals, which the strategy aims to avoid.
-
Timely Price Reversal and Breakout: Within a specified time after the formation of an FVG, the price must reverse back to the gap and break out from the opposite edge with a closed candle. This is achieved by only examining the most recent FVG within a short look-back period.
-
Breakout Strength Confirmation: The FVG should align with a previous rejection level, ensuring that a breakout of the FVG signals increased strength in the corresponding direction.
Now, let's walk through the code.
Firstly, we declare the necessary global variables. These global variables hold key data for tracking Fair Value Gaps (FVGs), current open trades, and the state of the system. Variables like previousGapHigh, previousGapLow, and lastGapIndex help track the most recent identified gap. handleMa will store the moving average handle. The buypos and sellpos track open trade tickets, while currentFVGstatus and newFVGformed track the condition of the last identified FVG.
string previousGapObjName = ""; double previousGapHigh = 0.0; double previousGapLow = 0.0; int LastGapIndex = 0; double gapHigh = 0.0; double gapLow = 0.0; double gap = 0.0; double lott= 0.1; ulong buypos = 0, sellpos = 0; double anyGap = 0.0; double anyGapHigh = 0.0; double anyGapLow = 0.0; int barsTotal = 0; int newFVGformed = 0; int currentFVGstatus = 0; int handleMa; #include <Trade/Trade.mqh> CTrade trade;
Next, we declare the following functions to execute trades with take profit and stop loss, and to track the order ticket for each trade.
//+------------------------------------------------------------------+ //| Store order ticket number into buypos/sellpos variables | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if (trans.type == TRADE_TRANSACTION_ORDER_ADD) { COrderInfo order; if (order.Select(trans.order)) { if (order.Magic() == Magic) { if (order.OrderType() == ORDER_TYPE_BUY) { buypos = order.Ticket(); } else if (order.OrderType() == ORDER_TYPE_SELL) { sellpos = order.Ticket(); } } } } } //+------------------------------------------------------------------+ //| Execute sell trade function | //+------------------------------------------------------------------+ void executeSell() { if (IsWithinTradingHours()){ double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); double tp = bid - tpp * _Point; tp = NormalizeDouble(tp, _Digits); double sl = bid + slp * _Point; sl = NormalizeDouble(sl, _Digits); trade.Sell(lott,_Symbol,bid,sl,tp); sellpos = trade.ResultOrder(); } } //+------------------------------------------------------------------+ //| Execute buy trade function | //+------------------------------------------------------------------+ void executeBuy() { if (IsWithinTradingHours()){ double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); double tp = ask + tpp * _Point; tp = NormalizeDouble(tp, _Digits); double sl = ask - slp * _Point; sl = NormalizeDouble(sl, _Digits); trade.Buy(lott,_Symbol,ask,sl,tp); buypos= trade.ResultOrder(); } } //+------------------------------------------------------------------+ //| Check if is trading hours | //+------------------------------------------------------------------+ bool IsWithinTradingHours() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; if (currentHour >= startHour && currentHour < endHour) return true; else return false; }
Then, we use these two functions to validate a fair value gap. IsReacted() checks that within the look-back period, there are at least two candle wicks inside the price range of the current FVG, which we interpret as a sign of previous rejection of the FVG. IsGapValid() then verifies that the gap size is within our desired range, returning either true or false.
//+------------------------------------------------------------------+ //| Function to validate the FVG gap | //+------------------------------------------------------------------+ bool IsGapValid(){ if (anyGap<=gapMaxPoint*_Point && anyGap>=gapMinPoint*_Point&&IsReacted()) return true; else return false; } //+------------------------------------------------------------------+ //| Check for gap reaction to validate its strength | //+------------------------------------------------------------------+ bool IsReacted(){ int count1 = 0; int count2 = 0; for (int i = 4; i < lookBack; i++){ double aLow = iLow(_Symbol,PERIOD_CURRENT,i); double aHigh = iHigh(_Symbol,PERIOD_CURRENT,i); if (aHigh<anyGapHigh&&aHigh>anyGapLow&&aLow<anyGapLow){ count1++; } else if (aLow<anyGapHigh&&aLow>anyGapLow&&aHigh>anyGapHigh){ count2++; } } if (count1>=2||count2>=2) return true; else return false; }
After that, we use these functions to check whether there is a breakout currently on the last FVG.
//+------------------------------------------------------------------+ //| Check if price broke out to the upside of the gap | //+------------------------------------------------------------------+ bool IsBrokenUp(){ int lastClosedIndex = 1; double lastOpen = iOpen(_Symbol, PERIOD_CURRENT, lastClosedIndex); double lastClose = iClose(_Symbol, PERIOD_CURRENT, lastClosedIndex); if (lastOpen < gapHigh && lastClose > gapHigh&&(lastClose-gapHigh)<maxBreakoutPoints*_Point) { if(currentFVGstatus==-1){ return true;} } return false; } //+------------------------------------------------------------------+ //| Check if price broke out to the downside of the gap | //+------------------------------------------------------------------+ bool IsBrokenLow(){ int lastClosedIndex = 1; double lastOpen = iOpen(_Symbol, PERIOD_CURRENT, lastClosedIndex); double lastClose = iClose(_Symbol, PERIOD_CURRENT, lastClosedIndex); if (lastOpen > gapLow && lastClose < gapLow&&(gapLow -lastClose)<maxBreakoutPoints*_Point) { if(currentFVGstatus==1){ return true;} } return false; }
Finally, we use these two functions to check gap validity with IsGapValid() and, if valid, updates global variables, marks the FVG as new, and draws it on the chart. The getFVG() function is essential to coding the entire strategy. We call it on every new bar to check whether there is a valid FVG. If the FVG is valid, we verify whether it is different from the last one we saved, and if so, we save it to the global variable to update the status.
//+------------------------------------------------------------------+ //| To get the most recent Fair Value Gap (FVG) | //+------------------------------------------------------------------+ void getFVG() { // Loop through the bars to find the most recent FVG for (int i = 1; i < 3; i++) { datetime currentTime = iTime(_Symbol,PERIOD_CURRENT, i); datetime previousTime = iTime(_Symbol,PERIOD_CURRENT, i + 2); // Get the high and low of the current and previous bars double currentLow = iLow(_Symbol,PERIOD_CURRENT, i); double previousHigh = iHigh(_Symbol,PERIOD_CURRENT, i+2); double currentHigh = iHigh(_Symbol,PERIOD_CURRENT, i); double previousLow = iLow(_Symbol,PERIOD_CURRENT, i+2); anyGap = MathAbs(previousLow - currentHigh); // Check for an upward gap if (currentLow > previousHigh) { anyGapHigh = currentLow; anyGapLow = previousHigh; //Check for singular if (LastGapIndex != i){ if (IsGapValid()){ gapHigh = currentLow; gapLow = previousHigh; gap = anyGap; currentFVGstatus = 1;//bullish FVG DrawGap(previousTime,currentTime,gapHigh,gapLow); LastGapIndex = i; newFVGformed =1; return; } } } // Check for a downward gap else if (currentHigh < previousLow) { anyGapHigh = previousLow; anyGapLow = currentHigh; if (LastGapIndex != i){ if(IsGapValid()){ gapHigh = previousLow; gapLow = currentHigh; gap = anyGap; currentFVGstatus = -1; DrawGap(previousTime,currentTime,gapHigh,gapLow); LastGapIndex = i; newFVGformed =1; return; } } } } } //+------------------------------------------------------------------+ //| Function to draw the FVG gap on the chart | //+------------------------------------------------------------------+ void DrawGap(datetime timeStart, datetime timeEnd, double gaphigh, double gaplow) { // Delete the previous gap object if it exists if (previousGapObjName != "") { ObjectDelete(0, previousGapObjName); } // Generate a new name for the gap object previousGapObjName = "FVG_" + IntegerToString(TimeCurrent()); // Create a rectangle object to highlight the gap ObjectCreate(0, previousGapObjName, OBJ_RECTANGLE, 0, timeStart, gaphigh, timeEnd, gaplow); // Set the properties of the rectangle ObjectSetInteger(0, previousGapObjName, OBJPROP_COLOR, clrRed); ObjectSetInteger(0, previousGapObjName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, previousGapObjName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, previousGapObjName, OBJPROP_RAY, false); // Update the previous gap information previousGapHigh = gaphigh; previousGapLow = gaplow; }
And we integrate all the strategy rules together in the OnTick() function like this, and we are done.
//+------------------------------------------------------------------+ //| OnTick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; double ma[]; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); CopyBuffer(handleMa,BASE_LINE,1,1,ma); if (IsBrokenLow()&&sellpos == buypos&&newFVGformed ==1&&bid<ma[0]){ executeSell(); newFVGformed =0; } else if (IsBrokenUp()&&sellpos == buypos&&newFVGformed ==1&&ask>ma[0]){ executeBuy(); newFVGformed =0; } getFVG(); if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; } } }
Some side notes, we call this at the beginning of the OnTick() function so that it only process the rest of the lines after a new bar has formed. This measure saves computing power.
int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars;
Besides, because we only want one trade at a time, we can only enter a trade when both ticket are set to 0 with this logic, which checks there are no current positions opened by this particular EA.
if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; }
Quick summary:
- Global Declarations & Inputs: Setup environment, variables, and user-configurable parameters.
- Initialization (OnInit): Prepare the moving average filter and set magic numbers.
- OnTick Logic: The main workflow—checks for new bars, detects FVGs, checks breakouts, and executes trades if conditions are met.
- FVG Detection (getFVG, IsGapValid, IsReacted): Identify and validate Fair Value Gaps and their market reactions.
- Breakout Checks (IsBrokenUp, IsBrokenLow): Confirm breakout direction for trade entries.
- Trade Management (OnTradeTransaction, executeBuy, executeSell): Handle order tickets and ensure that trades are placed correctly.
- Charting (DrawGap): Visualize identified FVGs.
- Time Filtering (IsWithinTradingHours): Restrict trading to specific hours.
Strategy Testing
The strategy works best on stock indices due to their relatively low spreads and high volatility, which are beneficial for retail intraday trading. We will test this strategy by trading the Nasdaq 100 index from January 1, 2020, to December 1, 2024, on the 3-minute (M3) timeframe. Here are the parameters I have chosen for this strategy.
Here are a few recommendations for choosing the parameter values for the strategy:
- Set the trading time during periods of high market volatility, typically when the stock market is open. This timing depends on your broker's server time. For example, with my server time (GMT+0), the stock market is open from around 14:00 to 19:00.
- A Reward to Risk Ratio greater than one is recommended because we are riding the macro trend in a highly volatile market. Additionally, avoid setting the take profit and stop loss (TPSL) levels too high or too low. If TPSL is too large, it won’t effectively capture the short-term pattern signals, and if it’s too small, spreads can negatively impact the trade.
- Do not excessively tune the values for gap thresholds, breakout candle thresholds, and the look-back period. Keep these parameters within a reasonable range relative to the price range of the traded security to avoid overfitting.
Now here is the backtest result:
We can see that the strategy has performed very consistently over the past five years, indicating its potential for profitability.
A typical trade in the strategy tester visualization part would be like this:
I encourage readers to build upon this strategy framework and add their creativity to improve it. Here are some of my suggestions:
- The strength of the IFVG is determined by the number of rejection candles around the FVG area. You can use the difference in these numbers as a rule for evaluation.
- In this article, we only focused on the max breakout points. However, sometimes the breakout candle may be too small, indicating weak breakout strength, which could negatively affect trend continuation. You can consider adding a threshold for the minimum breakout points as well.
- The exit rule is defined by take profit and stop loss. Alternatively, you can set the exit level based on relevant key levels for both directions over a given look-back period, or establish a fixed exit time.
Conclusion
In this article, I introduced my self-developed approach to quantifying and utilizing inverse fair value gaps as a strategy for MetaTrader 5 expert advisors, covering strategy motivation, development, and testing. This strategy demonstrates high profitability potential, having performed consistently over the past five years with more than 400 trades. Further modifications can be made to adapt this strategy to different securities and timeframes. The full code is attached below, and you are welcome to integrate it into your own trading developments.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use