添加新的支撑和压力位显示
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
//@version=6
|
||||
indicator("Enhanced Support/Resistance", overlay=true, max_lines_count=200, max_labels_count=100)
|
||||
|
||||
// Inputs (保持不变)
|
||||
leftBars = input.int(3, "Pivot Left", minval=1)
|
||||
rightBars = input.int(3, "Pivot Right", minval=1)
|
||||
lookbackBars = input.int(500, "Lookback Bars", minval=50, maxval=5000)
|
||||
atrLength = input.int(14, "ATR Length", minval=1)
|
||||
clusterTolATR = input.float(0.25, "Cluster Tolerance (x ATR)", step=0.05, minval=0.05)
|
||||
minTouches = input.int(2, "Minimum Touches to Validate", minval=1)
|
||||
maxSamplesPerLevel = input.int(20, "Max Samples per Level", minval=5, maxval=100)
|
||||
maxLevelsStored = input.int(60, "Max Stored Levels", minval=10, maxval=300)
|
||||
maxVisibleLevels = input.int(10, "Max Visible Levels", minval=1, maxval=50)
|
||||
lineWidth = input.int(2, "Line Width", minval=1, maxval=5)
|
||||
resistanceColor = input.color(color.new(color.red, 0), "Resistance Color")
|
||||
supportColor = input.color(color.new(color.teal, 0), "Support Color")
|
||||
showPriceLabels = input.bool(true, "Show Price Labels", inline="lbl")
|
||||
showTouchesInLbl = input.bool(true, "Touches In Label", inline="lbl")
|
||||
labelSizeOpt = input.string("Tiny", "Label Size", options=["Tiny", "Small", "Normal", "Large", "Huge"], inline="lbl")
|
||||
labelOffsetBars = input.int(1, "Label Offset (bars to right)", minval=1, maxval=500)
|
||||
dynamicParams = input.bool(true, "Dynamic Parameters")
|
||||
|
||||
// Calculations
|
||||
atrValue = ta.atr(atrLength)
|
||||
clusterTolerance = atrValue * clusterTolATR
|
||||
|
||||
// Dynamic pivot sensitivity
|
||||
dynamicLeft = dynamicParams ? math.max(2, int(5 - (atrValue/close)*100)) : leftBars
|
||||
dynamicRight = dynamicParams ? math.max(2, int(5 - (atrValue/close)*100)) : rightBars
|
||||
|
||||
// Level storage
|
||||
var float[] levelPrices = array.new_float()
|
||||
var int[] levelStartIndexes = array.new_int()
|
||||
var int[] levelSampleCounts = array.new_int()
|
||||
var float[] allPriceSamples = array.new_float()
|
||||
var float[] levelTotalWeightedTouches = array.new_float()
|
||||
var int[] levelLastTouchBarIndex = array.new_int()
|
||||
var line[] levelLines = array.new_line()
|
||||
var label[] levelLabels = array.new_label()
|
||||
|
||||
// Helper functions
|
||||
f_label_size(opt) =>
|
||||
opt == "Tiny" ? size.tiny : opt == "Small" ? size.small : opt == "Normal" ? size.normal :
|
||||
opt == "Large" ? size.large : size.huge
|
||||
|
||||
f_calculate_touch_weight() =>
|
||||
bodySize = math.abs(close - open)
|
||||
candleRange = high - low
|
||||
candleRange > 0 ? math.min(2.0, math.max(0.5, bodySize/candleRange * 3)) : 1.0
|
||||
|
||||
f_find_level_index(price, tolerance) =>
|
||||
int foundIndex = -1
|
||||
sz = array.size(levelPrices)
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
existing = array.get(levelPrices, i)
|
||||
if math.abs(existing - price) <= tolerance
|
||||
foundIndex := i
|
||||
break
|
||||
foundIndex
|
||||
|
||||
f_remove_level(idx) =>
|
||||
if idx >= 0 and idx < array.size(levelPrices) // 添加边界检查
|
||||
ln = array.get(levelLines, idx)
|
||||
if not na(ln)
|
||||
line.delete(ln)
|
||||
lb = array.get(levelLabels, idx)
|
||||
if not na(lb)
|
||||
label.delete(lb)
|
||||
|
||||
startIdx = array.get(levelStartIndexes, idx)
|
||||
sampleCount = array.get(levelSampleCounts, idx)
|
||||
|
||||
for i = 0 to sampleCount - 1
|
||||
if startIdx < array.size(allPriceSamples) // 边界检查
|
||||
array.remove(allPriceSamples, startIdx)
|
||||
|
||||
sz = array.size(levelStartIndexes)
|
||||
for i = idx + 1 to sz - 1
|
||||
if i < array.size(levelStartIndexes) // 边界检查
|
||||
currentStart = array.get(levelStartIndexes, i)
|
||||
array.set(levelStartIndexes, i, currentStart - sampleCount)
|
||||
|
||||
array.remove(levelPrices, idx)
|
||||
array.remove(levelStartIndexes, idx)
|
||||
array.remove(levelSampleCounts, idx)
|
||||
array.remove(levelTotalWeightedTouches, idx)
|
||||
array.remove(levelLastTouchBarIndex, idx)
|
||||
array.remove(levelLines, idx)
|
||||
array.remove(levelLabels, idx)
|
||||
|
||||
f_remove_old_levels() =>
|
||||
sz = array.size(levelPrices)
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
reverseIndex = sz - 1 - i
|
||||
if reverseIndex >= 0 and reverseIndex < array.size(levelLastTouchBarIndex) // 边界检查
|
||||
lastBar = array.get(levelLastTouchBarIndex, reverseIndex)
|
||||
if bar_index - lastBar > lookbackBars
|
||||
f_remove_level(reverseIndex)
|
||||
|
||||
f_ensure_capacity() =>
|
||||
if array.size(levelPrices) >= maxLevelsStored
|
||||
oldestIdx = 0
|
||||
oldestBar = array.get(levelLastTouchBarIndex, 0)
|
||||
for i = 1 to array.size(levelPrices) - 1
|
||||
if i < array.size(levelLastTouchBarIndex) // 边界检查
|
||||
b = array.get(levelLastTouchBarIndex, i)
|
||||
if b < oldestBar
|
||||
oldestBar := b
|
||||
oldestIdx := i
|
||||
f_remove_level(oldestIdx)
|
||||
|
||||
f_calculate_median(samples) =>
|
||||
if array.size(samples) == 0
|
||||
na
|
||||
else
|
||||
sorted = array.copy(samples)
|
||||
array.sort(sorted)
|
||||
mid = int(math.floor(array.size(sorted) / 2))
|
||||
if array.size(sorted) % 2 == 1
|
||||
array.get(sorted, mid)
|
||||
else
|
||||
(array.get(sorted, mid - 1) + array.get(sorted, mid)) / 2
|
||||
|
||||
// 修复:添加完整的边界检查
|
||||
f_add_or_update_level(price, isResistance) =>
|
||||
weight = f_calculate_touch_weight()
|
||||
idx = f_find_level_index(price, clusterTolerance)
|
||||
|
||||
if idx == -1
|
||||
f_ensure_capacity()
|
||||
array.push(levelPrices, price)
|
||||
array.push(levelStartIndexes, array.size(allPriceSamples))
|
||||
array.push(levelSampleCounts, 1)
|
||||
array.push(allPriceSamples, price)
|
||||
array.push(levelTotalWeightedTouches, weight)
|
||||
array.push(levelLastTouchBarIndex, bar_index)
|
||||
array.push(levelLines, na)
|
||||
array.push(levelLabels, na)
|
||||
else
|
||||
// 边界检查:确保idx在有效范围内
|
||||
if idx >= 0 and idx < array.size(levelStartIndexes) and idx < array.size(levelSampleCounts)
|
||||
startIdx = array.get(levelStartIndexes, idx)
|
||||
sampleCount = array.get(levelSampleCounts, idx)
|
||||
array.push(allPriceSamples, price)
|
||||
array.set(levelSampleCounts, idx, sampleCount + 1)
|
||||
|
||||
if sampleCount + 1 > maxSamplesPerLevel
|
||||
if startIdx < array.size(allPriceSamples) // 边界检查
|
||||
array.remove(allPriceSamples, startIdx)
|
||||
array.set(levelSampleCounts, idx, maxSamplesPerLevel)
|
||||
else
|
||||
sz = array.size(levelStartIndexes)
|
||||
// 修复:添加完整的边界检查
|
||||
if idx + 1 <= sz - 1
|
||||
for i = idx + 1 to sz - 1
|
||||
if i < array.size(levelStartIndexes) // 关键修复:防止索引越界
|
||||
currentStart = array.get(levelStartIndexes, i)
|
||||
array.set(levelStartIndexes, i, currentStart + 1)
|
||||
|
||||
// 边界检查
|
||||
if idx < array.size(levelStartIndexes) and idx < array.size(levelSampleCounts)
|
||||
currentStart = array.get(levelStartIndexes, idx)
|
||||
currentCount = array.get(levelSampleCounts, idx)
|
||||
samples = array.new_float()
|
||||
|
||||
// 边界检查:确保索引不越界
|
||||
maxSampleIndex = math.min(currentStart + currentCount - 1, array.size(allPriceSamples) - 1)
|
||||
if currentStart <= maxSampleIndex
|
||||
for i = currentStart to maxSampleIndex
|
||||
if i < array.size(allPriceSamples) // 边界检查
|
||||
samplePrice = array.get(allPriceSamples, i)
|
||||
array.push(samples, samplePrice)
|
||||
|
||||
medianPrice = f_calculate_median(samples)
|
||||
array.set(levelPrices, idx, medianPrice)
|
||||
array.set(levelTotalWeightedTouches, idx, array.get(levelTotalWeightedTouches, idx) + weight)
|
||||
array.set(levelLastTouchBarIndex, idx, bar_index)
|
||||
|
||||
f_draw_levels() =>
|
||||
var int[] candidates = array.new_int()
|
||||
array.clear(candidates)
|
||||
sz = array.size(levelPrices)
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
if i < array.size(levelTotalWeightedTouches) and i < array.size(levelLastTouchBarIndex) // 边界检查
|
||||
touches = array.get(levelTotalWeightedTouches, i)
|
||||
lastBar = array.get(levelLastTouchBarIndex, i)
|
||||
if touches >= minTouches and bar_index - lastBar <= lookbackBars
|
||||
array.push(candidates, i)
|
||||
|
||||
var int[] chosen = array.new_int()
|
||||
array.clear(chosen)
|
||||
candSz = array.size(candidates)
|
||||
if candSz > 0
|
||||
chooseCount = math.min(maxVisibleLevels, candSz)
|
||||
for _ = 0 to chooseCount - 1
|
||||
if array.size(candidates) == 0
|
||||
break
|
||||
bestPos = -1
|
||||
bestDist = 10e10
|
||||
curSz = array.size(candidates)
|
||||
if curSz > 0
|
||||
for pos = 0 to curSz - 1
|
||||
if pos < array.size(candidates) // 边界检查
|
||||
idx = array.get(candidates, pos)
|
||||
if idx < array.size(levelPrices) // 边界检查
|
||||
price = array.get(levelPrices, idx)
|
||||
d = math.abs(close - price)
|
||||
if d < bestDist
|
||||
bestDist := d
|
||||
bestPos := pos
|
||||
if bestPos != -1 and bestPos < array.size(candidates) // 边界检查
|
||||
pickedIdx = array.get(candidates, bestPos)
|
||||
array.push(chosen, pickedIdx)
|
||||
array.remove(candidates, bestPos)
|
||||
|
||||
var bool[] isChosen = array.new_bool()
|
||||
array.clear(isChosen)
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
array.push(isChosen, false)
|
||||
chosenSz = array.size(chosen)
|
||||
if chosenSz > 0 and sz > 0
|
||||
for j = 0 to chosenSz - 1
|
||||
if j < array.size(chosen) // 边界检查
|
||||
chIdx = array.get(chosen, j)
|
||||
if chIdx >= 0 and chIdx < sz
|
||||
array.set(isChosen, chIdx, true)
|
||||
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
if i < array.size(levelLines) and i < array.size(levelPrices) and i < array.size(levelTotalWeightedTouches) // 边界检查
|
||||
ln = array.get(levelLines, i)
|
||||
price = array.get(levelPrices, i)
|
||||
touches = array.get(levelTotalWeightedTouches, i)
|
||||
col = touches >= 2 ? resistanceColor : supportColor
|
||||
|
||||
if i < array.size(isChosen) and array.get(isChosen, i)
|
||||
xRight = bar_index
|
||||
xLeft = math.max(0, bar_index - lookbackBars)
|
||||
|
||||
if na(ln)
|
||||
ln := line.new(x1=xLeft, y1=price, x2=xRight, y2=price,
|
||||
extend=extend.none, color=col, width=lineWidth)
|
||||
line.set_style(ln, line.style_dotted)
|
||||
array.set(levelLines, i, ln)
|
||||
else
|
||||
line.set_xy1(ln, xLeft, price)
|
||||
line.set_xy2(ln, xRight, price)
|
||||
line.set_extend(ln, extend.none)
|
||||
line.set_color(ln, col)
|
||||
line.set_width(ln, lineWidth)
|
||||
line.set_style(ln, line.style_dotted)
|
||||
|
||||
lb = array.get(levelLabels, i)
|
||||
if showPriceLabels
|
||||
lblTxt = (touches >= 2 ? "R " : "S ") +
|
||||
str.tostring(price, "#.##") +
|
||||
(showTouchesInLbl ? " x" + str.tostring(math.round(touches)) : "")
|
||||
|
||||
lblX = bar_index + labelOffsetBars
|
||||
desiredSize = f_label_size(labelSizeOpt)
|
||||
|
||||
if na(lb)
|
||||
lb := label.new(x=lblX, y=price, text=lblTxt,
|
||||
style=label.style_label_left,
|
||||
color=color.new(col, 85),
|
||||
textcolor=color.white,
|
||||
size=desiredSize)
|
||||
array.set(levelLabels, i, lb)
|
||||
else
|
||||
label.set_text(lb, lblTxt)
|
||||
label.set_x(lb, lblX)
|
||||
label.set_y(lb, price)
|
||||
else if not na(lb)
|
||||
label.delete(lb)
|
||||
array.set(levelLabels, i, na)
|
||||
else if not na(ln)
|
||||
line.delete(ln)
|
||||
array.set(levelLines, i, na)
|
||||
lb = array.get(levelLabels, i)
|
||||
if not na(lb)
|
||||
label.delete(lb)
|
||||
array.set(levelLabels, i, na)
|
||||
|
||||
// Detect pivots
|
||||
ph = ta.pivothigh(high, dynamicLeft, dynamicRight)
|
||||
pl = ta.pivotlow(low, dynamicLeft, dynamicRight)
|
||||
|
||||
if not na(ph)
|
||||
f_add_or_update_level(ph, true)
|
||||
if not na(pl)
|
||||
f_add_or_update_level(pl, false)
|
||||
|
||||
// Maintenance and drawing
|
||||
f_remove_old_levels()
|
||||
f_draw_levels()
|
||||
|
||||
// Nearest levels
|
||||
var float nearestSupport = na
|
||||
var float nearestResistance = na
|
||||
|
||||
float bestBelow = na
|
||||
float bestAbove = na
|
||||
sz = array.size(levelPrices)
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
if i < array.size(levelTotalWeightedTouches) and i < array.size(levelLastTouchBarIndex) // 边界检查
|
||||
touches = array.get(levelTotalWeightedTouches, i)
|
||||
lastBar = array.get(levelLastTouchBarIndex, i)
|
||||
if touches >= minTouches and bar_index - lastBar <= lookbackBars
|
||||
if i < array.size(levelPrices) // 边界检查
|
||||
p = array.get(levelPrices, i)
|
||||
if p <= close and (na(bestBelow) or p > bestBelow)
|
||||
bestBelow := p
|
||||
if p >= close and (na(bestAbove) or p < bestAbove)
|
||||
bestAbove := p
|
||||
|
||||
nearestSupport := bestBelow
|
||||
nearestResistance := bestAbove
|
||||
|
||||
plot(nearestSupport, "Nearest Support", color.new(supportColor, 60), 2, plot.style_linebr)
|
||||
plot(nearestResistance, "Nearest Resistance", color.new(resistanceColor, 60), 2, plot.style_linebr)
|
||||
Reference in New Issue
Block a user