OPEN-SOURCE SCRIPT
V4

//version=5
indicator("ICT MTF Entry: OB + CHoCH + FVG + Liquidity", overlay=true, max_boxes_count=500, max_lines_count=500, max_labels_count=500)
// ================= INPUTS =================
grpHTF = "HTF Order Block / POI"
htfOB = input.timeframe("60", "Order Block TF", group=grpHTF)
dispMult = input.float(1.5, "Displacement (x ATR)", group=grpHTF)
obMaxAgeBars = input.int(150, "OB Max Age (chart bars)", group=grpHTF)
grpMTF = "MTF CHoCH"
mtfCHoCH = input.timeframe("15", "CHoCH TF", group=grpMTF)
pivotLen = input.int(5, "Swing Pivot Length (MTF bars)", group=grpMTF)
chochWindow = input.int(30, "CHoCH Validity Window (chart bars)", group=grpMTF)
grpLTF = "Entry FVG / IFVG (uses chart timeframe - run this on your 1M/5M chart)"
useIFVG = input.bool(true, "Include Inverse FVG entries", group=grpLTF)
requireFVG = input.bool(false, "Require FVG/IFVG for entry (off = CHoCH + OB retest only)", group=grpLTF)
grpLIQ = "Liquidity Targets"
liqPivotLen = input.int(10, "Liquidity Pivot Length", group=grpLIQ)
liqTolTicks = input.float(2, "Equal High/Low Tolerance (ticks)", group=grpLIQ)
grpVis = "Display"
showOB = input.bool(true, "Show Order Blocks", group=grpVis)
showCHoCH = input.bool(true, "Show CHoCH Labels", group=grpVis)
showFVG = input.bool(true, "Show FVG/IFVG", group=grpVis)
showLiq = input.bool(true, "Show Liquidity", group=grpVis)
grpKZ = "Kill Zones (New York time)"
useKillzones = input.bool(false, "Only allow entries within kill zones", group=grpKZ)
asiaSession = input.session("2000-0000", "Asia Killzone", group=grpKZ)
londonSession = input.session("0200-0500", "London Killzone", group=grpKZ)
nySession = input.session("0700-1000", "New York Killzone", group=grpKZ)
shadeKZ = input.bool(true, "Shade Kill Zones on Chart", group=grpKZ)
inAsia = not na(time(timeframe.period, asiaSession, "America/New_York"))
inLondon = not na(time(timeframe.period, londonSession, "America/New_York"))
inNY = not na(time(timeframe.period, nySession, "America/New_York"))
inKillzone = inAsia or inLondon or inNY
bgcolor(shadeKZ and inAsia ? color.new(color.purple, 92) : na, title="Asia KZ")
bgcolor(shadeKZ and inLondon ? color.new(color.blue, 92) : na, title="London KZ")
bgcolor(shadeKZ and inNY ? color.new(color.yellow, 92) : na, title="NY KZ")
tick = syminfo.mintick
// ================= HTF ORDER BLOCK =================
[hO, hH, hL, hC, hO1, hH1, hL1, hC1, hTime, hATR] = request.security(syminfo.tickerid, htfOB,
[open, high, low, close, open[1], high[1], low[1], close[1], time, ta.atr(14)], lookahead=barmerge.lookahead_off)
var box[] bullOBs = array.new_box()
var box[] bearOBs = array.new_box()
var int lastHTFTimeOB = na
newHTFBar = na(lastHTFTimeOB) or hTime != lastHTFTimeOB
if newHTFBar
lastHTFTimeOB := hTime
body = math.abs(hC - hO)
bullDisp = hC > hO and body >= hATR * dispMult
bearDisp = hC < hO and body >= hATR * dispMult
prevBear = hC1 < hO1
prevBull = hC1 > hO1
if bullDisp and prevBear and showOB
b = box.new(bar_index, hH1, bar_index + obMaxAgeBars, hL1, border_color=color.new(color.teal, 0), bgcolor=color.new(color.teal, 85), extend=extend.none)
array.push(bullOBs, b)
if bearDisp and prevBull and showOB
b = box.new(bar_index, hH1, bar_index + obMaxAgeBars, hL1, border_color=color.new(color.red, 0), bgcolor=color.new(color.red, 85), extend=extend.none)
array.push(bearOBs, b)
f_manageOB(boxes, isBull) =>
if array.size(boxes) > 0
for i = array.size(boxes) - 1 to 0
b = array.get(boxes, i)
box.set_right(b, bar_index + 5)
top = box.get_top(b)
bot = box.get_bottom(b)
mitigated = isBull ? close < bot : close > top
expired = bar_index > box.get_left(b) + obMaxAgeBars
if mitigated or expired
box.delete(b)
array.remove(boxes, i)
f_manageOB(bullOBs, true)
f_manageOB(bearOBs, false)
priceInBullOB() =>
inside = false
if array.size(bullOBs) > 0
for i = 0 to array.size(bullOBs) - 1
b = array.get(bullOBs, i)
if close <= box.get_top(b) and close >= box.get_bottom(b)
inside := true
inside
priceInBearOB() =>
inside = false
if array.size(bearOBs) > 0
for i = 0 to array.size(bearOBs) - 1
b = array.get(bearOBs, i)
if close >= box.get_bottom(b) and close <= box.get_top(b)
inside := true
inside
// ================= MTF CHoCH =================
[pivHi, pivLo] = request.security(syminfo.tickerid, mtfCHoCH, [ta.pivothigh(pivotLen, pivotLen), ta.pivotlow(pivotLen, pivotLen)], lookahead=barmerge.lookahead_off)
var float lastSwingHigh = na
var float lastSwingLow = na
var string trend = "neutral"
var int bullCHoCHBar = na
var int bearCHoCHBar = na
if not na(pivHi)
lastSwingHigh := pivHi
if not na(pivLo)
lastSwingLow := pivLo
bullishCHoCH = trend != "up" and not na(lastSwingHigh) and close > lastSwingHigh
bearishCHoCH = trend != "down" and not na(lastSwingLow) and close < lastSwingLow
var bool bullSignaled = false
var bool bearSignaled = false
if bullishCHoCH
trend := "up"
bullCHoCHBar := bar_index
bullSignaled := false
if showCHoCH
label.new(bar_index, low, "CHoCH↑", style=label.style_label_up, color=color.new(color.lime, 0), textcolor=color.black, size=size.small)
if bearishCHoCH
trend := "down"
bearCHoCHBar := bar_index
bearSignaled := false
if showCHoCH
label.new(bar_index, high, "CHoCH↓", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, size=size.small)
recentBullCHoCH = not na(bullCHoCHBar) and (bar_index - bullCHoCHBar) <= chochWindow
recentBearCHoCH = not na(bearCHoCHBar) and (bar_index - bearCHoCHBar) <= chochWindow
// ================= LTF FVG / IFVG (runs on chart's own timeframe) =================
bullFVG = low > high[2]
bearFVG = high < low[2]
var box[] bullFVGs = array.new_box()
var box[] bearFVGs = array.new_box()
if showFVG and bullFVG
fb = box.new(bar_index[1], low, bar_index + 20, high[2], border_color=color.new(color.blue, 0), bgcolor=color.new(color.blue, 80))
array.push(bullFVGs, fb)
if showFVG and bearFVG
fb = box.new(bar_index[1], low[2], bar_index + 20, high, border_color=color.new(color.orange, 0), bgcolor=color.new(color.orange, 80))
array.push(bearFVGs, fb)
var bool bullIFVGSignal = false
var bool bearIFVGSignal = false
bullIFVGSignal := false
bearIFVGSignal := false
if array.size(bullFVGs) > 0
for i = array.size(bullFVGs) - 1 to 0
b = array.get(bullFVGs, i)
bot = box.get_bottom(b)
box.set_right(b, bar_index + 5)
if close < bot
if useIFVG
bearIFVGSignal := true
box.delete(b)
array.remove(bullFVGs, i)
else if bar_index > box.get_left(b) + 100
box.delete(b)
array.remove(bullFVGs, i)
if array.size(bearFVGs) > 0
for i = array.size(bearFVGs) - 1 to 0
b = array.get(bearFVGs, i)
top = box.get_top(b)
box.set_right(b, bar_index + 5)
if close > top
if useIFVG
bullIFVGSignal := true
box.delete(b)
array.remove(bearFVGs, i)
else if bar_index > box.get_left(b) + 100
box.delete(b)
array.remove(bearFVGs, i)
// ================= LIQUIDITY (equal highs/lows) =================
liqPH = ta.pivothigh(liqPivotLen, liqPivotLen)
liqPL = ta.pivotlow(liqPivotLen, liqPivotLen)
var float lastPH = na
var float lastPH2 = na
var float lastPL = na
var float lastPL2 = na
if not na(liqPH)
lastPH2 := lastPH
lastPH := liqPH
if showLiq and not na(lastPH2) and math.abs(lastPH - lastPH2) <= liqTolTicks * tick
line.new(bar_index - liqPivotLen, lastPH, bar_index + 30, lastPH, color=color.new(color.fuchsia, 0), style=line.style_dashed)
label.new(bar_index, lastPH, "BSL", style=label.style_label_down, color=color.new(color.fuchsia, 0), textcolor=color.white, size=size.tiny)
if not na(liqPL)
lastPL2 := lastPL
lastPL := liqPL
if showLiq and not na(lastPL2) and math.abs(lastPL - lastPL2) <= liqTolTicks * tick
line.new(bar_index - liqPivotLen, lastPL, bar_index + 30, lastPL, color=color.new(color.aqua, 0), style=line.style_dashed)
label.new(bar_index, lastPL, "SSL", style=label.style_label_up, color=color.new(color.aqua, 0), textcolor=color.black, size=size.tiny)
// ================= ENTRY SIGNAL =================
kzOK = not useKillzones or inKillzone
fvgLongOK = not requireFVG or bullFVG or (useIFVG and bullIFVGSignal)
fvgShortOK = not requireFVG or bearFVG or (useIFVG and bearIFVGSignal)
longSetup = priceInBullOB() and trend == "up" and recentBullCHoCH and fvgLongOK and kzOK and not bullSignaled
shortSetup = priceInBearOB() and trend == "down" and recentBearCHoCH and fvgShortOK and kzOK and not bearSignaled
if longSetup
bullSignaled := true
if shortSetup
bearSignaled := true
plotshape(longSetup, title="Long Entry", style=shape.triangleup, location=location.belowbar, color=color.new(color.lime, 0), size=size.small)
plotshape(shortSetup, title="Short Entry", style=shape.triangledown, location=location.abovebar, color=color.new(color.red, 0), size=size.small)
if longSetup
label.new(bar_index, low, "BUY", style=label.style_label_up, color=color.new(color.lime, 0), textcolor=color.black, size=size.normal)
if shortSetup
label.new(bar_index, high, "SELL", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, size=size.normal)
alertcondition(longSetup, title="Long Setup", message="Bullish OB + CHoCH + FVG entry")
alertcondition(shortSetup, title="Short Setup", message="Bearish OB + CHoCH + FVG entry"
indicator("ICT MTF Entry: OB + CHoCH + FVG + Liquidity", overlay=true, max_boxes_count=500, max_lines_count=500, max_labels_count=500)
// ================= INPUTS =================
grpHTF = "HTF Order Block / POI"
htfOB = input.timeframe("60", "Order Block TF", group=grpHTF)
dispMult = input.float(1.5, "Displacement (x ATR)", group=grpHTF)
obMaxAgeBars = input.int(150, "OB Max Age (chart bars)", group=grpHTF)
grpMTF = "MTF CHoCH"
mtfCHoCH = input.timeframe("15", "CHoCH TF", group=grpMTF)
pivotLen = input.int(5, "Swing Pivot Length (MTF bars)", group=grpMTF)
chochWindow = input.int(30, "CHoCH Validity Window (chart bars)", group=grpMTF)
grpLTF = "Entry FVG / IFVG (uses chart timeframe - run this on your 1M/5M chart)"
useIFVG = input.bool(true, "Include Inverse FVG entries", group=grpLTF)
requireFVG = input.bool(false, "Require FVG/IFVG for entry (off = CHoCH + OB retest only)", group=grpLTF)
grpLIQ = "Liquidity Targets"
liqPivotLen = input.int(10, "Liquidity Pivot Length", group=grpLIQ)
liqTolTicks = input.float(2, "Equal High/Low Tolerance (ticks)", group=grpLIQ)
grpVis = "Display"
showOB = input.bool(true, "Show Order Blocks", group=grpVis)
showCHoCH = input.bool(true, "Show CHoCH Labels", group=grpVis)
showFVG = input.bool(true, "Show FVG/IFVG", group=grpVis)
showLiq = input.bool(true, "Show Liquidity", group=grpVis)
grpKZ = "Kill Zones (New York time)"
useKillzones = input.bool(false, "Only allow entries within kill zones", group=grpKZ)
asiaSession = input.session("2000-0000", "Asia Killzone", group=grpKZ)
londonSession = input.session("0200-0500", "London Killzone", group=grpKZ)
nySession = input.session("0700-1000", "New York Killzone", group=grpKZ)
shadeKZ = input.bool(true, "Shade Kill Zones on Chart", group=grpKZ)
inAsia = not na(time(timeframe.period, asiaSession, "America/New_York"))
inLondon = not na(time(timeframe.period, londonSession, "America/New_York"))
inNY = not na(time(timeframe.period, nySession, "America/New_York"))
inKillzone = inAsia or inLondon or inNY
bgcolor(shadeKZ and inAsia ? color.new(color.purple, 92) : na, title="Asia KZ")
bgcolor(shadeKZ and inLondon ? color.new(color.blue, 92) : na, title="London KZ")
bgcolor(shadeKZ and inNY ? color.new(color.yellow, 92) : na, title="NY KZ")
tick = syminfo.mintick
// ================= HTF ORDER BLOCK =================
[hO, hH, hL, hC, hO1, hH1, hL1, hC1, hTime, hATR] = request.security(syminfo.tickerid, htfOB,
[open, high, low, close, open[1], high[1], low[1], close[1], time, ta.atr(14)], lookahead=barmerge.lookahead_off)
var box[] bullOBs = array.new_box()
var box[] bearOBs = array.new_box()
var int lastHTFTimeOB = na
newHTFBar = na(lastHTFTimeOB) or hTime != lastHTFTimeOB
if newHTFBar
lastHTFTimeOB := hTime
body = math.abs(hC - hO)
bullDisp = hC > hO and body >= hATR * dispMult
bearDisp = hC < hO and body >= hATR * dispMult
prevBear = hC1 < hO1
prevBull = hC1 > hO1
if bullDisp and prevBear and showOB
b = box.new(bar_index, hH1, bar_index + obMaxAgeBars, hL1, border_color=color.new(color.teal, 0), bgcolor=color.new(color.teal, 85), extend=extend.none)
array.push(bullOBs, b)
if bearDisp and prevBull and showOB
b = box.new(bar_index, hH1, bar_index + obMaxAgeBars, hL1, border_color=color.new(color.red, 0), bgcolor=color.new(color.red, 85), extend=extend.none)
array.push(bearOBs, b)
f_manageOB(boxes, isBull) =>
if array.size(boxes) > 0
for i = array.size(boxes) - 1 to 0
b = array.get(boxes, i)
box.set_right(b, bar_index + 5)
top = box.get_top(b)
bot = box.get_bottom(b)
mitigated = isBull ? close < bot : close > top
expired = bar_index > box.get_left(b) + obMaxAgeBars
if mitigated or expired
box.delete(b)
array.remove(boxes, i)
f_manageOB(bullOBs, true)
f_manageOB(bearOBs, false)
priceInBullOB() =>
inside = false
if array.size(bullOBs) > 0
for i = 0 to array.size(bullOBs) - 1
b = array.get(bullOBs, i)
if close <= box.get_top(b) and close >= box.get_bottom(b)
inside := true
inside
priceInBearOB() =>
inside = false
if array.size(bearOBs) > 0
for i = 0 to array.size(bearOBs) - 1
b = array.get(bearOBs, i)
if close >= box.get_bottom(b) and close <= box.get_top(b)
inside := true
inside
// ================= MTF CHoCH =================
[pivHi, pivLo] = request.security(syminfo.tickerid, mtfCHoCH, [ta.pivothigh(pivotLen, pivotLen), ta.pivotlow(pivotLen, pivotLen)], lookahead=barmerge.lookahead_off)
var float lastSwingHigh = na
var float lastSwingLow = na
var string trend = "neutral"
var int bullCHoCHBar = na
var int bearCHoCHBar = na
if not na(pivHi)
lastSwingHigh := pivHi
if not na(pivLo)
lastSwingLow := pivLo
bullishCHoCH = trend != "up" and not na(lastSwingHigh) and close > lastSwingHigh
bearishCHoCH = trend != "down" and not na(lastSwingLow) and close < lastSwingLow
var bool bullSignaled = false
var bool bearSignaled = false
if bullishCHoCH
trend := "up"
bullCHoCHBar := bar_index
bullSignaled := false
if showCHoCH
label.new(bar_index, low, "CHoCH↑", style=label.style_label_up, color=color.new(color.lime, 0), textcolor=color.black, size=size.small)
if bearishCHoCH
trend := "down"
bearCHoCHBar := bar_index
bearSignaled := false
if showCHoCH
label.new(bar_index, high, "CHoCH↓", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, size=size.small)
recentBullCHoCH = not na(bullCHoCHBar) and (bar_index - bullCHoCHBar) <= chochWindow
recentBearCHoCH = not na(bearCHoCHBar) and (bar_index - bearCHoCHBar) <= chochWindow
// ================= LTF FVG / IFVG (runs on chart's own timeframe) =================
bullFVG = low > high[2]
bearFVG = high < low[2]
var box[] bullFVGs = array.new_box()
var box[] bearFVGs = array.new_box()
if showFVG and bullFVG
fb = box.new(bar_index[1], low, bar_index + 20, high[2], border_color=color.new(color.blue, 0), bgcolor=color.new(color.blue, 80))
array.push(bullFVGs, fb)
if showFVG and bearFVG
fb = box.new(bar_index[1], low[2], bar_index + 20, high, border_color=color.new(color.orange, 0), bgcolor=color.new(color.orange, 80))
array.push(bearFVGs, fb)
var bool bullIFVGSignal = false
var bool bearIFVGSignal = false
bullIFVGSignal := false
bearIFVGSignal := false
if array.size(bullFVGs) > 0
for i = array.size(bullFVGs) - 1 to 0
b = array.get(bullFVGs, i)
bot = box.get_bottom(b)
box.set_right(b, bar_index + 5)
if close < bot
if useIFVG
bearIFVGSignal := true
box.delete(b)
array.remove(bullFVGs, i)
else if bar_index > box.get_left(b) + 100
box.delete(b)
array.remove(bullFVGs, i)
if array.size(bearFVGs) > 0
for i = array.size(bearFVGs) - 1 to 0
b = array.get(bearFVGs, i)
top = box.get_top(b)
box.set_right(b, bar_index + 5)
if close > top
if useIFVG
bullIFVGSignal := true
box.delete(b)
array.remove(bearFVGs, i)
else if bar_index > box.get_left(b) + 100
box.delete(b)
array.remove(bearFVGs, i)
// ================= LIQUIDITY (equal highs/lows) =================
liqPH = ta.pivothigh(liqPivotLen, liqPivotLen)
liqPL = ta.pivotlow(liqPivotLen, liqPivotLen)
var float lastPH = na
var float lastPH2 = na
var float lastPL = na
var float lastPL2 = na
if not na(liqPH)
lastPH2 := lastPH
lastPH := liqPH
if showLiq and not na(lastPH2) and math.abs(lastPH - lastPH2) <= liqTolTicks * tick
line.new(bar_index - liqPivotLen, lastPH, bar_index + 30, lastPH, color=color.new(color.fuchsia, 0), style=line.style_dashed)
label.new(bar_index, lastPH, "BSL", style=label.style_label_down, color=color.new(color.fuchsia, 0), textcolor=color.white, size=size.tiny)
if not na(liqPL)
lastPL2 := lastPL
lastPL := liqPL
if showLiq and not na(lastPL2) and math.abs(lastPL - lastPL2) <= liqTolTicks * tick
line.new(bar_index - liqPivotLen, lastPL, bar_index + 30, lastPL, color=color.new(color.aqua, 0), style=line.style_dashed)
label.new(bar_index, lastPL, "SSL", style=label.style_label_up, color=color.new(color.aqua, 0), textcolor=color.black, size=size.tiny)
// ================= ENTRY SIGNAL =================
kzOK = not useKillzones or inKillzone
fvgLongOK = not requireFVG or bullFVG or (useIFVG and bullIFVGSignal)
fvgShortOK = not requireFVG or bearFVG or (useIFVG and bearIFVGSignal)
longSetup = priceInBullOB() and trend == "up" and recentBullCHoCH and fvgLongOK and kzOK and not bullSignaled
shortSetup = priceInBearOB() and trend == "down" and recentBearCHoCH and fvgShortOK and kzOK and not bearSignaled
if longSetup
bullSignaled := true
if shortSetup
bearSignaled := true
plotshape(longSetup, title="Long Entry", style=shape.triangleup, location=location.belowbar, color=color.new(color.lime, 0), size=size.small)
plotshape(shortSetup, title="Short Entry", style=shape.triangledown, location=location.abovebar, color=color.new(color.red, 0), size=size.small)
if longSetup
label.new(bar_index, low, "BUY", style=label.style_label_up, color=color.new(color.lime, 0), textcolor=color.black, size=size.normal)
if shortSetup
label.new(bar_index, high, "SELL", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, size=size.normal)
alertcondition(longSetup, title="Long Setup", message="Bullish OB + CHoCH + FVG entry")
alertcondition(shortSetup, title="Short Setup", message="Bearish OB + CHoCH + FVG entry"
Open-source script
In true PulseWire spirit, the creator of this script has made it open-source, so that traders can review and verify its functionality. Kudos to the author! While you can use it for free, remember that republishing the code is subject to our House Rules.
Disclaimer
The information and publications are not meant to be, and do not constitute, financial, investment, trading, or other types of advice or recommendations supplied or endorsed by PulseWire. Read more in the Terms of Use.
Open-source script
In true PulseWire spirit, the creator of this script has made it open-source, so that traders can review and verify its functionality. Kudos to the author! While you can use it for free, remember that republishing the code is subject to our House Rules.
Disclaimer
The information and publications are not meant to be, and do not constitute, financial, investment, trading, or other types of advice or recommendations supplied or endorsed by PulseWire. Read more in the Terms of Use.
