Skip to content

Free TradingView indicator

Fair Value Gaps

Draws three-candle fair value gaps as boxes that extend until filled, with an optional minimum gap size in ATR.

Smart moneyv1.0TradingView (Pine Script v6) Non-repainting signals Alerts

What it does

A fair value gap (FVG), sometimes called an imbalance, is a three-candle pattern where price moved so quickly that the first and third candles do not overlap. This indicator finds those gaps on closed candles, draws each one as a box and keeps extending it to the right until price comes back and fills it. You can filter out small gaps with an ATR-based minimum size.

How it works

For each closed candle the script compares it with the candle two bars earlier:

  • Bullish gap: the current low is above the high of two candles ago. The box runs from that earlier high (bottom) to the current low (top).
  • Bearish gap: the current high is below the low of two candles ago. The box runs from the current high (bottom) to that earlier low (top).

If a minimum size is set, the gap height must be at least that multiple of ATR (14). Open gaps are then checked on every closed candle. With "Full fill", a bullish gap is filled when a low reaches the bottom of the box. "Midpoint" uses the 50% line and "First touch" uses the near edge. With shrinking on, a partial fill trims the box so it only shows the part of the gap price has not yet traded through.

Filled gaps are deleted or kept in grey. Both lists are capped, so the script stays within TradingView's drawing limits.

How to use it

  • Gaps show where price moved one-sidedly. Traders often watch for price to revisit them, and some treat a partly filled gap in the direction of the trend as a possible area for a pullback to pause.
  • Gaps work best with context. A bullish gap formed after a break of structure, or near a higher-timeframe support level, carries more weight than a random gap inside a range.
  • On M1 and M5 charts, raise the minimum size to 0.2 to 0.5 ATR to cut out noise. On H1 and above, the default of 0 is usually fine.
  • Use "First touch" if you want an alert when price first returns, and "Full fill" if you only want to retire gaps that are completely closed.

Alerts: new bullish FVG, new bearish FVG, bullish FVG filled, bearish FVG filled.

Settings

InputDefaultWhat it changes
Minimum gap size (x ATR)0Filters small gaps
ATR length14ATR used for the size filter
Show bullish / bearish gapsOn / OnWhich side is drawn
Gap counts as filled onFull fillFull fill, midpoint or first touch
Shrink box as price fills itOnTrims partly filled gaps
Keep filled gaps (greyed)OffLeaves filled gaps visible
Max open gaps per side20Cap on live gaps
Max filled gaps kept20Cap on grey gaps

Limitations

Not every gap gets filled, and many fill only after a long time, so an open gap is not a target or a prediction. In trending markets gaps can pile up without price returning to them. Forex prices trade nearly around the clock, but weekend gaps and very thin sessions create gaps that mean little. Spread and different data feeds can change which candles form a gap, so two brokers may show slightly different results.

Pine Script source

In TradingView open Pine Editor, paste the code, click Save and then Add to chart.

FXP_FairValueGaps.pine

119 lines

// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/fair-value-gaps/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Draws three-candle fair value gaps (imbalances) as boxes that extend to the right
// until price fills them. Gaps are detected on closed candles only.
indicator("FXP Fair Value Gaps", shorttitle = "FXP FVG", overlay = true, max_boxes_count = 500)

// ── Inputs ─────────────────────────────────────────────────────────────
grpDet    = "Detection"
minAtr    = input.float(0.0, "Minimum gap size (x ATR)", minval = 0.0, step = 0.05, group = grpDet, tooltip = "Ignore gaps smaller than this many ATRs. 0 = keep every gap.")
atrLen    = input.int(14, "ATR length", minval = 1, group = grpDet)
showBull  = input.bool(true, "Show bullish gaps", group = grpDet)
showBear  = input.bool(true, "Show bearish gaps", group = grpDet)

grpFill   = "Filling"
fillMode  = input.string("Full fill", "Gap counts as filled on", options = ["Full fill", "Midpoint", "First touch"], group = grpFill)
shrink    = input.bool(true, "Shrink box as price fills it", group = grpFill)
keepFill  = input.bool(false, "Keep filled gaps (greyed)", group = grpFill)

grpDisp   = "Display"
maxActive = input.int(20, "Max open gaps per side", minval = 1, maxval = 100, group = grpDisp)
maxFilled = input.int(20, "Max filled gaps kept", minval = 0, maxval = 100, group = grpDisp)
colBull   = input.color(color.new(#26a69a, 80), "Bullish gap", group = grpDisp)
colBear   = input.color(color.new(#ef5350, 80), "Bearish gap", group = grpDisp)
colFilled = input.color(color.new(color.gray, 90), "Filled gap", group = grpDisp)

// ── Types and storage ──────────────────────────────────────────────────
type Gap
    box   bx
    float top
    float bottom
    float mid

var array<Gap> bullGaps   = array.new<Gap>()
var array<Gap> bearGaps   = array.new<Gap>()
var array<Gap> filledGaps = array.new<Gap>()

closeGap(Gap g) =>
    if keepFill and maxFilled > 0
        g.bx.set_right(bar_index)
        g.bx.set_bgcolor(colFilled)
        g.bx.set_border_color(color.new(color.gray, 70))
        filledGaps.push(g)
        while filledGaps.size() > maxFilled
            Gap old = filledGaps.shift()
            old.bx.delete()
    else
        g.bx.delete()
    true

// ── Update open gaps (closed bars) ─────────────────────────────────────
bool bullFilled = false
bool bearFilled = false

if bullGaps.size() > 0
    for i = bullGaps.size() - 1 to 0
        Gap g = bullGaps.get(i)
        if barstate.isconfirmed
            float lvl = fillMode == "First touch" ? g.top : fillMode == "Midpoint" ? g.mid : g.bottom
            if low <= lvl
                bullFilled := true
                bullGaps.remove(i)
                closeGap(g)
            else
                if shrink and low < g.top
                    g.top := math.max(low, g.bottom)
                    g.bx.set_top(g.top)
                g.bx.set_right(bar_index)
        else
            g.bx.set_right(bar_index)

if bearGaps.size() > 0
    for i = bearGaps.size() - 1 to 0
        Gap g = bearGaps.get(i)
        if barstate.isconfirmed
            float lvl = fillMode == "First touch" ? g.bottom : fillMode == "Midpoint" ? g.mid : g.top
            if high >= lvl
                bearFilled := true
                bearGaps.remove(i)
                closeGap(g)
            else
                if shrink and high > g.bottom
                    g.bottom := math.min(high, g.top)
                    g.bx.set_bottom(g.bottom)
                g.bx.set_right(bar_index)
        else
            g.bx.set_right(bar_index)

// ── Detect new gaps ────────────────────────────────────────────────────
float atr = ta.atr(atrLen)
float minSize = minAtr * nz(atr)

bool bullGap = barstate.isconfirmed and showBull and low > high[2] and (low - high[2]) >= minSize
bool bearGap = barstate.isconfirmed and showBear and high < low[2] and (low[2] - high) >= minSize

if bullGap
    float t = low
    float b = high[2]
    box bx = box.new(bar_index - 2, t, bar_index, b, border_color = color.new(colBull, 50), bgcolor = colBull, text = "FVG", text_color = color.new(colBull, 20), text_size = size.tiny, text_halign = text.align_right)
    bullGaps.push(Gap.new(bx, t, b, (t + b) / 2))
    while bullGaps.size() > maxActive
        Gap old = bullGaps.shift()
        old.bx.delete()

if bearGap
    float t = low[2]
    float b = high
    box bx = box.new(bar_index - 2, t, bar_index, b, border_color = color.new(colBear, 50), bgcolor = colBear, text = "FVG", text_color = color.new(colBear, 20), text_size = size.tiny, text_halign = text.align_right)
    bearGaps.push(Gap.new(bx, t, b, (t + b) / 2))
    while bearGaps.size() > maxActive
        Gap old = bearGaps.shift()
        old.bx.delete()

// ── Alerts ─────────────────────────────────────────────────────────────
alertcondition(bullGap, "New bullish FVG", "{{ticker}} {{interval}}: new bullish fair value gap")
alertcondition(bearGap, "New bearish FVG", "{{ticker}} {{interval}}: new bearish fair value gap")
alertcondition(bullFilled, "Bullish FVG filled", "{{ticker}} {{interval}}: bullish fair value gap filled at {{close}}")
alertcondition(bearFilled, "Bearish FVG filled", "{{ticker}} {{interval}}: bearish fair value gap filled at {{close}}")

Key inputs

Key inputs of Fair Value Gaps
InputDefaultDescription
Minimum gap size0 × ATRIgnore gaps smaller than this many ATRs (0 = keep all).
Gap counts as filled onFull fillFull fill, midpoint or first touch.
Shrink box as price fills itOnTrims the box to the part of the gap that is still open.
Keep filled gapsOffLeave filled gaps on the chart in grey.
Max open gaps per side20Oldest open gaps are removed beyond this number.

Educational tools, not advice. Indicators and Expert Advisors describe price; they do not predict it, and past behaviour does not guarantee future results. Test everything on a demo account first and never risk money you cannot afford to lose. See our disclaimer.

Keep exploring

Related tradingview indicators