This standalone indicator implements the stated lifecycle on the chart timeframe. Solid boxes are ordinary FVGs; dashed boxes are inverse FVGs. Circles mark inversions, triangles mark first rejections and crosses mark invalidations. The minimum width is a tick filter, not an optimized setting. Syntax validation does not establish runtime behavior or profitability; chart replay and live alert delivery have not been tested for this example.
Educational IFVG state machine, chart timeframe onlypinescript
//@version=6
indicator("IFVG lifecycle example", overlay = true, max_boxes_count = 100)
maxZones = input.int(30, "Maximum retained zones", minval = 1, maxval = 100)
minTicks = input.int(1, "Minimum gap size in ticks", minval = 1)
type Gap
box drawing
float top
float bottom
bool originalBull
int state
int inversionBar
// State 0: FVG. State 1: IFVG. State 2: first rejection recorded.
var array<Gap> gaps = array.new<Gap>()
bool flipped = false
bool bullRetest = false
bool bearRetest = false
bool failed = false
if barstate.isconfirmed
int i = array.size(gaps) - 1
while i >= 0
Gap g = array.get(gaps, i)
box.set_right(g.drawing, bar_index)
bool remove = false
if g.state == 0
bool inversion = g.originalBull ? close < g.bottom : close > g.top
if inversion
g.state := 1
g.inversionBar := bar_index
color invertedColor = g.originalBull ? color.red : color.teal
box.set_bgcolor(g.drawing, color.new(invertedColor, 85))
box.set_border_color(g.drawing, invertedColor)
box.set_border_style(g.drawing, line.style_dashed)
flipped := true
else
// Invalidation takes priority over a retest on the same bar.
remove := g.originalBull ? close > g.top : close < g.bottom
if remove
failed := true
else if g.state == 1 and bar_index > g.inversionBar
bool overlap = high >= g.bottom and low <= g.top
bool rejection = g.originalBull ? close < g.bottom : close > g.top
if overlap and rejection
g.state := 2
bearRetest := bearRetest or g.originalBull
bullRetest := bullRetest or not g.originalBull
if remove
box.delete(g.drawing)
array.remove(gaps, i)
i -= 1
bool bullGap = low > high[2] and low - high[2] >= minTicks * syminfo.mintick
bool bearGap = high < low[2] and low[2] - high >= minTicks * syminfo.mintick
if bullGap or bearGap
// Capacity eviction is housekeeping, not price invalidation.
if array.size(gaps) >= maxZones
Gap oldest = array.shift(gaps)
box.delete(oldest.drawing)
float top = bullGap ? low : low[2]
float bottom = bullGap ? high[2] : high
color initialColor = bullGap ? color.teal : color.red
box drawing = box.new(bar_index - 2, top, bar_index, bottom,
border_color = initialColor, bgcolor = color.new(initialColor, 92))
array.push(gaps, Gap.new(drawing, top, bottom, bullGap, 0, na))
plotshape(flipped, title = "Inversion", style = shape.circle, location = location.abovebar, color = color.orange, size = size.tiny)
plotshape(bullRetest, title = "Bullish IFVG rejection", style = shape.triangleup, location = location.belowbar, color = color.teal, size = size.tiny)
plotshape(bearRetest, title = "Bearish IFVG rejection", style = shape.triangledown, location = location.abovebar, color = color.red, size = size.tiny)
plotshape(failed, title = "IFVG invalidation", style = shape.xcross, location = location.abovebar, color = color.gray, size = size.tiny)
alertcondition(flipped, "IFVG formed", "A chart-timeframe FVG inverted on {{ticker}} at {{close}}.")
alertcondition(bullRetest, "Bullish IFVG rejection", "First bullish IFVG rejection on {{ticker}} at {{close}}.")
alertcondition(bearRetest, "Bearish IFVG rejection", "First bearish IFVG rejection on {{ticker}} at {{close}}.")
alertcondition(failed, "IFVG invalidated", "An IFVG failed on {{ticker}} at {{close}}.")