-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSMC-ICT-Zones-Pro.pine
More file actions
253 lines (211 loc) · 11.4 KB
/
Copy pathSMC-ICT-Zones-Pro.pine
File metadata and controls
253 lines (211 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//@version=6
indicator("SMC/ICT Zones Pro (H1/M15/M5) – OB, FVG, Breaker, Sweeps [v6+]", overlay=true, max_labels_count=500, max_boxes_count=500)
//=============================================================================
// Inputs
//=============================================================================
sym = input.symbol(syminfo.tickerid, "Symbol")
tfTrend = input.timeframe("60", "Trend TF (H1)")
tfEntry = input.timeframe("15", "Entry TF (M15)")
tfMicro = input.timeframe("5", "Micro TF (M5)")
showOB = input.bool(true, "Show Order Blocks")
showFVG = input.bool(true, "Show Fair Value Gaps")
showBreaker = input.bool(true, "Show Breaker Blocks")
showSweeps = input.bool(true, "Show Liquidity Sweeps")
showSignals = input.bool(true, "Show Signals (arrows)")
alertsOn = input.bool(true, "Enable Alert Conditions")
// Production hardening
maxBoxesPerType = input.int(50, "Max boxes per type (keeps chart fast)", minval=0, maxval=500)
boxExtendBars = input.int(250, "Box extend bars (to the right)", minval=10, maxval=5000)
debounceBars = input.int(10, "Signal debounce bars (per side)", minval=0, maxval=500)
oneSignalPerZone= input.bool(true, "One signal per zone touch (per zone)", tooltip="If ON, the same OB/FVG/Breaker won't trigger multiple times until a NEW zone appears.")
//=============================================================================
// Helpers
//=============================================================================
get(src, tf) => request.security(sym, tf, src, barmerge.gaps_off, barmerge.lookahead_off)
hh(tf) => get(high, tf)
ll(tf) => get(low, tf)
op(tf) => get(open, tf)
cl(tf) => get(close, tf)
tm(tf) => get(time, tf) // time at TF bar open (ms since epoch)
// Rolling-box manager
f_trim_boxes(box[] arr, int maxN) =>
if maxN > 0
while array.size(arr) > maxN
box b = array.shift(arr)
box.delete(b)
// Build an anchored box using TF timestamps (left/right)
// Using time stamps makes boxes correctly aligned to the TF, not the chart bars.
f_make_box_time(int leftT, int rightT, float y1, float y2, color borderC, color fillC) =>
// xloc.bar_time: left/right are timestamps (ms)
box.new(leftT, y1, rightT, y2, xloc=xloc.bar_time, border_color=borderC, bgcolor=fillC)
// Convert "extend bars" to milliseconds for approximate future time in the chart TF.
// We do this on chart TF because TradingView doesn't expose a direct "tf ms" reliably.
// It's still anchored at left edge to the correct TF time, and extends visually right.
chartMs = timeframe.in_seconds(timeframe.period) * 1000
rightTimeFromLeft(int leftTime) => leftTime + (boxExtendBars * chartMs)
//=============================================================================
// Trend (H1) – simple HH/HL, LL/LH heuristic
//=============================================================================
trendUp = hh(tfTrend)[1] > hh(tfTrend)[2] and ll(tfTrend)[1] > ll(tfTrend)[2]
trendDown = hh(tfTrend)[1] < hh(tfTrend)[2] and ll(tfTrend)[1] < ll(tfTrend)[2]
trend = trendUp ? 1 : trendDown ? -1 : 0
//=============================================================================
// Storage
//=============================================================================
var box[] obBoxes = array.new_box()
var box[] fvgBoxes = array.new_box()
var box[] brkBoxes = array.new_box()
// "current" zone values (most recently detected on Entry TF)
float obLow = na
float obHigh = na
int obLeftTime = na
float fvgLow = na
float fvgHigh = na
int fvgLeftTime = na
float brLow = na
float brHigh = na
int brLeftTime = na
// Zone IDs so we can do "one signal per zone"
var int obZoneId = 0
var int fvgZoneId = 0
var int brZoneId = 0
// "last-fired" trackers for oneSignalPerZone
var int lastFiredObZoneIdLong = na
var int lastFiredFvgZoneIdLong = na
var int lastFiredBrZoneIdLong = na
var int lastFiredObZoneIdShort = na
var int lastFiredFvgZoneIdShort = na
var int lastFiredBrZoneIdShort = na
// Global debounce trackers (per side)
var int lastLongSignalBar = na
var int lastShortSignalBar = na
//=============================================================================
// ICT Blocks on Entry TF (M15) – detect on new Entry TF bar
//=============================================================================
isNewEntryBar = ta.change(tm(tfEntry))
// OB (last opposite candle) on Entry TF
bearishOB = cl(tfEntry)[2] < op(tfEntry)[2]
bullishOB = cl(tfEntry)[2] > op(tfEntry)[2]
// FVG (3-candle gap) on Entry TF
bullFvg = ll(tfEntry)[0] > hh(tfEntry)[2] // bullish imbalance
bearFvg = hh(tfEntry)[0] < ll(tfEntry)[2] // bearish imbalance
// Breaker proxy: strong candle aligned with trend
bullBreaker = trend == 1 and cl(tfEntry)[1] > op(tfEntry)[1] and cl(tfEntry)[1] > hh(tfEntry)[2]
bearBreaker = trend == -1 and cl(tfEntry)[1] < op(tfEntry)[1] and cl(tfEntry)[1] < ll(tfEntry)[2]
// On new M15 bar: detect + draw boxes anchored to M15 bar time
if isNewEntryBar
// --- Order Block
if showOB
if trend == 1 and bearishOB
obLow := ll(tfEntry)[2]
obHigh := hh(tfEntry)[2]
obLeftTime := int(tm(tfEntry)[2])
obZoneId += 1
int rt = rightTimeFromLeft(obLeftTime)
array.push(obBoxes, f_make_box_time(obLeftTime, rt, obLow, obHigh, color.new(color.teal, 0), color.new(color.teal, 85)))
f_trim_boxes(obBoxes, maxBoxesPerType)
if trend == -1 and bullishOB
obLow := ll(tfEntry)[2]
obHigh := hh(tfEntry)[2]
obLeftTime := int(tm(tfEntry)[2])
obZoneId += 1
int rt = rightTimeFromLeft(obLeftTime)
array.push(obBoxes, f_make_box_time(obLeftTime, rt, obLow, obHigh, color.new(color.orange, 0), color.new(color.orange, 85)))
f_trim_boxes(obBoxes, maxBoxesPerType)
// --- Fair Value Gap
if showFVG
if bullFvg
fvgLow := hh(tfEntry)[2]
fvgHigh := ll(tfEntry)[0]
fvgLeftTime := int(tm(tfEntry)[2])
fvgZoneId += 1
int rt = rightTimeFromLeft(fvgLeftTime)
array.push(fvgBoxes, f_make_box_time(fvgLeftTime, rt, fvgLow, fvgHigh, color.new(color.lime, 0), color.new(color.lime, 88)))
f_trim_boxes(fvgBoxes, maxBoxesPerType)
if bearFvg
fvgLow := hh(tfEntry)[0]
fvgHigh := ll(tfEntry)[2]
fvgLeftTime := int(tm(tfEntry)[2])
fvgZoneId += 1
int rt = rightTimeFromLeft(fvgLeftTime)
array.push(fvgBoxes, f_make_box_time(fvgLeftTime, rt, fvgLow, fvgHigh, color.new(color.red, 0), color.new(color.red, 88)))
f_trim_boxes(fvgBoxes, maxBoxesPerType)
// --- Breaker Block
if showBreaker
if bullBreaker
brLow := ll(tfEntry)[1]
brHigh := hh(tfEntry)[1]
brLeftTime := int(tm(tfEntry)[1])
brZoneId += 1
int rt = rightTimeFromLeft(brLeftTime)
array.push(brkBoxes, f_make_box_time(brLeftTime, rt, brLow, brHigh, color.new(color.green, 0), color.new(color.green, 88)))
f_trim_boxes(brkBoxes, maxBoxesPerType)
if bearBreaker
brLow := ll(tfEntry)[1]
brHigh := hh(tfEntry)[1]
brLeftTime := int(tm(tfEntry)[1])
brZoneId += 1
int rt = rightTimeFromLeft(brLeftTime)
array.push(brkBoxes, f_make_box_time(brLeftTime, rt, brLow, brHigh, color.new(color.maroon, 0), color.new(color.maroon, 88)))
f_trim_boxes(brkBoxes, maxBoxesPerType)
//=============================================================================
// Liquidity Sweeps on Micro TF (M5)
//=============================================================================
priorHigh = ta.highest(get(high, tfMicro), 10)[1]
priorLow = ta.lowest(get(low, tfMicro), 10)[1]
sweepUp = showSweeps and high > priorHigh
sweepDn = showSweeps and low < priorLow
plotshape(sweepUp, title="Sweep Up", style=shape.triangledown, color=color.new(color.fuchsia, 0), size=size.tiny, location=location.abovebar, text="Sweep↑")
plotshape(sweepDn, title="Sweep Down", style=shape.triangleup, color=color.new(color.aqua, 0), size=size.tiny, location=location.belowbar, text="Sweep↓")
//=============================================================================
// Signal logic (touch + micro confirm) + production-grade gating
//=============================================================================
inOBLong = trend == 1 and not na(obLow) and low <= obHigh
inOBShort = trend == -1 and not na(obHigh) and high >= obLow
inFVGLong = trend == 1 and not na(fvgLow) and low <= fvgHigh
inFVGShort = trend == -1 and not na(fvgHigh) and high >= fvgLow
inBRKLong = trend == 1 and not na(brLow) and low <= brHigh
inBRKShort = trend == -1 and not na(brHigh) and high >= brLow
microBull = get(close, tfMicro) > get(open, tfMicro)
microBear = get(close, tfMicro) < get(open, tfMicro)
rawLong = showSignals and trend == 1 and (inOBLong or inFVGLong or inBRKLong or sweepDn) and microBull
rawShort = showSignals and trend == -1 and (inOBShort or inFVGShort or inBRKShort or sweepUp) and microBear
// 1) Debounce: don’t fire too frequently (per side)
debounceOkLong = debounceBars == 0 or na(lastLongSignalBar) or (bar_index - lastLongSignalBar > debounceBars)
debounceOkShort = debounceBars == 0 or na(lastShortSignalBar) or (bar_index - lastShortSignalBar > debounceBars)
// 2) One signal per zone (optional): only allow if this zone id hasn't fired yet
obOkLong = not oneSignalPerZone or (inOBLong and obZoneId != lastFiredObZoneIdLong) or not inOBLong
fvgOkLong = not oneSignalPerZone or (inFVGLong and fvgZoneId != lastFiredFvgZoneIdLong) or not inFVGLong
brOkLong = not oneSignalPerZone or (inBRKLong and brZoneId != lastFiredBrZoneIdLong) or not inBRKLong
obOkShort = not oneSignalPerZone or (inOBShort and obZoneId != lastFiredObZoneIdShort) or not inOBShort
fvgOkShort = not oneSignalPerZone or (inFVGShort and fvgZoneId != lastFiredFvgZoneIdShort) or not inFVGShort
brOkShort = not oneSignalPerZone or (inBRKShort and brZoneId != lastFiredBrZoneIdShort) or not inBRKShort
zoneOkLong = obOkLong and fvgOkLong and brOkLong
zoneOkShort = obOkShort and fvgOkShort and brOkShort
longSig = rawLong and debounceOkLong and zoneOkLong
shortSig = rawShort and debounceOkShort and zoneOkShort
// Update trackers on signal fire
if longSig
lastLongSignalBar := bar_index
if oneSignalPerZone
if inOBLong
lastFiredObZoneIdLong := obZoneId
if inFVGLong
lastFiredFvgZoneIdLong := fvgZoneId
if inBRKLong
lastFiredBrZoneIdLong := brZoneId
if shortSig
lastShortSignalBar := bar_index
if oneSignalPerZone
if inOBShort
lastFiredObZoneIdShort := obZoneId
if inFVGShort
lastFiredFvgZoneIdShort := fvgZoneId
if inBRKShort
lastFiredBrZoneIdShort := brZoneId
// Plot signals
plotshape(longSig, title="BUY", style=shape.arrowup, color=color.new(color.lime, 0), location=location.belowbar, size=size.large, text="BUY")
plotshape(shortSig, title="SELL", style=shape.arrowdown, color=color.new(color.red, 0), location=location.abovebar, size=size.large, text="SELL")
// Alerts
alertcondition(alertsOn and longSig, title="SMC/ICT BUY", message="BUY signal on {{ticker}} (H1/M15/M5 confluence)")
alertcondition(alertsOn and shortSig, title="SMC/ICT SELL", message="SELL signal on {{ticker}} (H1/M15/M5 confluence)")