Fair Value Gaps (FVG)

Detect and trade price imbalances — three-bar patterns where price moved so fast that the surrounding candle wicks don't overlap — using Pine Script box drawing.

A fair value gap (FVG) is a three-bar price pattern where a middle candle moves so strongly that the high of the bar two bars ago and the low of the current bar don't overlap. The gap between those two levels is the "fair value gap" — a zone of price that was essentially skipped over as the market moved quickly in one direction.

The concept comes from the ICT (Inner Circle Trader) methodology but is grounded in straightforward market mechanics. When price moves very quickly, limit orders sitting in that price range don't get filled. Those unfilled orders remain in the market's order book, and price frequently returns to that level — "filling the gap" — before resuming the original direction. This return to the FVG is called mitigation.

FVGs are particularly useful because they are fully algorithmic — no discretion is required. The three-bar pattern either meets the definition or it doesn't, making them easy to detect in Pine Script and incorporate into systematic strategies.

The anatomy of a fair value gap

Bullish FVG (gap up)

The condition: `low > high[2]` — the current bar's low is above the bar-two-ago high. The price range between `high[2]` and `low` was never traded during the gap candle's movement. That unfilled zone is the bullish FVG.

Bearish FVG (gap down)

The condition: `high < low[2]` — the current bar's high is below the bar-two-ago low.

Why FVGs act as support and resistance

When price moves through a level rapidly, limit buy orders sitting at those levels are not filled. The participants who wanted to buy at $150 but whose orders were skipped over as price gapped from $148 to $153 still have those orders open. When price returns to $150–$153, those orders execute, providing support.

The same logic applies to bearish FVGs: limit sell orders in the gap become resistance when price returns.

This is why FVGs function as "magnets" — the unfilled order book interest draws price back. Many experienced traders treat an unmitigated FVG on a higher timeframe as a high-probability target for a retracement.

The mitigation concept

A FVG is "mitigated" when price trades back into it. The code removes the box when `low < box.get_top(b)` (for bullish FVGs) — the current bar's low has dipped below the upper edge of the gap, meaning price entered the zone.

Strict mitigation requires the gap to be fully covered (price traded through the entire gap). Loose mitigation (used here) triggers when price first touches the gap's edge. Stricter definitions only remove the box when price closes inside the gap, not just wicks into it.

Using FVGs in a strategy

FVGs have several roles in a systematic strategy:

### Entry zone After a strong impulsive move that creates a bullish FVG, wait for price to pull back into the FVG zone. An entry at the 50% level of the FVG (the midpoint) with a stop below the gap's lower edge is a clean, defined-risk setup.

### Target If price is in an uptrend and a bearish FVG exists above current price from a prior down-move, that FVG is a logical take-profit target. Price often stalls when it encounters an unmitigated opposing FVG.

### Confluence with other levels FVGs that align with session highs and lows, VWAP, or previous day levels carry more weight than isolated FVGs. Confluence increases the probability that the level will hold.

Timeframe selection

FVGs on higher timeframes carry more significance than those on lower timeframes because they represent larger unfilled order imbalances. A daily chart FVG may act as support or resistance for days. A 1-minute FVG may last only a few bars.

A common approach is to identify FVGs on a higher timeframe (15-min, 1-hour, 4-hour) and then use a lower timeframe for entry timing — entering on a lower-timeframe signal when price is testing a higher-timeframe FVG.

//@version=6
indicator("Fair Value Gaps (FVG)", overlay=true, max_boxes_count=200)

maxFVGs    = input.int(15,   "Max FVGs to display", minval=1, maxval=50)
extBars    = input.int(30,   "Extend right (bars)",  minval=1, maxval=200)
showMitig  = input.bool(true, "Remove when mitigated (price enters gap)")

// ── FVG Detection ──────────────────────────────────────────────────────
// Bullish FVG: current bar's low is above bar[2]'s high — gap up
bullFVG = low > high[2]
// Bearish FVG: current bar's high is below bar[2]'s low — gap down
bearFVG = high < low[2]

// ── Draw zones as persistent boxes ────────────────────────────────────
var box[] bullBoxes = array.new<box>(0)
var box[] bearBoxes = array.new<box>(0)

if bullFVG
    b = box.new(bar_index[2], low, bar_index + extBars, high[2],
                bgcolor      = color.new(color.green, 88),
                border_color = color.new(color.green, 50),
                border_width = 1)
    array.push(bullBoxes, b)
    if array.size(bullBoxes) > maxFVGs
        box.delete(array.shift(bullBoxes))

if bearFVG
    b = box.new(bar_index[2], low[2], bar_index + extBars, high,
                bgcolor      = color.new(color.red, 88),
                border_color = color.new(color.red, 50),
                border_width = 1)
    array.push(bearBoxes, b)
    if array.size(bearBoxes) > maxFVGs
        box.delete(array.shift(bearBoxes))

// ── Extend boxes and check mitigation on each bar ─────────────────────
for i = array.size(bullBoxes) - 1 to 0
    b = array.get(bullBoxes, i)
    box.set_right(b, bar_index + extBars)
    // Mitigated when price trades back into the gap (below gap top)
    if showMitig and low < box.get_top(b)
        box.delete(b)
        array.remove(bullBoxes, i)

for i = array.size(bearBoxes) - 1 to 0
    b = array.get(bearBoxes, i)
    box.set_right(b, bar_index + extBars)
    // Mitigated when price trades back into the gap (above gap bottom)
    if showMitig and high > box.get_bottom(b)
        box.delete(b)
        array.remove(bearBoxes, i)