Skip to content
← Indicator library
tradingviewstructureindicatorpinescript-v6

ICT Gold Engine 5m

AlgoNexta ICT Gold Engine: a 5-minute gold setup scanner for higher-timeframe bias, liquidity sweeps, displacement, structure, FVG, and session filters.

Published by AlgoNexa. AlgoNexa builds and documents evidence-first trading tools so you can inspect the logic before using it.About AlgoNexaIndicator library
Published Aug 9, 2026#ict#market-structure#liquidity#fair-value-gap#atr#indicator#tradingview#pinescript-v6

How it works

Read the logic before you use it.

1

When it finds liquidity

Confirmed pivots from **Confirmed swing length** create the latest swing-high and swing-low liquidity references. A bullish setup begins after price trades below the swing low and closes back above it; bearish logic mirrors this at the swing high.

2

When imbalance and retracement align

The script checks ATR-based displacement and a three-candle FVG, then waits for a return to the **Retracement entry** level, defaulting to the FVG midpoint. Balanced mode uses recent displacement plus HTF EMA direction; Selective A+ can require the full sweep, displacement, structure, and FVG sequence.

3

How the visual plan is framed

On a confirmed marker, **Show Entry / SL / TP plan** can draw an entry, an ATR-distance stop, and three R-multiple target lines. Default values are 1.5 ATR for the stop and 1R, 2R, and 3R for targets; they are visual references rather than orders.

Walkthrough

Watch how it reads the chart.

Live market chart

The logic on real candles.

Backtest evidence

What the numbers actually show.

This script's logic could not be confidently reconstructed into an automated reference backtest, so no performance figures are shown rather than estimated ones.

Verify it in TradingView's Strategy Tester (or the equivalent tool for tradingview) before relying on it.

Vision Grade

What was measured, and how confident we are in it.

Vision Grade requires a reference backtest with enough reconstructed entries to measure — not available for this script yet.

Full breakdown

How the script behaves.

About AlgoNexta

Published by AlgoNexta, an evidence-first platform for building, reviewing, and documenting algorithmic trading tools. This page explains the script's actual logic and intended use; it does not promise profitability or replace verification in TradingView.

What it does

AlgoNexta ICT Gold Engine is a TradingView overlay designed to organize a gold-focused, lower-timeframe market-structure workflow. Its default higher-timeframe context is 60 minutes, while the chart timeframe remains the user's choice; the script does not force a symbol or execute trades.

Signal sequence

The scanner can require aligned higher-timeframe bias, a liquidity sweep, displacement, a linked fair-value gap, premium/discount alignment, and an active London or New York session. With the default Balanced profile, it confirms structure through MSS or BOS, requires a displacement body of at least 0.75 ATR and a range of 1.20 ATR, and looks for a qualifying FVG within 10 bars of the structure event. The default retracement reference is the 50% FVG midpoint, with touch and deep-retracement alternatives.

Quality and chart plan

Displayed grades and scores are configurable quality gates, not probabilities or performance ratings. The default minimum score is 70 with A and A+ thresholds at 70 and 85. When enabled, the visual trade plan projects an ATR-based reference stop and 1R, 2R, and 3R levels for chart planning; these drawings are not orders, fills, or guaranteed targets.

AlgoNexa publication note

This is an AlgoNexa publication for research and education. Verify the exact Pine source and compilation in TradingView, test the selected symbol and session timezone, and treat every marker as a conditional analytical observation rather than financial advice.

Source

pinescript-v6 · full source

ict-gold-engine-5m-algonexta.pine
// ============================================================================
// © 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)

FAQ

Common questions.

What does ICT Gold Engine detect?

It scans confirmed swing liquidity, rejection sweeps, ATR-based displacement, three-candle fair value gaps, and a later retracement into an FVG entry level. The Selective A+ profile also follows a state sequence that can require a sweep, displacement, structure confirmation, and FVG.

Does it only work on Gold or the 5-minute chart?

No. The inputs are described as Gold defaults and the suggested title references 5 minutes, but the code does not lock the script to XAUUSD or any timeframe. Its default Higher timeframe input is 60 minutes.

Does the indicator repaint?

Signal conditions use confirmed bars and higher-timeframe requests use lookahead_off. However, swing pivots are confirmed only after the configured right-side bars, and active setup drawings can be invalidated or updated as later price action develops.

What is the difference between Balanced and Selective A+?

Balanced, the default, pairs a recent displacement and FVG with a higher-timeframe EMA-direction filter before waiting for retracement. Selective A+ uses the stricter liquidity sweep, displacement, MSS/BOS, linked FVG, and quality-gate sequence.

Does it place trades or manage the displayed stop and targets?

No. This is an indicator, not a strategy, and it does not submit orders. The Entry/SL/TP plan draws ATR-based visual levels using defaults of 1.5 ATR risk and 1R, 2R, and 3R targets.

How do alerts and results validation work?

You can create TradingView alerts for A, A+, or any qualified BUY/SELL setup; messages include the ticker, interval, and signal score placeholder. This indicator is not financial advice, and TradingView Strategy Tester with a separately specified execution model, or equivalent platform records, is the source of truth for live results.

Want a version tuned to your market?

Describe the change in plain English and AlgoNexta rebuilds it as a fresh Pine v6 script — with its own explanation, evidence, and walkthrough.

Build your own version

Educational content only — not financial advice. TradingView is the source of truth for live data, compilation, and Strategy Tester results.