PROTECTED SOURCE SCRIPT

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

23
//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)

Feragatname

Bilgiler ve yayınlar, TradingView tarafından sağlanan veya onaylanan finansal, yatırım, işlem veya diğer türden tavsiye veya tavsiyeler anlamına gelmez ve teşkil etmez. Kullanım Şartları'nda daha fazlasını okuyun.