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