ny_anchor_fvg_breakout.pine
Copy
// This source code is subject to the terms of the Mozilla Public License 2.0
// © david.riego.01@gmail.com
//@version=5
indicator("NY Anchor Range FVG Breakout", overlay=true,
max_boxes_count=100, max_lines_count=200, max_labels_count=200)
// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS
// ═══════════════════════════════════════════════════════════════════════════════
i_tz = input.string("America/New_York", "Timezone",
group="Anchor Session")
i_session = input.string("0900-1000", "Session Window (HHMM-HHMM)",
group="Anchor Session")
i_breakoutLegLen = input.int(10, "Breakout Leg Length (bars)",
minval=3, maxval=50, group="FVG Settings",
tooltip="Number of bars after breakout considered part of the breakout leg (Tier 2 FVGs).")
i_swingLen = input.int(5, "Swing Lookback",
minval=2, maxval=20, group="BOS Settings",
tooltip="Left and right bar count for pivot high/low confirmation.")
i_bosLimit = input.int(3, "BOS Cancel Count",
minval=1, maxval=5, group="BOS Settings",
tooltip="Cancel limit order after this many with-bias breaks of structure.")
i_rrRatio = input.float(1.0, "R:R Ratio",
minval=0.5, maxval=5.0, step=0.5, group="Risk")
i_showAnchorBox = input.bool(true, "Show Anchor Box", group="Display")
i_showFVGBoxes = input.bool(true, "Show FVG Zones", group="Display")
i_showBOSLabels = input.bool(true, "Show BOS Labels", group="Display")
i_showTradeLines = input.bool(true, "Show Trade Lines", group="Display")
i_deleteOld = input.bool(true, "Clean Up Prior Day", group="Display")
// ═══════════════════════════════════════════════════════════════════════════════
// STATE (var = persists across bars; reset manually on new day)
// ═══════════════════════════════════════════════════════════════════════════════
// ── Anchor range ─────────────────────────────────────────────────────────────
var float anchorHigh = na
var float anchorLow = na
var bool anchorLocked = false
var int anchorLeft = na
var box anchorBox = na
// ── Bias / breakout ───────────────────────────────────────────────────────────
var int bias = 0 // 0=none 1=bullish -1=bearish
var bool breakoutDone = false
var int breakoutBar = na
// ── FVG storage (parallel arrays; one element per detected FVG) ───────────────
var float[] fvgTop = array.new_float(0) // upper bound of gap
var float[] fvgBot = array.new_float(0) // lower bound of gap
var float[] fvgEntry = array.new_float(0) // limit price (near edge)
var float[] fvgSL = array.new_float(0) // raw SL level (before mintick offset)
var int[] fvgTier = array.new_int(0) // 1=overlaps anchor 2=breakout leg 3=other
var int[] fvgDir = array.new_int(0) // 1=bullish -1=bearish
var box[] fvgBoxes = array.new_box(0)
// ── Active / selected FVG ─────────────────────────────────────────────────────
var int activeFVG = -1
// ── Trade levels & lines ──────────────────────────────────────────────────────
var float entryPrice = na
var float stopPrice = na
var float tpPrice = na
var line entryLine = na
var line slLine = na
var line tpLine = na
// ── Trade lifecycle ───────────────────────────────────────────────────────────
var bool inTrade = false
var bool orderCancelled = false
// ── BOS tracking ─────────────────────────────────────────────────────────────
var int bosCount = 0
var float lastPivotRef = na // last confirmed swing level in bias direction
// ═══════════════════════════════════════════════════════════════════════════════
// DAILY RESET
// ═══════════════════════════════════════════════════════════════════════════════
isNewDay = ta.change(time("D")) != 0
if isNewDay
// Delete prior-day drawing objects
if i_deleteOld
box.delete(anchorBox)
line.delete(entryLine)
line.delete(slLine)
line.delete(tpLine)
int _fvgN = array.size(fvgBoxes)
if _fvgN > 0
for i = _fvgN - 1 to 0
box.delete(array.get(fvgBoxes, i))
anchorBox := na
anchorHigh := na
anchorLow := na
anchorLocked := false
anchorLeft := na
bias := 0
breakoutDone := false
breakoutBar := na
activeFVG := -1
entryPrice := na
stopPrice := na
tpPrice := na
entryLine := na
slLine := na
tpLine := na
inTrade := false
orderCancelled := false
bosCount := 0
lastPivotRef := na
array.clear(fvgTop)
array.clear(fvgBot)
array.clear(fvgEntry)
array.clear(fvgSL)
array.clear(fvgTier)
array.clear(fvgDir)
array.clear(fvgBoxes)
// ═══════════════════════════════════════════════════════════════════════════════
// PHASE 1 — ANCHOR RANGE (9:00–10:00 AM New York)
// ═══════════════════════════════════════════════════════════════════════════════
inAnchorSession = not na(time(timeframe.period, i_session, i_tz))
prevInSession = not na(time(timeframe.period, i_session, i_tz)[1])
anchorJustClosed = prevInSession and not inAnchorSession and not anchorLocked
if inAnchorSession and not anchorLocked
if na(anchorHigh)
anchorHigh := high
anchorLow := low
anchorLeft := bar_index
else
anchorHigh := math.max(anchorHigh, high)
anchorLow := math.min(anchorLow, low)
if anchorJustClosed
anchorLocked := true
if i_showAnchorBox
anchorBox := box.new(
left = anchorLeft,
top = anchorHigh,
right = bar_index + 500,
bottom = anchorLow,
border_color = color.new(color.yellow, 0),
bgcolor = color.new(color.yellow, 85),
border_width = 1)
// ═══════════════════════════════════════════════════════════════════════════════
// PHASE 2 — BREAKOUT (candle close outside anchor range)
// ═══════════════════════════════════════════════════════════════════════════════
if barstate.isconfirmed and anchorLocked and not breakoutDone
if close > anchorHigh
bias := 1
breakoutDone := true
breakoutBar := bar_index
label.new(bar_index, high,
text="▲ BO", style=label.style_label_down,
color=color.new(color.teal, 0), textcolor=color.white, size=size.small)
else if close < anchorLow
bias := -1
breakoutDone := true
breakoutBar := bar_index
label.new(bar_index, low,
text="▼ BO", style=label.style_label_up,
color=color.new(color.maroon, 0), textcolor=color.white, size=size.small)
// ═══════════════════════════════════════════════════════════════════════════════
// PHASE 3 — FVG DETECTION (runs every confirmed bar after breakout)
// ═══════════════════════════════════════════════════════════════════════════════
newFVGDetected = false
if barstate.isconfirmed and breakoutDone and not inTrade and not orderCancelled
// 3-candle FVG pattern — all 3 candles must be at or after the breakout bar
bullFVG = bias == 1 and high[2] < low[0] and (bar_index - 2) >= breakoutBar
bearFVG = bias == -1 and low[2] > high[0] and (bar_index - 2) >= breakoutBar
if bullFVG or bearFVG
// Gap boundaries
gTop = bullFVG ? low[0] : low[2] // upper edge
gBot = bullFVG ? high[2] : high[0] // lower edge
// Limit entry at near edge (top for longs, bottom for shorts)
ePrice = bullFVG ? gTop : gBot
// SL: one tick beyond the first (oldest) candle of the FVG pattern
// First candle = candle[2] (bar_index - 2)
slRaw = bullFVG ? low[2] - syminfo.mintick : high[2] + syminfo.mintick
// ── Priority tier ──────────────────────────────────────────────────
// Tier 1: FVG zone contains the anchor boundary level
overlapsAnchor = bullFVG
? (gBot <= anchorHigh and gTop >= anchorHigh)
: (gBot <= anchorLow and gTop >= anchorLow)
// Tier 2: middle candle of FVG formed within the breakout leg
onBreakoutLeg = bar_index[1] <= breakoutBar + i_breakoutLegLen
tier = overlapsAnchor ? 1 : onBreakoutLeg ? 2 : 3
// ── Store FVG ──────────────────────────────────────────────────────
array.push(fvgTop, gTop)
array.push(fvgBot, gBot)
array.push(fvgEntry, ePrice)
array.push(fvgSL, slRaw)
array.push(fvgTier, tier)
array.push(fvgDir, bias)
// ── Draw FVG box (dim by default; selected one will be brightened) ─
fvgBorderCol = bullFVG ? color.new(color.aqua, 50) : color.new(color.orange, 50)
fvgBGCol = bullFVG ? color.new(color.aqua, 90) : color.new(color.orange, 90)
if i_showFVGBoxes
newBox = box.new(
left=bar_index - 2, top=gTop, right=bar_index + 100, bottom=gBot,
border_color=fvgBorderCol, bgcolor=fvgBGCol, border_width=1)
array.push(fvgBoxes, newBox)
else
array.push(fvgBoxes, na)
newFVGDetected := true
// ═══════════════════════════════════════════════════════════════════════════════
// FVG SELECTION (re-evaluates every time a new FVG is detected)
// Priority: lowest tier wins; ties broken by distance to anchor boundary
// ═══════════════════════════════════════════════════════════════════════════════
if newFVGDetected and not inTrade and not orderCancelled
n = array.size(fvgTop)
bestIdx = -1
bestTier = 99
bestDist = 1e10
for i = 0 to n - 1
t = array.get(fvgTier, i)
mid = (array.get(fvgTop, i) + array.get(fvgBot, i)) / 2.0
d = bias == 1 ? math.abs(mid - anchorHigh) : math.abs(mid - anchorLow)
if t < bestTier or (t == bestTier and d < bestDist)
bestIdx := i
bestTier := t
bestDist := d
if bestIdx >= 0 and bestIdx != activeFVG
isUpgrade = activeFVG >= 0 // true when replacing a prior active FVG
// ── Dim the previously active FVG box ─────────────────────────────
if activeFVG >= 0 and i_showFVGBoxes
prevBox = array.get(fvgBoxes, activeFVG)
if not na(prevBox)
box.set_border_color(prevBox,
bias == 1 ? color.new(color.aqua, 50) : color.new(color.orange, 50))
box.set_bgcolor(prevBox,
bias == 1 ? color.new(color.aqua, 90) : color.new(color.orange, 90))
box.set_border_width(prevBox, 1)
activeFVG := bestIdx
// ── Highlight the new active FVG box ──────────────────────────────
if i_showFVGBoxes
newActiveBox = array.get(fvgBoxes, bestIdx)
if not na(newActiveBox)
box.set_border_color(newActiveBox,
bias == 1 ? color.new(color.aqua, 0) : color.new(color.orange, 0))
box.set_bgcolor(newActiveBox,
bias == 1 ? color.new(color.aqua, 55) : color.new(color.orange, 55))
box.set_border_width(newActiveBox, 2)
// ── Delete old entry/SL/TP lines ──────────────────────────────────
line.delete(entryLine)
line.delete(slLine)
line.delete(tpLine)
// ── Calculate trade levels ─────────────────────────────────────────
entryPrice := array.get(fvgEntry, bestIdx)
stopPrice := array.get(fvgSL, bestIdx)
riskAmt = math.abs(entryPrice - stopPrice)
tpPrice := bias == 1
? entryPrice + riskAmt * i_rrRatio
: entryPrice - riskAmt * i_rrRatio
// ── Draw new trade lines ──────────────────────────────────────────
if i_showTradeLines
entryLine := line.new(bar_index, entryPrice, bar_index + 100, entryPrice,
color=color.new(color.white, 0), style=line.style_dashed, width=1)
slLine := line.new(bar_index, stopPrice, bar_index + 100, stopPrice,
color=color.new(color.red, 0), style=line.style_solid, width=1)
tpLine := line.new(bar_index, tpPrice, bar_index + 100, tpPrice,
color=color.new(color.lime, 0), style=line.style_solid, width=1)
// Tier label next to entry line
tierTxt = bestTier == 1 ? "T1" : bestTier == 2 ? "T2" : "T3"
dirTxt = bias == 1 ? "LONG" : "SHORT"
label.new(bar_index, entryPrice,
text=dirTxt + " " + tierTxt + "\nE: " + str.tostring(entryPrice, format.mintick) +
"\nSL: " + str.tostring(stopPrice, format.mintick) +
"\nTP: " + str.tostring(tpPrice, format.mintick),
style=bias == 1 ? label.style_label_upper_left : label.style_label_lower_left,
color=color.new(bias == 1 ? color.teal : color.maroon, 10),
textcolor=color.white, size=size.small)
alert(
"NY FVG Limit " + (isUpgrade ? "UPGRADED" : "SET") +
" | " + (bias == 1 ? "LONG" : "SHORT") +
" | Tier " + str.tostring(bestTier) +
" | Entry: " + str.tostring(entryPrice, format.mintick) +
" | SL: " + str.tostring(stopPrice, format.mintick) +
" | TP: " + str.tostring(tpPrice, format.mintick),
alert.freq_once_per_bar)
// ═══════════════════════════════════════════════════════════════════════════════
// EXTEND TRADE LINES & FVG BOXES (keep them reaching forward each bar)
// ═══════════════════════════════════════════════════════════════════════════════
if not na(entryLine) and not inTrade and not orderCancelled
line.set_x2(entryLine, bar_index + 100)
line.set_x2(slLine, bar_index + 100)
line.set_x2(tpLine, bar_index + 100)
if i_showFVGBoxes and breakoutDone and not inTrade
int _n = array.size(fvgBoxes)
if _n > 0
for i = 0 to _n - 1
b = array.get(fvgBoxes, i)
if not na(b)
box.set_right(b, bar_index + 100)
// ═══════════════════════════════════════════════════════════════════════════════
// PHASE 4 — BOS DETECTION
// Counts with-bias structure breaks (higher highs for bullish, lower lows for bearish)
// Cancel limit order once bosCount >= i_bosLimit
// ═══════════════════════════════════════════════════════════════════════════════
if barstate.isconfirmed and breakoutDone and not inTrade and not orderCancelled
ph = ta.pivothigh(i_swingLen, i_swingLen)
pl = ta.pivotlow(i_swingLen, i_swingLen)
// Pivot occurred i_swingLen bars ago
pivotBar = bar_index - i_swingLen
if bias == 1 and not na(ph) and pivotBar >= breakoutBar
if na(lastPivotRef)
// First swing high after breakout — set reference, no count yet
lastPivotRef := ph
else if ph > lastPivotRef
// Price broke above the previous swing high → BOS in bias direction
bosCount += 1
lastPivotRef := ph
if i_showBOSLabels
label.new(pivotBar, ph,
text="HH " + str.tostring(bosCount),
xloc=xloc.bar_index, style=label.style_label_down,
color=color.new(color.purple, 0), textcolor=color.white, size=size.small)
if bias == -1 and not na(pl) and pivotBar >= breakoutBar
if na(lastPivotRef)
lastPivotRef := pl
else if pl < lastPivotRef
bosCount += 1
lastPivotRef := pl
if i_showBOSLabels
label.new(pivotBar, pl,
text="LL " + str.tostring(bosCount),
xloc=xloc.bar_index, style=label.style_label_up,
color=color.new(color.purple, 0), textcolor=color.white, size=size.small)
if bosCount >= i_bosLimit
orderCancelled := true
// Grey out lines
if not na(entryLine)
line.set_color(entryLine, color.new(color.gray, 60))
line.set_color(slLine, color.new(color.gray, 60))
line.set_color(tpLine, color.new(color.gray, 60))
// Cancellation label
label.new(bar_index,
bias == 1 ? low - syminfo.mintick * 8 : high + syminfo.mintick * 8,
text="CANCELLED\n(" + str.tostring(i_bosLimit) + " BOS)",
style=bias == 1 ? label.style_label_up : label.style_label_down,
color=color.new(color.red, 0), textcolor=color.white, size=size.normal)
alert("NY FVG Order CANCELLED — " + str.tostring(i_bosLimit) +
" BOS reached | " + syminfo.ticker,
alert.freq_once_per_bar)
// ═══════════════════════════════════════════════════════════════════════════════
// PHASE 5 — TRADE SIMULATION (limit fill + SL/TP check)
// ═══════════════════════════════════════════════════════════════════════════════
if barstate.isconfirmed and not na(entryPrice) and not inTrade and not orderCancelled
filled = (bias == 1 and low <= entryPrice) or (bias == -1 and high >= entryPrice)
if filled
inTrade := true
alert(
"NY FVG ENTRY FILLED | " + (bias == 1 ? "LONG" : "SHORT") +
" @ " + str.tostring(entryPrice, format.mintick) +
" | SL: " + str.tostring(stopPrice, format.mintick) +
" | TP: " + str.tostring(tpPrice, format.mintick),
alert.freq_once_per_bar)
if barstate.isconfirmed and inTrade
slHit = (bias == 1 and low <= stopPrice) or (bias == -1 and high >= stopPrice)
tpHit = (bias == 1 and high >= tpPrice) or (bias == -1 and low <= tpPrice)
if slHit or tpHit
inTrade := false
resultTxt = tpHit ? "TP ✓" : "SL ✗"
resultClr = tpHit ? color.new(color.lime, 0) : color.new(color.red, 0)
resultY = tpHit ? tpPrice : stopPrice
// Extend lines to exit bar
if not na(entryLine)
line.set_x2(entryLine, bar_index)
line.set_x2(slLine, bar_index)
line.set_x2(tpLine, bar_index)
label.new(bar_index, resultY,
text=resultTxt,
style=label.style_label_left,
color=resultClr, textcolor=color.white, size=size.normal)
// ═══════════════════════════════════════════════════════════════════════════════
// ALERT CONDITIONS (for TradingView alert manager)
// ═══════════════════════════════════════════════════════════════════════════════
alertcondition(
not na(entryPrice) and na(entryPrice[1]) and not orderCancelled,
title = "NY FVG — Limit Order Set",
message = "NY Anchor FVG: New limit order set on {{ticker}} | {{interval}}")
alertcondition(
inTrade and not inTrade[1],
title = "NY FVG — Entry Filled",
message = "NY Anchor FVG: Entry filled on {{ticker}}")
alertcondition(
orderCancelled and not orderCancelled[1],
title = "NY FVG — Order Cancelled",
message = "NY Anchor FVG: Order cancelled ({{interval}} {{ticker}})")
Show all 445 lines