A fair value gap (FVG) is the hole a fast candle leaves behind: three candles in a row where the wicks of the first and the third do not overlap. Many traders treat that hole as a zone price is likely to come back to.
What happens when price does come back and goes straight through it? The zone does not simply stop mattering. A bullish gap that has been broken from above often works as resistance on the next visit, and a broken bearish gap as support. That flipped zone is called a breaker.
FVG Breaker Zones follows every gap through that whole life: it detects it, tracks it while it holds, turns it into a breaker when it breaks, and removes the breaker once price goes back through its other side. Older zones that overlap the newest ones are hidden, so the chart keeps only the levels that still mean something.
An optional filter, the ATR threshold, looks at the middle candle of the three: for a bullish gap it must close above its own low by at least that many ATR(200), and for a bearish gap below its own high. With the default value of 0 the filter only rejects a middle candle that closes exactly on its low (or high).
The gap is registered one bar after the pattern is complete. Its top and bottom are the two wicks that leave the hole, and the zone starts on the first of the three candles. The indicator needs 200 bars of history for its ATR before it starts detecting gaps.
On every bar, each gap that has not been broken yet is checked against the current candle with the chosen mitigation method:
A bullish gap broken downwards becomes a bearish breaker; a bearish gap broken upwards becomes a bullish breaker. The original gap is drawn up to the bar of the break, and the breaker from there on, with a border, a dashed midline and a “BULL BRK FVG” or “BEAR BRK FVG” label.
A breaker is removed when price goes back through its other side, using the same method: a bearish breaker (an old bullish gap) disappears when price climbs back above its top, and a bullish breaker when price falls back below its bottom.
With Hide Overlap on, after the breaks are processed:
This is what keeps the chart readable in a trend: each new gap replaces the older ones it sits on top of.
These are reading ideas, not tested trading rules.
All parameters sit at the top of the code. Declare them as variables to change them from the indicator settings:
Colours are set in the drawing section at the end of the code.
//--------------------------------------------
// PRC_FVG Breaker Zones
// version = 0
// 02.11.2026
// Iván González @ www.prorealcode.com
// Sharing ProRealTime knowledge
//--------------------------------------------
// Fair value gaps that flip into breakers.
// - A bullish FVG (low above the high of two bars back) turns into a
// BEARISH breaker when price breaks below it. A bearish FVG turns
// into a BULLISH breaker when price breaks above it.
// - A breaker is removed when price goes back through its other side.
// - Older zones that overlap the newest ones are hidden.
// - An FVG is registered one bar after its 3-candle pattern completes.
//--------------------------------------------
DEFPARAM DRAWONLASTBARONLY = true
// ===================== FAIR VALUE GAPS =====================
showRegularFVG = 1 // 1 = show FVGs that have not been broken yet
showLast = 30 // zones drawn per direction, newest first (1 to 100)
fvgThreshold = 0 // ATR(200) multiple the middle candle must close beyond its low/high (0 to 2)
mitigationMethod = 0 // 0 = Close (candle body), 1 = Wick, 2 = Avg (zone midpoint)
hideOverlap = 1 // 1 = remove older zones that overlap the newest ones
extendFVG = 1 // 1 = extend live zones to the right
maxTracked = 300 // zones kept in memory per direction (20 to 1000)
// ===================== BREAKER FVG =====================
showBreakers = 1 // 1 = show breaker FVGs
showMidline = 1 // 1 = midline, solid before the break and dashed after it
showLabels = 1 // 1 = "BULL BRK FVG" / "BEAR BRK FVG" labels
// ===================== STYLE =====================
// Bullish FVG 8,153,129 / Bearish FVG 242,54,69 (fill alpha 51)
// Bullish breaker 0,207,255 (fill alpha 89) / Bearish breaker 231,130,130 (fill alpha 51)
// Labels use a darker shade of the breaker colour so they read on a light background
extendBars = 100 // how far extended zones reach to the right (bars)
nLast = min(100, max(1, round(showLast)))
maxTrk = min(1000, max(20, round(maxTracked)))
stride = maxTrk + 2
// ===================== ATR =====================
IF barindex >= 199 THEN
atrV = averagetruerange[200](close)
ENDIF
atr14 = averagetruerange[14](close)
// ===================== FVG DETECTION =====================
upFvg = 0
dnFvg = 0
IF barindex >= 200 THEN
blth = low[1] + atrV[1] * fvgThreshold
brth = high[1] - atrV[1] * fvgThreshold
IF low > high[2] AND close[1] > blth THEN
upFvg = 1
ENDIF
IF low[2] > high AND brth > close[1] THEN
dnFvg = 1
ENDIF
ENDIF
// ===================== ZONE STATE =====================
// Two pools, p = 0 bullish FVGs and p = 1 bearish FVGs, oldest first.
// Each pool has TWO copies, one per bar parity: a bar reads the copy left
// by the previous bar and rebuilds its own, so every tick of the live bar
// starts again from the state at the previous close (arrays are NOT rewound
// between ticks the way scalars are).
// Slot fields: $zT top, $zB bottom, $zL first bar, $zX bar of the break,
// $zK state: 0 = FVG, 1 = breaker, -1 = removed during this bar.
pb = barindex MOD 2
IF barindex = 0 THEN
n0 = 0
n1 = 0
ENDIF
FOR p = 0 TO 1 DO
IF p = 0 THEN
nOld = n0
ELSE
nOld = n1
ENDIF
bNow = (2 * p + pb) * stride
bPrev = (2 * p + 1 - pb) * stride
// copy the zones alive at the previous close, keeping the newest maxTrk
nAlive = 0
IF nOld > 0 THEN
FOR i = 0 TO nOld - 1 DO
IF $zK[bPrev + i] >= 0 THEN
nAlive = nAlive + 1
ENDIF
NEXT
ENDIF
nSkip = max(0, nAlive - maxTrk)
nNew = 0
IF nOld > 0 THEN
FOR i = 0 TO nOld - 1 DO
IF $zK[bPrev + i] >= 0 THEN
IF nSkip > 0 THEN
nSkip = nSkip - 1
ELSE
$zT[bNow + nNew] = $zT[bPrev + i]
$zB[bNow + nNew] = $zB[bPrev + i]
$zL[bNow + nNew] = $zL[bPrev + i]
$zX[bNow + nNew] = $zX[bPrev + i]
$zK[bNow + nNew] = $zK[bPrev + i]
nNew = nNew + 1
ENDIF
ENDIF
NEXT
ENDIF
IF p = 0 THEN
n0 = nNew
ELSE
n1 = nNew
ENDIF
NEXT
// ===================== NEW ZONES =====================
// registered one bar after the pattern: top/bottom from bars 1 and 3 back
IF upFvg[1] = 1 THEN
k = pb * stride + n0
$zT[k] = low[1]
$zB[k] = high[3]
$zL[k] = barindex - 3
$zX[k] = 0
$zK[k] = 0
n0 = n0 + 1
ENDIF
IF dnFvg[1] = 1 THEN
k = (2 + pb) * stride + n1
$zT[k] = low[3]
$zB[k] = high[1]
$zL[k] = barindex - 3
$zX[k] = 0
$zK[k] = 0
n1 = n1 + 1
ENDIF
// ===================== BREAK AND INVALIDATION =====================
// A bullish FVG broken downwards becomes a bearish breaker, which is
// removed when price goes back above it. Mirror logic for bearish FVGs.
bodyLow = min(close, open)
bodyHigh = max(close, open)
FOR p = 0 TO 1 DO
IF p = 0 THEN
nP = n0
ELSE
nP = n1
ENDIF
bNow = (2 * p + pb) * stride
IF nP > 0 THEN
FOR i = 0 TO nP - 1 DO
k = bNow + i
zT = $zT[k]
zB = $zB[k]
zM = (zT + zB) / 2
IF mitigationMethod = 0 THEN
hitDown = zB > bodyLow
hitUp = bodyHigh > zT
ELSIF mitigationMethod = 1 THEN
hitDown = zB > low
hitUp = high > zT
ELSE
hitDown = zM > low
hitUp = high > zM
ENDIF
IF p = 0 THEN
zBreak = hitDown
zKill = hitUp
ELSE
zBreak = hitUp
zKill = hitDown
ENDIF
IF $zK[k] = 0 THEN
IF zBreak = 1 THEN
$zK[k] = 1
$zX[k] = barindex
ENDIF
ELSE
IF zKill = 1 THEN
$zK[k] = -1
ENDIF
ENDIF
NEXT
ENDIF
NEXT
// ===================== OVERLAP FILTER =====================
// step 0: bullish zones overlapping the newest bullish zone are removed
// step 1: bearish zones overlapping the newest bearish zone are removed
// step 2: bullish zones overlapping the newest bearish zone are removed
// step 3: bearish zones overlapping the newest remaining bullish zone are removed
IF hideOverlap = 1 THEN
FOR stp = 0 TO 3 DO
p = stp MOD 2
IF 2 > stp THEN
q = p
ELSE
q = 1 - p
ENDIF
IF p = 0 THEN
nP = n0
ELSE
nP = n1
ENDIF
IF q = 0 THEN
nQ = n0
ELSE
nQ = n1
ENDIF
bP = (2 * p + pb) * stride
bQ = (2 * q + pb) * stride
// newest live zone of the reference pool
kc = -1
IF nQ > 0 THEN
FOR i = nQ - 1 DOWNTO 0 DO
IF 0 > kc THEN
IF $zK[bQ + i] >= 0 THEN
kc = bQ + i
ENDIF
ENDIF
NEXT
ENDIF
IF kc >= 0 AND nP > 0 THEN
cT = $zT[kc]
cB = $zB[kc]
FOR i = 0 TO nP - 1 DO
k = bP + i
IF NOT (k = kc) THEN
IF $zK[k] >= 0 THEN
sT = $zT[k]
sB = $zB[k]
ov = (sB > cB AND cT > sB) OR (cT > sT AND sB > cB) OR (sT > cT AND cB > sB) OR (cT > sT AND sT > cB)
IF ov = 1 THEN
$zK[k] = -1
ENDIF
ENDIF
ENDIF
NEXT
ENDIF
NEXT
ENDIF
// ===================== DRAWING ON THE LAST BAR =====================
IF islastbarupdate THEN
IF extendFVG = 1 THEN
xEnd = barindex + extendBars
ELSE
xEnd = barindex
ENDIF
lblOff = 0.5 * atr14
nShow = min(nLast, maxTrk)
FOR p = 0 TO 1 DO
IF p = 0 THEN
// bullish FVGs: their breakers are bearish
nP = n0
fR = 8
fG = 153
fB = 129
kR = 231
kG = 130
kB = 130
kA = 51
txR = 190
txG = 60
txB = 60
ELSE
// bearish FVGs: their breakers are bullish
nP = n1
fR = 242
fG = 54
fB = 69
kR = 0
kG = 207
kB = 255
kA = 89
txR = 0
txG = 130
txB = 170
ENDIF
bP = (2 * p + pb) * stride
nDone = 0
IF nP > 0 THEN
FOR i = nP - 1 DOWNTO 0 DO
k = bP + i
IF nShow > nDone THEN
IF $zK[k] >= 0 THEN
nDone = nDone + 1
zT = $zT[k]
zB = $zB[k]
zL = $zL[k]
IF $zK[k] = 1 THEN
IF showBreakers = 1 THEN
zX = $zX[k]
zM = (zT + zB) / 2
// original FVG up to the break, then the breaker
DRAWRECTANGLE(zL, zT, zX, zB) COLOURED(fR, fG, fB, 0) FILLCOLOR(fR, fG, fB, 51)
DRAWRECTANGLE(zX, zT, xEnd, zB) COLOURED(kR, kG, kB, 255) FILLCOLOR(kR, kG, kB, kA)
IF showMidline = 1 THEN
DRAWSEGMENT(zL, zM, zX, zM) COLOURED(fR, fG, fB, 255)
DRAWSEGMENT(zX, zM, xEnd, zM) COLOURED(kR, kG, kB, 255) STYLE(dottedline2, 1)
ENDIF
IF showLabels = 1 THEN
IF p = 0 THEN
DRAWTEXT("BEAR BRK FVG", zX, zT + lblOff, SansSerif, Bold, 10) COLOURED(txR, txG, txB, 255)
ELSE
DRAWTEXT("BULL BRK FVG", zX, zB - lblOff, SansSerif, Bold, 10) COLOURED(txR, txG, txB, 255)
ENDIF
ENDIF
ENDIF
ELSE
IF showRegularFVG = 1 THEN
DRAWRECTANGLE(zL, zT, xEnd, zB) COLOURED(fR, fG, fB, 0) FILLCOLOR(fR, fG, fB, 51)
ENDIF
ENDIF
ENDIF
ENDIF
NEXT
ENDIF
NEXT
ENDIF
RETURN
FVG Breaker Zones keeps the fair value gap idea and adds what happens after the gap fails: the broken zone becomes a level of its own and stays on the chart until price invalidates it as well. With the overlap filter on, the chart shows the few zones that are still active instead of a stack of old rectangles.