//@version=6 // 功能:智能支撑/压力位识别系统(优化版) // // 核心改进: // ✅ 强度评分系统:综合触碰次数、反应幅度、成交量、时间衰减 // ✅ 突破检测:识别有效突破并标记 // ✅ S/R 转换:自动检测支撑变压力、压力变支撑 // ✅ 区域显示:支持显示价格区域而非单一线条 // ✅ 智能排序:按强度和距离综合评分选择最重要的水平 // ✅ 性能优化:改进标签管理、减少重复计算 // ✅ 接近预警:可选的价格接近提醒 // // 使用场景: // 1. 震荡交易:在强支撑附近做多,强压力附近做空 // 2. 突破交易:关注"突破"标记的水平线,确认趋势延续 // 3. S/R转换:关注"转换"标记,这些是关键的心理价位 // 4. 风险管理:根据最近S/R和强度分数设置止损位 indicator("智能支撑/压力位 Pro", overlay=true, max_lines_count=250, max_labels_count=150, max_boxes_count=50) // ========== 输入参数 ========== // // 枢轴检测 pivotGroup = "枢轴检测" leftBars = input.int(4, "左侧K线数", minval=1, group=pivotGroup) rightBars = input.int(4, "右侧K线数", minval=1, group=pivotGroup) lookbackBars = input.int(500, "回溯周期", minval=50, maxval=5000, group=pivotGroup) // 聚类与过滤 clusterGroup = "聚类与过滤" atrLength = input.int(14, "ATR周期", minval=1, group=clusterGroup) clusterTolATR = input.float(0.3, "聚类容差 (×ATR)", step=0.05, minval=0.05, group=clusterGroup) minTouches = input.int(2, "最少触碰次数", minval=1, group=clusterGroup) minStrength = input.float(0, "最低强度分数", minval=0, maxval=100, step=5, group=clusterGroup, tooltip="0-100,越高要求越严格") // 强度评分权重 strengthGroup = "强度评分" weightTouches = input.float(40, "触碰次数权重 %", minval=0, maxval=100, group=strengthGroup) weightReaction = input.float(30, "反应强度权重 %", minval=0, maxval=100, group=strengthGroup) weightVolume = input.float(20, "成交量权重 %", minval=0, maxval=100, group=strengthGroup) weightRecency = input.float(10, "时效性权重 %", minval=0, maxval=100, group=strengthGroup) reactionBars = input.int(5, "反应检测K线数", minval=1, maxval=20, group=strengthGroup, tooltip="检测触碰后多少根K线的价格反应") // EMA设置 emaGroup = "EMA线" showEMA = input.bool(true, "显示EMA", group=emaGroup) ema6 = input.int(6, "EMA周期1", minval=1, group=emaGroup) ema12 = input.int(12, "EMA周期2", minval=1, group=emaGroup) ema24 = input.int(24, "EMA周期3", minval=1, group=emaGroup) ema52 = input.int(52, "EMA周期4", minval=1, group=emaGroup) emaWidth = input.int(2, "EMA线宽", minval=1, maxval=5, group=emaGroup) color6 = input.color(#FF6B9D, "EMA6色", group=emaGroup) color12 = input.color(#C44569, "EMA12色", group=emaGroup) color24 = input.color(#FFA502, "EMA24色", group=emaGroup) color52 = input.color(#26A69A, "EMA52色", group=emaGroup) // 显示设置 displayGroup = "显示设置" maxVisibleLevels = input.int(12, "最多显示水平数", minval=1, maxval=50, group=displayGroup) showAsZone = input.bool(false, "显示为区域", group=displayGroup, tooltip="用半透明区域代替线条") zoneWidthATR = input.float(0.15, "区域宽度 (×ATR)", step=0.05, minval=0.05, group=displayGroup) lineWidth = input.int(2, "线条宽度", minval=1, maxval=5, group=displayGroup) resistanceColor = input.color(#FF5252, "压力色", group=displayGroup) supportColor = input.color(#26A69A, "支撑色", group=displayGroup) breakoutColor = input.color(#FFA726, "突破色", group=displayGroup) flippedColor = input.color(#AB47BC, "转换色", group=displayGroup) // 标签设置 labelGroup = "标签设置" showLabels = input.bool(true, "显示标签", inline="lbl1", group=labelGroup) showStrength = input.bool(true, "显示强度", inline="lbl1", group=labelGroup) showTouches = input.bool(true, "显示触碰", inline="lbl2", group=labelGroup) showBreakout = input.bool(true, "标记突破", inline="lbl2", group=labelGroup) showFlipped = input.bool(true, "标记转换", inline="lbl3", group=labelGroup) labelSize = input.string("Small", "标签大小", options=["Tiny", "Small", "Normal", "Large"], group=labelGroup) labelOffset = input.int(2, "标签偏移", minval=0, maxval=100, group=labelGroup) // 预警设置 alertGroup = "预警设置" enableAlerts = input.bool(false, "启用接近预警", group=alertGroup) alertDistanceATR = input.float(0.5, "预警距离 (×ATR)", step=0.1, minval=0.1, group=alertGroup) // ========== 全局变量 ========== // var float[] levelPrices = array.new_float() var int[] levelTouches = array.new_int() var int[] levelSupportTouches = array.new_int() var int[] levelResistTouches = array.new_int() var int[] levelLastTouchBar = array.new_int() var int[] levelFirstTouchBar = array.new_int() var float[] levelReactionSum = array.new_float() // 累积反应幅度 var float[] levelVolumeSum = array.new_float() // 累积成交量 var bool[] levelBrokenUp = array.new_bool() // 向上突破 var bool[] levelBrokenDown = array.new_bool() // 向下突破 var bool[] levelFlipped = array.new_bool() // S/R转换 var line[] levelLines = array.new_line() var box[] levelBoxes = array.new_box() var label[] levelLabels = array.new_label() // 计算 atrValue = ta.atr(atrLength) clusterTolerance = atrValue * clusterTolATR avgVolume = ta.sma(volume, 50) // 计算EMA线 ema6Value = showEMA ? ta.ema(close, ema6) : na ema12Value = showEMA ? ta.ema(close, ema12) : na ema24Value = showEMA ? ta.ema(close, ema24) : na ema52Value = showEMA ? ta.ema(close, ema52) : na // ========== 辅助函数 ========== // // 标签大小转换 f_label_size(opt) => switch opt "Tiny" => size.tiny "Small" => size.small "Normal" => size.normal "Large" => size.large => size.small // 查找相近价位索引 f_find_level(price, tolerance) => int foundIdx = -1 int sz = array.size(levelPrices) if sz > 0 for i = 0 to sz - 1 if math.abs(array.get(levelPrices, i) - price) <= tolerance foundIdx := i break foundIdx // 删除水平线 f_remove_level(idx) => if not na(array.get(levelLines, idx)) line.delete(array.get(levelLines, idx)) if not na(array.get(levelBoxes, idx)) box.delete(array.get(levelBoxes, idx)) if not na(array.get(levelLabels, idx)) label.delete(array.get(levelLabels, idx)) array.remove(levelPrices, idx) array.remove(levelTouches, idx) array.remove(levelSupportTouches, idx) array.remove(levelResistTouches, idx) array.remove(levelLastTouchBar, idx) array.remove(levelFirstTouchBar, idx) array.remove(levelReactionSum, idx) array.remove(levelVolumeSum, idx) array.remove(levelBrokenUp, idx) array.remove(levelBrokenDown, idx) array.remove(levelFlipped, idx) array.remove(levelLines, idx) array.remove(levelBoxes, idx) array.remove(levelLabels, idx) // 清理过期水平 f_cleanup_old_levels() => int sz = array.size(levelPrices) if sz > 0 for k = 0 to sz - 1 i = sz - 1 - k if bar_index - array.get(levelLastTouchBar, i) > lookbackBars f_remove_level(i) // 计算反应强度(触碰后的价格变化) f_calculate_reaction(price, isResistance, touchBar) => float reaction = 0.0 if bar_index >= touchBar + reactionBars // 检查触碰后的价格变化 float maxMove = 0.0 for j = 1 to reactionBars if touchBar + j <= bar_index barIdx = touchBar + j priceMove = isResistance ? (price - low[bar_index - barIdx]) : (high[bar_index - barIdx] - price) maxMove := math.max(maxMove, priceMove) reaction := maxMove reaction // 计算强度分数 (0-100) f_calculate_strength(idx) => touches = array.get(levelTouches, idx) reactionSum = array.get(levelReactionSum, idx) volumeSum = array.get(levelVolumeSum, idx) lastBar = array.get(levelLastTouchBar, idx) firstBar = array.get(levelFirstTouchBar, idx) // 归一化各项指标 touchScore = math.min(touches / 10.0, 1.0) * 100 // 10次触碰为满分 avgReaction = touches > 0 ? reactionSum / touches : 0 reactionScore = math.min(avgReaction / (atrValue * 2), 1.0) * 100 // 2倍ATR反应为满分 avgVol = touches > 0 ? volumeSum / touches : 0 volumeScore = avgVolume > 0 ? math.min(avgVol / avgVolume, 2.0) / 2.0 * 100 : 50 // 时效性:越近期越高分 barsSinceTouch = bar_index - lastBar recencyScore = math.max(0, 100 - (barsSinceTouch / lookbackBars * 100)) // 加权计算总分 totalWeight = weightTouches + weightReaction + weightVolume + weightRecency float strength = 50.0 if totalWeight > 0 strength := (touchScore * weightTouches + reactionScore * weightReaction + volumeScore * weightVolume + recencyScore * weightRecency) / totalWeight strength // 检测突破 f_check_breakout(idx) => price = array.get(levelPrices, idx) supTouches = array.get(levelSupportTouches, idx) resTouches = array.get(levelResistTouches, idx) wasResistance = resTouches >= supTouches brokenUp = false brokenDown = false flipped = false // 突破判断:收盘价显著突破水平(超过容差) if close > price + clusterTolerance and wasResistance brokenUp := true // S/R转换:突破后价格站稳,原压力变支撑 if close > price and low < price + clusterTolerance * 2 flipped := true else if close < price - clusterTolerance and not wasResistance brokenDown := true // S/R转换:跌破后,原支撑变压力 if close < price and high > price - clusterTolerance * 2 flipped := true [brokenUp, brokenDown, flipped] // 添加或更新水平 f_add_or_update_level(price, isResistance) => idx = f_find_level(price, clusterTolerance) if idx == -1 // 新建水平 array.push(levelPrices, price) array.push(levelTouches, 1) array.push(levelSupportTouches, isResistance ? 0 : 1) array.push(levelResistTouches, isResistance ? 1 : 0) array.push(levelLastTouchBar, bar_index) array.push(levelFirstTouchBar, bar_index) array.push(levelReactionSum, 0.0) array.push(levelVolumeSum, volume) array.push(levelBrokenUp, false) array.push(levelBrokenDown, false) array.push(levelFlipped, false) array.push(levelLines, na) array.push(levelBoxes, na) array.push(levelLabels, na) else // 更新现有水平 oldPrice = array.get(levelPrices, idx) touches = array.get(levelTouches, idx) newTouches = touches + 1 // 加权平均更新价格 newPrice = (oldPrice * touches + price) / newTouches array.set(levelPrices, idx, newPrice) array.set(levelTouches, idx, newTouches) array.set(levelLastTouchBar, idx, bar_index) // 更新支撑/压力计数 if isResistance array.set(levelResistTouches, idx, array.get(levelResistTouches, idx) + 1) else array.set(levelSupportTouches, idx, array.get(levelSupportTouches, idx) + 1) // 累积成交量 array.set(levelVolumeSum, idx, array.get(levelVolumeSum, idx) + volume) // 计算并累积反应强度(需要等待几根K线) touchBar = array.get(levelLastTouchBar, idx) reaction = f_calculate_reaction(price, isResistance, touchBar - newTouches + 1) if reaction > 0 array.set(levelReactionSum, idx, array.get(levelReactionSum, idx) + reaction) // 绘制水平线 f_draw_levels() => int sz = array.size(levelPrices) // 收集有效候选 var int[] candidates = array.new_int() array.clear(candidates) if sz > 0 for i = 0 to sz - 1 touches = array.get(levelTouches, i) lastBar = array.get(levelLastTouchBar, i) strength = f_calculate_strength(i) if touches >= minTouches and bar_index - lastBar <= lookbackBars and strength >= minStrength array.push(candidates, i) // 综合评分排序选择(距离×强度) var int[] selected = array.new_int() array.clear(selected) int candSz = array.size(candidates) if candSz > 0 int selectCount = math.min(maxVisibleLevels, candSz) for _ = 0 to selectCount - 1 if array.size(candidates) == 0 break int bestPos = -1 float bestScore = -1 int curSz = array.size(candidates) if curSz > 0 for pos = 0 to curSz - 1 idx = array.get(candidates, pos) price = array.get(levelPrices, idx) strength = f_calculate_strength(idx) // 距离因素(归一化) distance = math.abs(close - price) maxDistance = high - low > 0 ? high - low : atrValue distanceFactor = 1.0 - math.min(distance / (maxDistance * 5), 1.0) // 综合评分:强度占70%,距离占30% score = strength * 0.7 + distanceFactor * 100 * 0.3 if score > bestScore bestScore := score bestPos := pos if bestPos != -1 array.push(selected, array.get(candidates, bestPos)) array.remove(candidates, bestPos) // 标记选中的水平 var bool[] isSelected = array.new_bool() array.clear(isSelected) if sz > 0 for _ = 0 to sz - 1 array.push(isSelected, false) int selSz = array.size(selected) if selSz > 0 and sz > 0 for j = 0 to selSz - 1 idx = array.get(selected, j) if idx >= 0 and idx < sz array.set(isSelected, idx, true) // 绘制或更新图形 if sz > 0 for i = 0 to sz - 1 price = array.get(levelPrices, i) supTouches = array.get(levelSupportTouches, i) resTouches = array.get(levelResistTouches, i) isRes = resTouches >= supTouches // 检测突破和转换 [brokenUp, brokenDown, flipped] = f_check_breakout(i) array.set(levelBrokenUp, i, brokenUp) array.set(levelBrokenDown, i, brokenDown) if flipped array.set(levelFlipped, i, true) // 确定颜色 color col = supportColor if array.get(levelFlipped, i) and showFlipped col := flippedColor else if (brokenUp or brokenDown) and showBreakout col := breakoutColor else if isRes col := resistanceColor else col := supportColor if array.get(isSelected, i) // 绘制水平线 int xRight = bar_index int xLeft = math.max(0, bar_index - lookbackBars) if showAsZone // 绘制区域 float zoneWidth = atrValue * zoneWidthATR float top = price + zoneWidth / 2 float bottom = price - zoneWidth / 2 bx = array.get(levelBoxes, i) if na(bx) bx := box.new(left=xLeft, top=top, right=xRight, bottom=bottom, border_color=col, bgcolor=color.new(col, 90), border_width=1, border_style=line.style_dashed) array.set(levelBoxes, i, bx) else box.set_lefttop(bx, xLeft, top) box.set_rightbottom(bx, xRight, bottom) box.set_border_color(bx, col) box.set_bgcolor(bx, color.new(col, 90)) else // 绘制线条 ln = array.get(levelLines, i) if na(ln) ln := line.new(x1=xLeft, y1=price, x2=xRight, y2=price, color=col, width=lineWidth, style=line.style_dashed) array.set(levelLines, i, ln) else line.set_xy1(ln, xLeft, price) line.set_xy2(ln, xRight, price) line.set_color(ln, col) line.set_width(ln, lineWidth) // 绘制标签 if showLabels touches = array.get(levelTouches, i) strength = f_calculate_strength(i) // 构建标签文本 string lblText = isRes ? "R" : "S" lblText += " " + str.tostring(price, format.price) if showStrength lblText += " [" + str.tostring(math.round(strength), "#") + "]" if showTouches lblText += " ×" + str.tostring(touches) if array.get(levelFlipped, i) and showFlipped lblText += " 🔄" else if brokenUp and showBreakout lblText += " ⬆️" else if brokenDown and showBreakout lblText += " ⬇️" lb = array.get(levelLabels, i) // 只在文本或位置变化时重建 bool needsUpdate = na(lb) if not needsUpdate and not na(lb) oldY = label.get_y(lb) if math.abs(oldY - price) > syminfo.mintick needsUpdate := true if needsUpdate if not na(lb) label.delete(lb) lblX = bar_index + labelOffset lb := label.new(x=lblX, y=price, text=lblText, style=label.style_label_left, color=color.new(col, 85), textcolor=color.white, size=f_label_size(labelSize)) array.set(levelLabels, i, lb) else if not na(lb) label.set_text(lb, lblText) label.set_x(lb, bar_index + labelOffset) label.set_color(lb, color.new(col, 85)) else // 删除未选中的图形 if not na(array.get(levelLines, i)) line.delete(array.get(levelLines, i)) array.set(levelLines, i, na) if not na(array.get(levelBoxes, i)) box.delete(array.get(levelBoxes, i)) array.set(levelBoxes, i, na) if not na(array.get(levelLabels, i)) label.delete(array.get(levelLabels, i)) array.set(levelLabels, i, na) // ========== 主逻辑 ========== // // 检测枢轴点 pivotHigh = ta.pivothigh(high, leftBars, rightBars) pivotLow = ta.pivotlow(low, leftBars, rightBars) // 添加水平 if not na(pivotHigh) f_add_or_update_level(pivotHigh, true) if not na(pivotLow) f_add_or_update_level(pivotLow, false) // 清理和绘制 f_cleanup_old_levels() f_draw_levels() // 绘制EMA线 plot(showEMA ? ema6Value : na, "EMA6", color=color6, linewidth=emaWidth, style=plot.style_line) plot(showEMA ? ema12Value : na, "EMA12", color=color12, linewidth=emaWidth, style=plot.style_line) plot(showEMA ? ema24Value : na, "EMA24", color=color24, linewidth=emaWidth, style=plot.style_line) plot(showEMA ? ema52Value : na, "EMA52", color=color52, linewidth=emaWidth, style=plot.style_line) // 计算最近的支撑和压力 var float nearestSupport = na var float nearestResistance = na float bestSup = na float bestRes = na int sz = array.size(levelPrices) if sz > 0 for i = 0 to sz - 1 touches = array.get(levelTouches, i) lastBar = array.get(levelLastTouchBar, i) if touches >= minTouches and bar_index - lastBar <= lookbackBars price = array.get(levelPrices, i) if price < close bestSup := na(bestSup) ? price : math.max(bestSup, price) else if price > close bestRes := na(bestRes) ? price : math.min(bestRes, price) nearestSupport := bestSup nearestResistance := bestRes // 绘制最近S/R参考线 plot(nearestSupport, "最近支撑", color=color.new(supportColor, 70), linewidth=1, style=plot.style_circles) plot(nearestResistance, "最近压力", color=color.new(resistanceColor, 70), linewidth=1, style=plot.style_circles) // 接近预警 if enableAlerts alertDistance = atrValue * alertDistanceATR if not na(nearestSupport) and math.abs(close - nearestSupport) < alertDistance alert("价格接近支撑位: " + str.tostring(nearestSupport, format.price), alert.freq_once_per_bar) if not na(nearestResistance) and math.abs(close - nearestResistance) < alertDistance alert("价格接近压力位: " + str.tostring(nearestResistance, format.price), alert.freq_once_per_bar) // 在图表上显示统计信息 if barstate.islast and sz > 0 var table statsTable = table.new(position.top_right, 2, 4, border_width=1) int validLevels = 0 float avgStrength = 0.0 for i = 0 to sz - 1 touches = array.get(levelTouches, i) if touches >= minTouches validLevels += 1 avgStrength += f_calculate_strength(i) if validLevels > 0 avgStrength := avgStrength / validLevels table.cell(statsTable, 0, 0, "水平线数", text_color=color.white, bgcolor=color.gray) table.cell(statsTable, 1, 0, str.tostring(validLevels), text_color=color.white, bgcolor=color.gray) table.cell(statsTable, 0, 1, "平均强度", text_color=color.white, bgcolor=color.gray) table.cell(statsTable, 1, 1, str.tostring(math.round(avgStrength), "#"), text_color=color.white, bgcolor=color.gray) table.cell(statsTable, 0, 2, "最近支撑", text_color=color.white, bgcolor=supportColor) table.cell(statsTable, 1, 2, not na(nearestSupport) ? str.tostring(nearestSupport, format.price) : "—", text_color=color.white, bgcolor=supportColor) table.cell(statsTable, 0, 3, "最近压力", text_color=color.white, bgcolor=resistanceColor) table.cell(statsTable, 1, 3, not na(nearestResistance) ? str.tostring(nearestResistance, format.price) : "—", text_color=color.white, bgcolor=resistanceColor)