551 lines
23 KiB
Plaintext
551 lines
23 KiB
Plaintext
//@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)
|
||
|