ict-gold-engine-5m-algonexta.pine Copy raw source
// ============================================================================
// © algonexta
// This script is for education and research; it does not guarantee results.
// Verify this source in TradingView before use.
// ============================================================================
//@version=6
indicator("ICT Gold Engine — A+ Setup Scanner", shorttitle="ICTGoldA+", overlay=true, max_boxes_count=100, max_lines_count=100, max_labels_count=150)
//──────────────────────────────────────────────────────────────────────────────
// Inputs — Gold defaults
//──────────────────────────────────────────────────────────────────────────────
groupContext = "1. Context and Structure"
biasMode = input.string("Structural HTF Bias", "HTF bias mode", options=["Structural HTF Bias", "EMA Bias", "Bias Filter Off"], group=groupContext)
htf = input.timeframe("60", "Higher timeframe", group=groupContext)
swingLength = input.int(5, "Confirmed swing length", minval=2, maxval=20, group=groupContext)
atrLength = input.int(14, "ATR length", minval=2, group=groupContext)
wickBreakMode = input.bool(false, "Allow wick structure breaks", group=groupContext)
showStructure = input.bool(false, "Show MSS / BOS", group=groupContext)
groupLiquidity = "2. Liquidity"
equalTolerance = input.float(0.12, "EQH/EQL tolerance (ATR)", minval=0.01, step=0.01, group=groupLiquidity)
sweepMemory = input.int(12, "Sweep memory (bars)", minval=1, maxval=50, group=groupLiquidity)
preferEqual = input.bool(false, "Prefer EQH/EQL sweep", group=groupLiquidity, tooltip="Adds a quality gate: the swept pool must have been classified as equal liquidity.")
showLiquidity = input.bool(false, "Show active liquidity", group=groupLiquidity)
groupSequence = "3. Sequence"
signalProfile = input.string("Balanced", "Signal profile", options=["Balanced", "Selective A+"], group=groupSequence)
structureConfirmationMode = input.string("MSS or BOS", "Structure confirmation", options=["MSS or BOS", "MSS only"], group=groupSequence)
bodyFactor = input.float(0.75, "Displacement body (ATR)", minval=0.1, step=0.05, group=groupSequence)
rangeFactor = input.float(1.20, "Displacement range (ATR)", minval=0.1, step=0.05, group=groupSequence)
requireCloseExpansion = input.bool(true, "Displacement closes beyond prior candle", group=groupSequence)
fvgMinimum = input.float(0.10, "Minimum FVG size (ATR)", minval=0.0, step=0.01, group=groupSequence)
fvgMemory = input.int(10, "Bars allowed from MSS to FVG", minval=0, maxval=50, group=groupSequence)
entryMode = input.string("50% FVG midpoint", "Retracement entry", options=["Touch FVG", "50% FVG midpoint", "Deep FVG retracement"], group=groupSequence)
maxSetupAge = input.int(60, "Maximum setup age (bars)", minval=5, maxval=500, group=groupSequence)
signalCooldown = input.int(20, "Minimum bars between arrows", minval=1, maxval=100, group=groupSequence)
showFvg = input.bool(false, "Show active setup FVG", group=groupSequence)
groupSessions = "4. Sessions"
sessionFilter = input.bool(true, "Session filter", group=groupSessions)
londonEnabled = input.bool(true, "London enabled", inline="L", group=groupSessions)
londonSession = input.session("0200-0500", "", inline="L", group=groupSessions)
newYorkEnabled = input.bool(true, "New York enabled", inline="N", group=groupSessions)
newYorkSession = input.session("0700-1000", "", inline="N", group=groupSessions)
shadeSessions = input.bool(false, "Shade enabled sessions", group=groupSessions)
groupFilters = "5. Quality Gates"
requireBias = input.bool(true, "Require aligned HTF bias", group=groupFilters)
requireSweep = input.bool(true, "Require liquidity sweep", group=groupFilters)
requireDisplacement = input.bool(true, "Require displacement", group=groupFilters)
requireFvg = input.bool(true, "Require linked FVG", group=groupFilters)
requirePd = input.bool(false, "Require premium/discount alignment", group=groupFilters)
requireSession = input.bool(true, "Require active session", group=groupFilters)
minimumGrade = input.string("A", "Minimum displayed grade", options=["B", "A", "A+"], group=groupFilters)
minimumScore = input.int(70, "Minimum score", minval=0, maxval=100, group=groupFilters)
gradeB = input.int(60, "B threshold", minval=0, maxval=100, group=groupFilters)
gradeA = input.int(70, "A threshold", minval=0, maxval=100, group=groupFilters)
gradeAPlus = input.int(85, "A+ threshold", minval=0, maxval=100, group=groupFilters)
groupVisual = "6. Display"
cleanSignalMode = input.bool(true, "Clean signal mode — arrows only", group=groupVisual)
signalMarkerSize = input.string("Large", "Signal marker size", options=["Normal", "Large", "Huge"], group=groupVisual)
showEquilibrium = input.bool(false, "Show structural equilibrium", group=groupVisual)
showDashboard = input.bool(false, "Show dashboard", group=groupVisual)
showSignalDetails = input.bool(false, "Show grade and score beside arrow", group=groupVisual)
groupTradePlan = "7. Visual Risk / Reward"
showTradePlan = input.bool(true, "Show Entry / SL / TP plan", group=groupTradePlan)
atrStopMultiple = input.float(1.5, "Stop distance (ATR)", minval=0.25, maxval=10.0, step=0.25, group=groupTradePlan)
targetR1 = input.float(1.0, "TP1 (R)", minval=0.25, maxval=10.0, step=0.25, group=groupTradePlan)
targetR2 = input.float(2.0, "TP2 (R)", minval=0.5, maxval=15.0, step=0.25, group=groupTradePlan)
targetR3 = input.float(3.0, "TP3 (R)", minval=0.75, maxval=20.0, step=0.25, group=groupTradePlan)
planProjectionBars = input.int(16, "Plan width (bars)", minval=3, maxval=100, group=groupTradePlan)
rrHistoryCount = input.int(15, "Signal RR plans to keep", minval=1, maxval=20, group=groupTradePlan)
//──────────────────────────────────────────────────────────────────────────────
// Helpers
//──────────────────────────────────────────────────────────────────────────────
f_structuralBias(int len) =>
float ph = ta.pivothigh(high, len, len)
float pl = ta.pivotlow(low, len, len)
var float lastH = na
var float lastL = na
var int direction = 0
if not na(ph)
lastH := ph
if not na(pl)
lastL := pl
if not na(lastH) and close > lastH
direction := 1
if not na(lastL) and close < lastL
direction := -1
direction
f_grade(int score) => score >= gradeAPlus ? "A+" : score >= gradeA ? "A" : score >= gradeB ? "B" : "—"
f_gradeRank(string grade) => grade == "A+" ? 3 : grade == "A" ? 2 : grade == "B" ? 1 : 0
f_stateName(int state) => state == 0 ? "IDLE" : state == 1 ? "SWEEP" : state == 2 ? "DISPLACEMENT" : state == 3 ? "MSS" : state == 4 ? "WAITING FOR RETRACEMENT" : state == 5 ? "READY" : "INVALIDATED"
// HTF calculations execute in the HTF context; lookahead is explicitly off.
htfStructuralBias = request.security(syminfo.tickerid, htf, f_structuralBias(swingLength), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off)
htfClose = request.security(syminfo.tickerid, htf, close, gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off)
htfEma = request.security(syminfo.tickerid, htf, ta.ema(close, 50), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off)
int htfBias = biasMode == "Bias Filter Off" ? 0 : biasMode == "EMA Bias" ? (htfClose > htfEma ? 1 : htfClose < htfEma ? -1 : 0) : htfStructuralBias
float atr = ta.atr(atrLength)
float pivotHigh = ta.pivothigh(high, swingLength, swingLength)
float pivotLow = ta.pivotlow(low, swingLength, swingLength)
var float lastSwingHigh = na
var float priorSwingHigh = na
var float lastSwingLow = na
var float priorSwingLow = na
var bool lastHighEqual = false
var bool lastLowEqual = false
var bool highLiquidityAvailable = false
var bool lowLiquidityAvailable = false
var bool highStructureAvailable = false
var bool lowStructureAvailable = false
bool newEqh = false
bool newEql = false
if not na(pivotHigh)
priorSwingHigh := lastSwingHigh
lastSwingHigh := pivotHigh
lastHighEqual := not na(priorSwingHigh) and math.abs(lastSwingHigh - priorSwingHigh) <= atr[swingLength] * equalTolerance
highLiquidityAvailable := true
highStructureAvailable := true
newEqh := lastHighEqual
if not na(pivotLow)
priorSwingLow := lastSwingLow
lastSwingLow := pivotLow
lastLowEqual := not na(priorSwingLow) and math.abs(lastSwingLow - priorSwingLow) <= atr[swingLength] * equalTolerance
lowLiquidityAvailable := true
lowStructureAvailable := true
newEql := lastLowEqual
// Structure is based on confirmed swing levels and close breaks by default.
float bullBreakPrice = wickBreakMode ? high : close
float bearBreakPrice = wickBreakMode ? low : close
bool breakHigh = highStructureAvailable and not na(lastSwingHigh) and bullBreakPrice > lastSwingHigh
bool breakLow = lowStructureAvailable and not na(lastSwingLow) and bearBreakPrice < lastSwingLow
var int structureDirection = 0
bool bullMss = breakHigh and structureDirection <= -1
bool bearMss = breakLow and structureDirection >= 1
bool bullBos = breakHigh and structureDirection == 1
bool bearBos = breakLow and structureDirection == -1
if breakHigh
structureDirection := 1
highStructureAvailable := false
if breakLow
structureDirection := -1
lowStructureAvailable := false
// Structural dealing range: most recently confirmed swing low and high.
float equilibrium = not na(lastSwingHigh) and not na(lastSwingLow) ? (lastSwingHigh + lastSwingLow) * 0.5 : na
bool inDiscount = not na(equilibrium) and close <= equilibrium
bool inPremium = not na(equilibrium) and close >= equilibrium
bool inLondon = londonEnabled and not na(time(timeframe.period, londonSession, "America/New_York"))
bool inNewYork = newYorkEnabled and not na(time(timeframe.period, newYorkSession, "America/New_York"))
bool inKillZone = inLondon or inNewYork
string sessionName = inLondon ? "LONDON" : inNewYork ? "NEW YORK" : "OFF SESSION"
// True rejection: trade through confirmed liquidity, then close back inside.
bool sslSweep = barstate.isconfirmed and lowLiquidityAvailable and not na(lastSwingLow) and low < lastSwingLow and close > lastSwingLow
bool bslSweep = barstate.isconfirmed and highLiquidityAvailable and not na(lastSwingHigh) and high > lastSwingHigh and close < lastSwingHigh
bool bullDisplacement = barstate.isconfirmed and close > open and math.abs(close-open) >= atr*bodyFactor and (high-low) >= atr*rangeFactor and (not requireCloseExpansion or close > high[1])
bool bearDisplacement = barstate.isconfirmed and close < open and math.abs(close-open) >= atr*bodyFactor and (high-low) >= atr*rangeFactor and (not requireCloseExpansion or close < low[1])
bool bullFvgNow = barstate.isconfirmed and low > high[2] and (low-high[2]) >= atr*fvgMinimum
bool bearFvgNow = barstate.isconfirmed and high < low[2] and (low[2]-high) >= atr*fvgMinimum
//──────────────────────────────────────────────────────────────────────────────
// Sequence state. 0 idle, 1 sweep, 2 displacement, 3 MSS, 4 wait, 5 ready.
//──────────────────────────────────────────────────────────────────────────────
var int bullState = 0
var int bearState = 0
var int bullStart = na
var int bearStart = na
var int bullStateBar = na
var int bearStateBar = na
var float bullFvgTop = na
var float bullFvgBottom = na
var float bearFvgTop = na
var float bearFvgBottom = na
var int bullFvgBar = na
var int bearFvgBar = na
var bool bullSweptEqual = false
var bool bearSweptEqual = false
var box bullBox = na
var box bearBox = na
var string lastLiquidityEvent = "NONE"
if sslSweep
lowLiquidityAvailable := false
bullState := 1
bullStart := bar_index
bullStateBar := bar_index
bullSweptEqual := lastLowEqual
lastLiquidityEvent := "SSL SWEEP"
bearState := 0
if not na(bearBox)
box.delete(bearBox)
bearBox := na
if bslSweep
highLiquidityAvailable := false
bearState := 1
bearStart := bar_index
bearStateBar := bar_index
bearSweptEqual := lastHighEqual
lastLiquidityEvent := "BSL SWEEP"
bullState := 0
if not na(bullBox)
box.delete(bullBox)
bullBox := na
if bullState == 1 and bar_index - bullStateBar <= sweepMemory and bullDisplacement
bullState := 2
bullStateBar := bar_index
if bearState == 1 and bar_index - bearStateBar <= sweepMemory and bearDisplacement
bearState := 2
bearStateBar := bar_index
// The imbalance commonly forms on the displacement candle before structure is
// confirmed. Preserve that setup-owned FVG instead of searching for a random one.
if bullState == 2 and bullFvgNow
bullFvgTop := low
bullFvgBottom := high[2]
bullFvgBar := bar_index
if bearState == 2 and bearFvgNow
bearFvgTop := low[2]
bearFvgBottom := high
bearFvgBar := bar_index
bool bullStructureConfirmed = bullMss or (structureConfirmationMode == "MSS or BOS" and breakHigh)
bool bearStructureConfirmed = bearMss or (structureConfirmationMode == "MSS or BOS" and breakLow)
if bullState == 2 and bullStructureConfirmed
bullState := not na(bullFvgBar) and bullFvgBar >= bullStart ? 4 : 3
bullStateBar := bar_index
if bearState == 2 and bearStructureConfirmed
bearState := not na(bearFvgBar) and bearFvgBar >= bearStart ? 4 : 3
bearStateBar := bar_index
// Only an FVG formed after the sequence MSS owns the setup.
if bullState == 3 and bar_index - bullStateBar <= fvgMemory and bullFvgNow
bullFvgTop := low
bullFvgBottom := high[2]
bullFvgBar := bar_index
bullState := 4
if not na(bullBox)
box.delete(bullBox)
bullBox := box.new(bar_index-2, bullFvgTop, bar_index+1, bullFvgBottom, border_color=cleanSignalMode ? color.new(color.teal, 100) : color.new(color.teal, 45), bgcolor=showFvg and not cleanSignalMode ? color.new(color.teal, 87) : color.new(color.teal, 100), extend=extend.right)
if bearState == 3 and bar_index - bearStateBar <= fvgMemory and bearFvgNow
bearFvgTop := low[2]
bearFvgBottom := high
bearFvgBar := bar_index
bearState := 4
if not na(bearBox)
box.delete(bearBox)
bearBox := box.new(bar_index-2, bearFvgTop, bar_index+1, bearFvgBottom, border_color=cleanSignalMode ? color.new(color.orange, 100) : color.new(color.orange, 45), bgcolor=showFvg and not cleanSignalMode ? color.new(color.orange, 87) : color.new(color.orange, 100), extend=extend.right)
float bullEntryLevel = entryMode == "Touch FVG" ? bullFvgTop : entryMode == "50% FVG midpoint" ? (bullFvgTop+bullFvgBottom)*0.5 : bullFvgBottom + (bullFvgTop-bullFvgBottom)*0.25
float bearEntryLevel = entryMode == "Touch FVG" ? bearFvgBottom : entryMode == "50% FVG midpoint" ? (bearFvgTop+bearFvgBottom)*0.5 : bearFvgTop - (bearFvgTop-bearFvgBottom)*0.25
bool bullRetrace = bullState == 4 and bar_index > bullFvgBar and low <= bullEntryLevel and high >= bullFvgBottom
bool bearRetrace = bearState == 4 and bar_index > bearFvgBar and high >= bearEntryLevel and low <= bearFvgTop
// Balanced public-facing path: impulse imbalance, then a real pullback into it.
// It remains confirmed-bar and HTF-filtered, but does not require every strict
// ICT event to occur in one exact order.
var float balancedBullTop = na
var float balancedBullBottom = na
var float balancedBearTop = na
var float balancedBearBottom = na
var int balancedBullBar = na
var int balancedBearBar = na
var bool balancedBullActive = false
var bool balancedBearActive = false
var int lastBuyArrowBar = na
var int lastSellArrowBar = na
var int lastAnyArrowBar = na
var int lastSignalDirection = 0
var line[] rrLines = array.new_line()
var label[] rrLabels = array.new_label()
int barsSinceBullDisplacement = nz(ta.barssince(bullDisplacement), 10000)
int barsSinceBearDisplacement = nz(ta.barssince(bearDisplacement), 10000)
// Balanced mode uses the stable HTF EMA direction so a slowly-confirming swing
// bias cannot suppress an entire trend. Selective A+ retains structural bias.
bool balancedBullBias = biasMode == "Bias Filter Off" or htfClose >= htfEma
bool balancedBearBias = biasMode == "Bias Filter Off" or htfClose <= htfEma
if signalProfile == "Balanced" and not balancedBullActive and bullFvgNow and barsSinceBullDisplacement <= 2 and balancedBullBias
balancedBullTop := low
balancedBullBottom := high[2]
balancedBullBar := bar_index
balancedBullActive := true
if signalProfile == "Balanced" and not balancedBearActive and bearFvgNow and barsSinceBearDisplacement <= 2 and balancedBearBias
balancedBearTop := low[2]
balancedBearBottom := high
balancedBearBar := bar_index
balancedBearActive := true
float balancedBullEntry = entryMode == "Touch FVG" ? balancedBullTop : entryMode == "50% FVG midpoint" ? (balancedBullTop+balancedBullBottom)*0.5 : balancedBullBottom + (balancedBullTop-balancedBullBottom)*0.25
float balancedBearEntry = entryMode == "Touch FVG" ? balancedBearBottom : entryMode == "50% FVG midpoint" ? (balancedBearTop+balancedBearBottom)*0.5 : balancedBearTop - (balancedBearTop-balancedBearBottom)*0.25
bool balancedBuyRetrace = balancedBullActive and bar_index > balancedBullBar and low <= balancedBullEntry and high >= balancedBullBottom
bool balancedSellRetrace = balancedBearActive and bar_index > balancedBearBar and high >= balancedBearEntry and low <= balancedBearTop
bool globalCooldownReady = na(lastAnyArrowBar) or bar_index-lastAnyArrowBar >= signalCooldown
bool buyCooldownReady = globalCooldownReady and lastSignalDirection != 1 and (na(lastBuyArrowBar) or bar_index-lastBuyArrowBar >= signalCooldown)
bool sellCooldownReady = globalCooldownReady and lastSignalDirection != -1 and (na(lastSellArrowBar) or bar_index-lastSellArrowBar >= signalCooldown)
bool balancedBuySignal = barstate.isconfirmed and balancedBuyRetrace and balancedBullBias and buyCooldownReady
bool balancedSellSignal = barstate.isconfirmed and balancedSellRetrace and balancedBearBias and sellCooldownReady
int recentSslBars = nz(ta.barssince(sslSweep), 10000)
int recentBslBars = nz(ta.barssince(bslSweep), 10000)
int balancedBullScore = math.min(100, 70 + (htfBias == 1 ? 10 : 0) + (recentSslBars <= 20 ? 10 : 0) + (inDiscount ? 5 : 0) + (inKillZone ? 5 : 0))
int balancedBearScore = math.min(100, 70 + (htfBias == -1 ? 10 : 0) + (recentBslBars <= 20 ? 10 : 0) + (inPremium ? 5 : 0) + (inKillZone ? 5 : 0))
// Score is transparent and event-based. Sequence prerequisites are retained in state.
int bullScore = (htfBias == 1 ? 15 : 0) + (bullState >= 1 ? 20 : 0) + (bullState >= 2 ? 15 : 0) + (bullState >= 3 ? 15 : 0) + (bullState >= 4 ? 15 : 0) + (bullRetrace ? 10 : 0) + (inDiscount ? 5 : 0) + (inKillZone ? 5 : 0)
int bearScore = (htfBias == -1 ? 15 : 0) + (bearState >= 1 ? 20 : 0) + (bearState >= 2 ? 15 : 0) + (bearState >= 3 ? 15 : 0) + (bearState >= 4 ? 15 : 0) + (bearRetrace ? 10 : 0) + (inPremium ? 5 : 0) + (inKillZone ? 5 : 0)
string bullGrade = f_grade(bullScore)
string bearGrade = f_grade(bearScore)
int requiredRank = f_gradeRank(minimumGrade)
bool effectiveSessionRequired = signalProfile == "Selective A+" and requireSession
bool bullGates = (biasMode == "Bias Filter Off" or not requireBias or htfBias == 1) and (not requireSweep or bullState >= 1) and (not requireDisplacement or bullState >= 2) and (not requireFvg or bullState >= 4) and (not requirePd or inDiscount) and (not sessionFilter or not effectiveSessionRequired or inKillZone) and (not preferEqual or bullSweptEqual)
bool bearGates = (biasMode == "Bias Filter Off" or not requireBias or htfBias == -1) and (not requireSweep or bearState >= 1) and (not requireDisplacement or bearState >= 2) and (not requireFvg or bearState >= 4) and (not requirePd or inPremium) and (not sessionFilter or not effectiveSessionRequired or inKillZone) and (not preferEqual or bearSweptEqual)
bool strictBuySignal = barstate.isconfirmed and bullRetrace and bullGates and bullScore >= minimumScore and f_gradeRank(bullGrade) >= requiredRank
bool strictSellSignal = barstate.isconfirmed and bearRetrace and bearGates and bearScore >= minimumScore and f_gradeRank(bearGrade) >= requiredRank
bool buySignal = signalProfile == "Balanced" ? balancedBuySignal : strictBuySignal
bool sellSignal = signalProfile == "Balanced" ? balancedSellSignal : strictSellSignal
float signalBuyLevel = signalProfile == "Balanced" ? balancedBullEntry : bullEntryLevel
float signalSellLevel = signalProfile == "Balanced" ? balancedBearEntry : bearEntryLevel
int signalBullScore = signalProfile == "Balanced" ? balancedBullScore : bullScore
int signalBearScore = signalProfile == "Balanced" ? balancedBearScore : bearScore
string signalBullGrade = f_grade(signalBullScore)
string signalBearGrade = f_grade(signalBearScore)
if buySignal
lastBuyArrowBar := bar_index
lastAnyArrowBar := bar_index
lastSignalDirection := 1
balancedBullActive := false
bullState := 5
bullStateBar := bar_index
string buyMarkerText = signalMarkerSize == "Huge" ? "▲\nBUY" : "BUY"
label.new(bar_index, signalBuyLevel, buyMarkerText, style=label.style_label_up, color=color.rgb(0, 200, 83), textcolor=color.white, size=signalMarkerSize == "Normal" ? size.normal : signalMarkerSize == "Large" ? size.large : size.huge, tooltip="BUY entry · " + signalBullGrade + " · Score " + str.tostring(signalBullScore))
if showTradePlan
float buyRisk = atr*atrStopMultiple
float buyStop = signalBuyLevel-buyRisk
float buyTp1 = signalBuyLevel+buyRisk*targetR1
float buyTp2 = signalBuyLevel+buyRisk*targetR2
float buyTp3 = signalBuyLevel+buyRisk*targetR3
line buyEntryLine = line.new(bar_index, signalBuyLevel, bar_index+planProjectionBars, signalBuyLevel, color=color.new(color.white, 15), style=line.style_dashed, width=1)
line buySlLine = line.new(bar_index, buyStop, bar_index+planProjectionBars, buyStop, color=color.rgb(255, 45, 85), width=2)
line buyTp1Line = line.new(bar_index, buyTp1, bar_index+planProjectionBars, buyTp1, color=color.new(color.lime, 35), width=1)
line buyTp2Line = line.new(bar_index, buyTp2, bar_index+planProjectionBars, buyTp2, color=color.new(color.lime, 15), width=2)
line buyTp3Line = line.new(bar_index, buyTp3, bar_index+planProjectionBars, buyTp3, color=color.rgb(0, 230, 118), width=2)
label buySlLabel = label.new(bar_index+planProjectionBars, buyStop, "SL", style=label.style_label_left, color=color.new(color.red, 10), textcolor=color.white, size=size.small)
label buyTp1Label = label.new(bar_index+planProjectionBars, buyTp1, "TP1 · " + str.tostring(targetR1, "#.##") + "R", style=label.style_label_left, color=color.new(color.green, 35), textcolor=color.white, size=size.tiny)
label buyTp2Label = label.new(bar_index+planProjectionBars, buyTp2, "TP2 · " + str.tostring(targetR2, "#.##") + "R", style=label.style_label_left, color=color.new(color.green, 20), textcolor=color.white, size=size.tiny)
label buyTp3Label = label.new(bar_index+planProjectionBars, buyTp3, "TP3 · " + str.tostring(targetR3, "#.##") + "R", style=label.style_label_left, color=color.new(color.green, 5), textcolor=color.white, size=size.tiny)
array.push(rrLines, buyEntryLine)
array.push(rrLines, buySlLine)
array.push(rrLines, buyTp1Line)
array.push(rrLines, buyTp2Line)
array.push(rrLines, buyTp3Line)
array.push(rrLabels, buySlLabel)
array.push(rrLabels, buyTp1Label)
array.push(rrLabels, buyTp2Label)
array.push(rrLabels, buyTp3Label)
if showSignalDetails and not cleanSignalMode
label.new(bar_index, signalBuyLevel, signalBullGrade + " · " + str.tostring(signalBullScore), style=label.style_label_up, color=color.new(color.green, 15), textcolor=color.white, size=size.small)
if not na(bullBox)
box.set_extend(bullBox, extend.none)
box.set_right(bullBox, bar_index)
if sellSignal
lastSellArrowBar := bar_index
lastAnyArrowBar := bar_index
lastSignalDirection := -1
balancedBearActive := false
bearState := 5
bearStateBar := bar_index
string sellMarkerText = signalMarkerSize == "Huge" ? "SELL\n▼" : "SELL"
label.new(bar_index, signalSellLevel, sellMarkerText, style=label.style_label_down, color=color.rgb(255, 45, 85), textcolor=color.white, size=signalMarkerSize == "Normal" ? size.normal : signalMarkerSize == "Large" ? size.large : size.huge, tooltip="SELL entry · " + signalBearGrade + " · Score " + str.tostring(signalBearScore))
if showTradePlan
float sellRisk = atr*atrStopMultiple
float sellStop = signalSellLevel+sellRisk
float sellTp1 = signalSellLevel-sellRisk*targetR1
float sellTp2 = signalSellLevel-sellRisk*targetR2
float sellTp3 = signalSellLevel-sellRisk*targetR3
line sellEntryLine = line.new(bar_index, signalSellLevel, bar_index+planProjectionBars, signalSellLevel, color=color.new(color.white, 15), style=line.style_dashed, width=1)
line sellSlLine = line.new(bar_index, sellStop, bar_index+planProjectionBars, sellStop, color=color.rgb(255, 45, 85), width=2)
line sellTp1Line = line.new(bar_index, sellTp1, bar_index+planProjectionBars, sellTp1, color=color.new(color.lime, 35), width=1)
line sellTp2Line = line.new(bar_index, sellTp2, bar_index+planProjectionBars, sellTp2, color=color.new(color.lime, 15), width=2)
line sellTp3Line = line.new(bar_index, sellTp3, bar_index+planProjectionBars, sellTp3, color=color.rgb(0, 230, 118), width=2)
label sellSlLabel = label.new(bar_index+planProjectionBars, sellStop, "SL", style=label.style_label_left, color=color.new(color.red, 10), textcolor=color.white, size=size.small)
label sellTp1Label = label.new(bar_index+planProjectionBars, sellTp1, "TP1 · " + str.tostring(targetR1, "#.##") + "R", style=label.style_label_left, color=color.new(color.green, 35), textcolor=color.white, size=size.tiny)
label sellTp2Label = label.new(bar_index+planProjectionBars, sellTp2, "TP2 · " + str.tostring(targetR2, "#.##") + "R", style=label.style_label_left, color=color.new(color.green, 20), textcolor=color.white, size=size.tiny)
label sellTp3Label = label.new(bar_index+planProjectionBars, sellTp3, "TP3 · " + str.tostring(targetR3, "#.##") + "R", style=label.style_label_left, color=color.new(color.green, 5), textcolor=color.white, size=size.tiny)
array.push(rrLines, sellEntryLine)
array.push(rrLines, sellSlLine)
array.push(rrLines, sellTp1Line)
array.push(rrLines, sellTp2Line)
array.push(rrLines, sellTp3Line)
array.push(rrLabels, sellSlLabel)
array.push(rrLabels, sellTp1Label)
array.push(rrLabels, sellTp2Label)
array.push(rrLabels, sellTp3Label)
if showSignalDetails and not cleanSignalMode
label.new(bar_index, signalSellLevel, signalBearGrade + " · " + str.tostring(signalBearScore), style=label.style_label_down, color=color.new(color.red, 15), textcolor=color.white, size=size.small)
if not na(bearBox)
box.set_extend(bearBox, extend.none)
box.set_right(bearBox, bar_index)
// Invalidation: expiry, opposite structure, or close through the far FVG boundary.
bool bullInvalid = bullState > 0 and bullState < 5 and ((bar_index-bullStart > maxSetupAge) or (bullState == 1 and bar_index-bullStateBar > sweepMemory) or (bullState == 3 and bar_index-bullStateBar > fvgMemory) or bearMss or (bullState >= 4 and close < bullFvgBottom))
bool bearInvalid = bearState > 0 and bearState < 5 and ((bar_index-bearStart > maxSetupAge) or (bearState == 1 and bar_index-bearStateBar > sweepMemory) or (bearState == 3 and bar_index-bearStateBar > fvgMemory) or bullMss or (bearState >= 4 and close > bearFvgTop))
if bullInvalid
bullState := 6
bullStateBar := bar_index
if not na(bullBox)
box.set_extend(bullBox, extend.none)
box.set_right(bullBox, bar_index)
box.set_bgcolor(bullBox, color.new(color.gray, 94))
if bearInvalid
bearState := 6
bearStateBar := bar_index
if not na(bearBox)
box.set_extend(bearBox, extend.none)
box.set_right(bearBox, bar_index)
box.set_bgcolor(bearBox, color.new(color.gray, 94))
if balancedBullActive and (bar_index-balancedBullBar > maxSetupAge or close < balancedBullBottom)
balancedBullActive := false
if balancedBearActive and (bar_index-balancedBearBar > maxSetupAge or close > balancedBearTop)
balancedBearActive := false
if (bullState == 5 or bullState == 6) and bar_index > bullStateBar
bullState := 0
if (bearState == 5 or bearState == 6) and bar_index > bearStateBar
bearState := 0
// Recycle the oldest visual plans to keep the chart clean and object use bounded.
while array.size(rrLines) > rrHistoryCount*5
line.delete(array.shift(rrLines))
while array.size(rrLabels) > rrHistoryCount*4
label.delete(array.shift(rrLabels))
//──────────────────────────────────────────────────────────────────────────────
// Visuals
//──────────────────────────────────────────────────────────────────────────────
// BUY/SELL labels above are anchored by their arrow tip to the selected FVG entry.
plot(showLiquidity and not cleanSignalMode ? lastSwingHigh : na, "BSL", color=color.new(color.red, 55), linewidth=1, style=plot.style_stepline)
plot(showLiquidity and not cleanSignalMode ? lastSwingLow : na, "SSL", color=color.new(color.aqua, 55), linewidth=1, style=plot.style_stepline)
plot(showEquilibrium and not cleanSignalMode ? equilibrium : na, "Equilibrium", color=color.new(color.silver, 55), linewidth=1, style=plot.style_stepline)
plotshape(showLiquidity and not cleanSignalMode and newEqh, title="Equal highs confirmed", text="EQH", style=shape.labeldown, location=location.abovebar, color=color.new(color.red, 70), textcolor=color.white, size=size.tiny)
plotshape(showLiquidity and not cleanSignalMode and newEql, title="Equal lows confirmed", text="EQL", style=shape.labelup, location=location.belowbar, color=color.new(color.teal, 70), textcolor=color.white, size=size.tiny)
plotshape(showStructure and not cleanSignalMode and bullMss, title="Bullish MSS", text="MSS", style=shape.labelup, location=location.belowbar, color=color.new(color.teal, 55), textcolor=color.white, size=size.tiny)
plotshape(showStructure and not cleanSignalMode and bearMss, title="Bearish MSS", text="MSS", style=shape.labeldown, location=location.abovebar, color=color.new(color.orange, 55), textcolor=color.white, size=size.tiny)
plotshape(showStructure and not cleanSignalMode and bullBos, title="Bullish BOS", text="BOS", style=shape.labelup, location=location.belowbar, color=color.new(color.blue, 70), textcolor=color.white, size=size.tiny)
plotshape(showStructure and not cleanSignalMode and bearBos, title="Bearish BOS", text="BOS", style=shape.labeldown, location=location.abovebar, color=color.new(color.purple, 70), textcolor=color.white, size=size.tiny)
plotshape(showLiquidity and not cleanSignalMode and sslSweep, title="SSL Sweep", text="SSL Sweep", style=shape.labelup, location=location.belowbar, color=color.new(color.aqua, 55), textcolor=color.black, size=size.tiny)
plotshape(showLiquidity and not cleanSignalMode and bslSweep, title="BSL Sweep", text="BSL Sweep", style=shape.labeldown, location=location.abovebar, color=color.new(color.red, 55), textcolor=color.white, size=size.tiny)
bgcolor(not cleanSignalMode and shadeSessions and inKillZone ? color.new(inLondon ? color.blue : color.purple, 93) : na)
// Hidden plots provide dynamic values to alert placeholders.
plot(buySignal or sellSignal ? (buySignal ? signalBullScore : signalBearScore) : na, "Signal Score", display=display.none)
bool aBuy = buySignal and signalBullGrade == "A"
bool aSell = sellSignal and signalBearGrade == "A"
bool apBuy = buySignal and signalBullGrade == "A+"
bool apSell = sellSignal and signalBearGrade == "A+"
alertcondition(aBuy, "A BUY", "{{ticker}} {{interval}} BUY A | Score {{plot(\"Signal Score\")}}")
alertcondition(aSell, "A SELL", "{{ticker}} {{interval}} SELL A | Score {{plot(\"Signal Score\")}}")
alertcondition(apBuy, "A+ BUY", "{{ticker}} {{interval}} BUY A+ | Score {{plot(\"Signal Score\")}}")
alertcondition(apSell, "A+ SELL", "{{ticker}} {{interval}} SELL A+ | Score {{plot(\"Signal Score\")}}")
alertcondition(buySignal or sellSignal, "Any qualified setup", "{{ticker}} {{interval}} qualified ICT setup | Score {{plot(\"Signal Score\")}}")
var table dashboard = table.new(position.top_right, 2, 6, bgcolor=color.new(color.black, 18), border_color=color.new(color.gray, 65), border_width=1)
if barstate.islast and showDashboard and not cleanSignalMode
int activeDirection = bullState > 0 ? 1 : bearState > 0 ? -1 : 0
int activeState = activeDirection == 1 ? bullState : activeDirection == -1 ? bearState : 0
int activeScore = activeDirection == 1 ? bullScore : activeDirection == -1 ? bearScore : 0
string activeGrade = activeDirection == 1 ? bullGrade : activeDirection == -1 ? bearGrade : "—"
table.cell(dashboard, 0, 0, "HTF Bias", text_color=color.silver)
table.cell(dashboard, 1, 0, htfBias == 1 ? "BULLISH" : htfBias == -1 ? "BEARISH" : "NEUTRAL/OFF", text_color=htfBias == 1 ? color.lime : htfBias == -1 ? color.red : color.silver)
table.cell(dashboard, 0, 1, "Session", text_color=color.silver)
table.cell(dashboard, 1, 1, sessionName, text_color=inKillZone ? color.yellow : color.gray)
table.cell(dashboard, 0, 2, "Liquidity", text_color=color.silver)
table.cell(dashboard, 1, 2, lastLiquidityEvent, text_color=color.white)
table.cell(dashboard, 0, 3, "Structure", text_color=color.silver)
table.cell(dashboard, 1, 3, structureDirection == 1 ? "BULLISH" : structureDirection == -1 ? "BEARISH" : "UNSET", text_color=color.white)
table.cell(dashboard, 0, 4, "Setup", text_color=color.silver)
table.cell(dashboard, 1, 4, (activeDirection == 1 ? "BUY · " : activeDirection == -1 ? "SELL · " : "") + f_stateName(activeState), text_color=color.white)
table.cell(dashboard, 0, 5, "Grade / Score", text_color=color.silver)
table.cell(dashboard, 1, 5, activeGrade + " / " + str.tostring(activeScore), text_color=activeGrade == "A+" ? color.lime : color.white)