PROTECTED SOURCE SCRIPT

Most-Crossed Channels (FAST • Top-K • Flexible Window)

34
//version=5
indicator("Most-Crossed Channels (FAST • Top-K • Flexible Window)", overlay=true, max_boxes_count=60, max_labels_count=60)

// ---------- Inputs ----------
windowMode = input.string(defval="Last N Bars", title="Scan Window", options=["Last N Bars","Today (session)","Last N Sessions","Last X Minutes","Since time"])
barsLookback = input.int(defval=800, title="If Last N Bars → how many?", minval=100, maxval=5000)
sess = input.session(defval="0830-1500", title="Session (exchange tz)")
sessionsBack = input.int(defval=1, title="If Last N Sessions → how many?", minval=1, maxval=10)
minutesLookback = input.int(defval=120, title="If Last X Minutes → how many?", minval=5, maxval=24*60)
sinceTs = input.time(defval=timestamp("2024-01-01T09:30:00"), title="Since time (chart tz)")

channelsK = input.int(defval=3, title="How many channels (Top-K)?", minval=1, maxval=10)
binTicks = input.int(defval=8, title="Bin width (ticks)", minval=1, maxval=200) // NQ tick=0.25; 8 ticks = 2.0 pts
minSepTicks = input.int(defval=12, title="Min separation between channels (ticks)", minval=1, maxval=500)

countSource = input.string(defval="Wick (H-L)", title="Count bars using", options=["Wick (H-L)","Body only"])
drawMode = input.string(defval="Use Candle", title="Draw channel as", options=["Use Candle","Fixed Thickness"])
anchorPart = input.string(defval="Body", title="If Use Candle → part", options=["Body","Wick (High–Low)"])
fixedTicks = input.int(defval=8, title="If Fixed Thickness → thickness (ticks)", minval=1, maxval=200)

extendBars = input.int(defval=400, title="Extend to right (bars)", minval=50, maxval=5000)
showLabels = input.bool(defval=true, title="Show labels with counts")

// ---------- Colors ----------
colFill = color.new(color.blue, 78)
colEdge = color.new(color.blue, 0)
colTxt = color.white

// ---------- Draw caches (never empty) ----------
var box[] g_boxes = array.new_box()
var label[] g_lbls = array.new_label()

// ---------- Helpers ----------
barsFromMinutes(mins, avgBarMs) =>
ms = mins * 60000.0
int(math.max(2, math.round(ms / nz(avgBarMs, 60000.0))))

// First (oldest) candle in [0..scanN-1] whose selected part contains `level`
anchorIndexForPrice(level, useBody, scanNLocal) =>
idx = -1
for m = 1 to scanNLocal - 1
k = scanNLocal - m // oldest → newest
o = open[k]
c = close[k]
h = high[k]
l = low[k]
topZ = useBody ? math.max(o, c) : h
botZ = useBody ? math.min(o, c) : l
if level >= botZ and level <= topZ
idx := k
break
idx

// ---------- Window depth ----------
inSess = not na(time(timeframe.period, sess))
sessStartIdx = ta.valuewhen(inSess and not inSess[1], bar_index, 0)
sessStartIdxN = ta.valuewhen(inSess and not inSess[1], bar_index, sessionsBack - 1)
sinceStartIdx = ta.valuewhen(time >= sinceTs and time[1] < sinceTs, bar_index, 0)
avgBarMs = ta.sma(time - time[1], 50)

depthRaw = switch windowMode
"Last N Bars" => barsLookback
"Today (session)" => bar_index - nz(sessStartIdx, bar_index)
"Last N Sessions" => bar_index - nz(sessStartIdxN, bar_index)
"Last X Minutes" => barsFromMinutes(minutesLookback, avgBarMs)
"Since time" => bar_index - nz(sinceStartIdx, bar_index)

avail = bar_index + 1
scanN = math.min(avail, math.max(2, depthRaw))
scanN := math.min(scanN, 2000) // performance cap

// ---------- Early guard ----------
if scanN < 2
na
else
// ---------- Build price histogram (O(N + B)) ----------
priceMin = 10e10
priceMax = -10e10
for j = 0 to scanN - 1
loB = math.min(open[j], close[j])
hiB = math.max(open[j], close[j])
lo = (countSource == "Body only") ? loB : low[j]
hi = (countSource == "Body only") ? hiB : high[j]
priceMin := math.min(priceMin, nz(lo, priceMin))
priceMax := math.max(priceMax, nz(hi, priceMax))

rng = priceMax - priceMin
tick = syminfo.mintick
binSize = tick * binTicks

if na(rng) or rng <= 0 or binSize <= 0
na
else
// Pre-allocate fixed-size arrays (never size 0)
MAX_BINS = 600
var float[] diff = array.new_float(MAX_BINS + 2, 0.0) // +2 so iH+1 is safe
var float[] counts = array.new_float(MAX_BINS + 1, 0.0)
var int[] blocked = array.new_int(MAX_BINS + 1, 0)
var int[] topIdx = array.new_int()

binsN = math.max(1, math.min(MAX_BINS, int(math.ceil(rng / binSize)) + 1))

// reset slices
for i = 0 to binsN + 1
array.set(diff, i, 0.0)
for i = 0 to binsN
array.set(counts, i, 0.0)
array.set(blocked, i, 0)
array.clear(topIdx)

// Range adds
for j = 0 to scanN - 1
loB = math.min(open[j], close[j])
hiB = math.max(open[j], close[j])
lo = (countSource == "Body only") ? loB : low[j]
hi = (countSource == "Body only") ? hiB : high[j]
iL = int(math.floor((lo - priceMin) / binSize))
iH = int(math.floor((hi - priceMin) / binSize))
iL := math.max(0, math.min(binsN - 1, iL))
iH := math.max(0, math.min(binsN - 1, iH))
array.set(diff, iL, array.get(diff, iL) + 1.0)
array.set(diff, iH + 1, array.get(diff, iH + 1) - 1.0)

// Prefix sum → counts
run = 0.0
for b = 0 to binsN - 1
run += array.get(diff, b)
array.set(counts, b, run)

// Top-K with spacing
sepBins = math.max(1, int(math.ceil(minSepTicks / binTicks)))
picks = math.min(channelsK, binsN)
if picks > 0
for _ = 0 to picks - 1
bestVal = -1e9
bestBin = -1
for b = 0 to binsN - 1
if array.get(blocked, b) == 0
v = array.get(counts, b)
if v > bestVal
bestVal := v
bestBin := b
if bestBin >= 0
array.push(topIdx, bestBin)
lB = math.max(0, bestBin - sepBins)
rB = math.min(binsN - 1, bestBin + sepBins)
for bb = lB to rB
array.set(blocked, bb, 1)

// Clear old drawings safely
while array.size(g_boxes) > 0
box.delete(array.pop(g_boxes))
while array.size(g_lbls) > 0
label.delete(array.pop(g_lbls))

// Draw Top-K channels
sz = array.size(topIdx)
if sz > 0
for t = 0 to sz - 1
b = array.get(topIdx, t)
level = priceMin + (b + 0.5) * binSize
useBody = (drawMode == "Use Candle")

anc = anchorIndexForPrice(level, useBody, scanN)
anc := anc == -1 ? scanN - 1 : anc

oA = open[anc]
cA = close[anc]
hA = high[anc]
lA = low[anc]

float topV = na
float botV = na
if drawMode == "Use Candle"
topV := (anchorPart == "Body") ? math.max(oA, cA) : hA
botV := (anchorPart == "Body") ? math.min(oA, cA) : lA
else
half = (fixedTicks * tick) * 0.5
topV := level + half
botV := level - half

left = bar_index - anc
right = bar_index + extendBars

bx = box.new(left, topV, right, botV, xloc=xloc.bar_index, bgcolor=colFill, border_color=colEdge, border_width=2)
array.push(g_boxes, bx)

if showLabels
txt = str.tostring(int(array.get(counts, b))) + " crosses"
lb = label.new(left, topV, txt, xloc=xloc.bar_index, style=label.style_label_down, textcolor=colTxt, color=colEdge)
array.push(g_lbls, lb)

Thông báo miễn trừ trách nhiệm

Thông tin và ấn phẩm không có nghĩa là và không cấu thành, tài chính, đầu tư, kinh doanh, hoặc các loại lời khuyên hoặc khuyến nghị khác được cung cấp hoặc xác nhận bởi TradingView. Đọc thêm trong Điều khoản sử dụng.