添加新的支撑和压力位显示
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)
|
||||
+524
@@ -0,0 +1,524 @@
|
||||
//@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线的价格反应")
|
||||
|
||||
// 显示设置
|
||||
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)
|
||||
|
||||
// ========== 辅助函数 ========== //
|
||||
|
||||
// 标签大小转换
|
||||
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()
|
||||
|
||||
// 计算最近的支撑和压力
|
||||
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)
|
||||
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
# 支撑/压力位识别系统 - 优化版说明
|
||||
|
||||
## 📊 核心改进对比
|
||||
|
||||
### 原版本特点
|
||||
✅ 基于枢轴点识别
|
||||
✅ ATR 容差聚类
|
||||
✅ 触碰次数统计
|
||||
✅ 动态价格更新
|
||||
|
||||
### 优化版新增功能
|
||||
|
||||
#### 1️⃣ **智能强度评分系统** (0-100分)
|
||||
综合四个维度评估每个水平线的重要性:
|
||||
|
||||
- **触碰次数** (40%):触碰越多越可靠,10次触碰为满分
|
||||
- **反应强度** (30%):价格触碰后的反弹/回落幅度,2倍ATR为满分
|
||||
- **成交量确认** (25%):触碰时的成交量,高于平均成交量得高分
|
||||
- **时效性** (10%):最近触碰的水平权重更高,采用时间衰减
|
||||
|
||||
**实战意义**:
|
||||
- 强度 80+ 分:极强水平,可作为关键支撑/压力位
|
||||
- 强度 60-79 分:中等强度,适合辅助判断
|
||||
- 强度 <60 分:弱水平,谨慎参考
|
||||
|
||||
#### 2️⃣ **突破检测与标记** ⬆️⬇️
|
||||
自动检测价格有效突破水平线:
|
||||
- **向上突破** (⬆️):收盘价突破压力位 > 容差
|
||||
- **向下突破** (⬇️):收盘价跌破支撑位 > 容差
|
||||
- 突破后水平线显示为橙色
|
||||
|
||||
**交易应用**:
|
||||
- 突破确认后顺势入场
|
||||
- 假突破回测原水平线时反向操作
|
||||
- 结合成交量判断突破有效性
|
||||
|
||||
#### 3️⃣ **S/R 转换检测** 🔄
|
||||
识别关键的角色转换:
|
||||
- **压力变支撑**:突破压力后,回测不破,原压力成为新支撑
|
||||
- **支撑变压力**:跌破支撑后,反弹受阻,原支撑成为新压力
|
||||
- 转换后显示为紫色,标记 🔄
|
||||
|
||||
**心理意义**:
|
||||
- S/R 转换位是市场多空力量逆转的关键点
|
||||
- 这些位置往往伴随重要的交易机会
|
||||
- 转换确认后可作为最强支撑/压力参考
|
||||
|
||||
#### 4️⃣ **区域显示模式**
|
||||
可选择显示为半透明区域而非线条:
|
||||
- 更符合实际交易中 S/R 是"区域"而非精确价位的特点
|
||||
- 区域宽度可调节(ATR 倍数)
|
||||
- 减少对精确点位的过度依赖
|
||||
|
||||
#### 5️⃣ **智能优先级排序**
|
||||
不再仅按距离选择显示的水平线,而是综合评分:
|
||||
- **强度权重 70%**:优先显示强度高的水平
|
||||
- **距离权重 30%**:兼顾当前价格附近的水平
|
||||
- 确保看到最重要的 S/R 位
|
||||
|
||||
#### 6️⃣ **接近预警功能**
|
||||
可设置价格接近 S/R 时自动预警:
|
||||
- 自定义预警距离(ATR 倍数)
|
||||
- 每根K线最多触发一次
|
||||
- 帮助及时关注关键价位
|
||||
|
||||
#### 7️⃣ **实时统计面板**
|
||||
右上角显示当前市场统计:
|
||||
- 有效水平线数量
|
||||
- 平均强度分数
|
||||
- 最近支撑价格
|
||||
- 最近压力价格
|
||||
|
||||
#### 8️⃣ **性能优化**
|
||||
- 标签只在必要时重建,减少重复操作
|
||||
- 优化数组操作逻辑
|
||||
- 改进突破检测算法
|
||||
|
||||
## 🎯 参数配置建议
|
||||
|
||||
### 日内交易(5分钟 - 15分钟图)
|
||||
```
|
||||
枢轴检测:左侧3,右侧3
|
||||
回溯周期:300
|
||||
聚类容差:0.2-0.3×ATR
|
||||
最少触碰:2次
|
||||
最低强度:40分
|
||||
```
|
||||
|
||||
### 波段交易(1小时 - 4小时图)
|
||||
```
|
||||
枢轴检测:左侧4,右侧4
|
||||
回溯周期:500
|
||||
聚类容差:0.3-0.4×ATR
|
||||
最少触碰:3次
|
||||
最低强度:50分
|
||||
```
|
||||
|
||||
### 趋势交易(日线 - 周线图)
|
||||
```
|
||||
枢轴检测:左侧5,右侧5
|
||||
回溯周期:200
|
||||
聚类容差:0.4-0.5×ATR
|
||||
最少触碰:4次
|
||||
最低强度:60分
|
||||
```
|
||||
|
||||
## 📈 使用场景
|
||||
|
||||
### 场景1:震荡区间交易
|
||||
1. 找到强度 >70 的支撑和压力
|
||||
2. 在支撑附近做多,压力附近做空
|
||||
3. 止损设在水平线外 1-1.5 倍 ATR
|
||||
4. 目标位设在对面的 S/R 位
|
||||
|
||||
### 场景2:突破交易
|
||||
1. 关注带有 ⬆️⬇️ 标记的突破水平
|
||||
2. 等待回测确认(假突破过滤)
|
||||
3. 回测不破原水平 → 顺势入场
|
||||
4. 止损设在突破前的高/低点
|
||||
|
||||
### 场景3:S/R 转换交易
|
||||
1. 重点关注 🔄 标记的转换位
|
||||
2. 这些位置往往成为新的强支撑/压力
|
||||
3. 在转换位附近等待入场信号
|
||||
4. 转换位破位时及时止损
|
||||
|
||||
### 场景4:多周期确认
|
||||
1. 在大周期(日线)找强支撑/压力
|
||||
2. 切换到小周期(1小时)等待价格到达
|
||||
3. 小周期形成反转信号时入场
|
||||
4. 大周期 S/R 作为最终止损位
|
||||
|
||||
## 🔧 颜色含义
|
||||
|
||||
| 颜色 | 含义 | 说明 |
|
||||
|------|------|------|
|
||||
| 🟢 青绿色 | 支撑位 | 价格下方的支撑力量 |
|
||||
| 🔴 红色 | 压力位 | 价格上方的阻力 |
|
||||
| 🟠 橙色 | 突破位 | 已被突破的水平线 |
|
||||
| 🟣 紫色 | 转换位 | S/R 角色转换的关键位 |
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **不是圣杯**:S/R 只是辅助工具,需结合其他指标和价格行为
|
||||
2. **假突破**:市场经常出现假突破,需要确认机制
|
||||
3. **新闻影响**:重大新闻可能导致 S/R 失效
|
||||
4. **趋势优先**:强趋势中,S/R 作用会减弱
|
||||
5. **资金管理**:无论 S/R 多强,都要做好止损和仓位控制
|
||||
|
||||
## 🆚 对比原版的主要优势
|
||||
|
||||
| 特性 | 原版 | 优化版 |
|
||||
|------|------|---------|
|
||||
| 强度评分 | ❌ 仅触碰次数 | ✅ 四维度综合评分 |
|
||||
| 突破检测 | ❌ | ✅ 自动检测并标记 |
|
||||
| S/R转换 | ❌ | ✅ 智能识别转换 |
|
||||
| 成交量确认 | ❌ | ✅ 纳入强度计算 |
|
||||
| 时间衰减 | ❌ | ✅ 近期更高权重 |
|
||||
| 显示方式 | 线条 | 线条/区域可选 |
|
||||
| 选择算法 | 仅距离 | 强度+距离综合 |
|
||||
| 预警功能 | ❌ | ✅ 接近自动提醒 |
|
||||
| 统计面板 | ❌ | ✅ 实时市场统计 |
|
||||
|
||||
## 💡 高级技巧
|
||||
|
||||
### 技巧1:强度分层策略
|
||||
- 80+ 分水平:作为主要交易依据
|
||||
- 60-79 分:作为辅助参考
|
||||
- <60 分:仅用于观察
|
||||
|
||||
### 技巧2:突破确认三步法
|
||||
1. 收盘价突破水平线
|
||||
2. 成交量放大(标签显示)
|
||||
3. 回测不破(等待2-3根K线)
|
||||
|
||||
### 技巧3:转换位重点关注
|
||||
转换位的重要性通常大于普通 S/R:
|
||||
- 市场心理变化的体现
|
||||
- 往往伴随更强的支撑/压力作用
|
||||
- 破位后影响更大
|
||||
|
||||
### 技巧4:多空力量对比
|
||||
查看标签中的触碰次数:
|
||||
- 支撑触碰多 → 买盘强劲
|
||||
- 压力触碰多 → 卖压较重
|
||||
- 对比两者评估多空力量对比
|
||||
|
||||
### 技巧5:ATR 容差优化
|
||||
根据市场波动调整容差:
|
||||
- 高波动市场(加密货币):增大到 0.4-0.5
|
||||
- 低波动市场(外汇主要货币对):减小到 0.2-0.3
|
||||
- 确保水平线不过密也不过疏
|
||||
|
||||
## 📞 常见问题
|
||||
|
||||
**Q: 为什么有时候强度很高的水平没显示?**
|
||||
A: 可能超出了回溯周期,或者不在最近的 maxVisibleLevels 个水平中。可以增大这些参数。
|
||||
|
||||
**Q: 突破标记后价格又回来了,怎么办?**
|
||||
A: 这就是假突破,属于正常现象。等待回测确认是过滤假突破的关键。
|
||||
|
||||
**Q: 强度分数一直在变化正常吗?**
|
||||
A: 正常。强度包含时效性,随时间推移会衰减。新的触碰会提升强度。
|
||||
|
||||
**Q: 区域模式和线条模式哪个好?**
|
||||
A: 区域模式更符合实际,但线条模式更清晰。建议根据个人习惯选择。
|
||||
|
||||
**Q: 如何判断突破是否有效?**
|
||||
A: 看三点:1) 收盘价突破 2) 成交量确认 3) 回测不破。三者都满足成功率较高。
|
||||
|
||||
---
|
||||
|
||||
**版本**:v1.0 优化版
|
||||
**更新日期**:2025-10-21
|
||||
**适用于**:TradingView Pine Script v6
|
||||
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
//@version=6
|
||||
// 功能:短期支撑/压力位(当前周期)
|
||||
// 原理:
|
||||
// - 基于枢轴点(Pivot)在当前时间周期内形成的高点/低点,结合 ATR 容差进行“价格聚类”,
|
||||
// 聚合出更稳定的水平价位;用触碰次数过滤弱水平。
|
||||
// - 每条水平线从“最新K线”向左以虚线绘制,右侧固定显示标签(类型S/R、价格、触碰次数)。
|
||||
// 主要参数:
|
||||
// - Pivot Left/Right:枢轴确认强度;越大越严格、信号越少。
|
||||
// - Minimum Touches:最少触碰次数;越大越稳定、越少越敏感。
|
||||
// - ATR Length / Cluster Tolerance:聚类容差(ATR倍数);越大越易合并为少量更粗水平。
|
||||
// - Max Visible Levels:图上最多显示的水平数量。
|
||||
// - Label Size / Label Offset:标签字号与向右偏移的柱数。
|
||||
// 交易应用(示例,不构成建议):
|
||||
// - 震荡区间:靠近“支撑S”观察反弹做多;靠近“压力R”观察回落做空;以水平外的 ATR 容差作为入场缓冲。
|
||||
// - 趋势回踩:上升趋势中,回踩最近“支撑S”且收盘未跌破→顺势接回;跌破并收回失败→止损或反手。
|
||||
// - 突破回测:收盘有效突破“压力R”,回测不跌回→看多延续;跌破“支撑S”并回测不过→看空延续。
|
||||
// - 风险控制:可用“当前价与最近S/R的距离/ATR 倍数”推导止损间距与仓位;(若需要,可在脚本中扩展显示)。
|
||||
// 说明:本脚本不使用未来函数;水平会随新枢轴与触碰实时更新。
|
||||
|
||||
indicator("Support/Resistance (Current TF)", 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)
|
||||
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)
|
||||
|
||||
// Calculations
|
||||
atrValue = ta.atr(atrLength)
|
||||
clusterTolerance = atrValue * clusterTolATR
|
||||
|
||||
// Level storage
|
||||
var float[] levelPrices = array.new_float()
|
||||
var int[] levelTotalTouches = array.new_int()
|
||||
var int[] levelLastTouchBarIndex = array.new_int()
|
||||
var int[] levelSupportTouches = array.new_int()
|
||||
var int[] levelResistanceTouches = array.new_int()
|
||||
var line[] levelLines = array.new_line()
|
||||
var label[] levelLabels = array.new_label()
|
||||
|
||||
// helper to map size option to Pine size enum
|
||||
f_label_size(opt) =>
|
||||
opt == "Tiny" ? size.tiny : opt == "Small" ? size.small : opt == "Normal" ? size.normal : opt == "Large" ? size.large : size.huge
|
||||
|
||||
// Utilities
|
||||
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) =>
|
||||
ln = array.get(levelLines, idx)
|
||||
if not na(ln)
|
||||
line.delete(ln)
|
||||
lb = array.get(levelLabels, idx)
|
||||
if not na(lb)
|
||||
label.delete(lb)
|
||||
array.remove(levelPrices, idx)
|
||||
array.remove(levelTotalTouches, idx)
|
||||
array.remove(levelLastTouchBarIndex, idx)
|
||||
array.remove(levelSupportTouches, idx)
|
||||
array.remove(levelResistanceTouches, idx)
|
||||
array.remove(levelLines, idx)
|
||||
array.remove(levelLabels, idx)
|
||||
|
||||
f_remove_old_levels() =>
|
||||
sz = array.size(levelPrices)
|
||||
if sz > 0
|
||||
// iterate safely in reverse using computed index
|
||||
for k = 0 to sz - 1
|
||||
i = sz - 1 - k
|
||||
lastBar = array.get(levelLastTouchBarIndex, i)
|
||||
if bar_index - lastBar > lookbackBars
|
||||
f_remove_level(i)
|
||||
|
||||
f_ensure_capacity() =>
|
||||
if array.size(levelPrices) >= maxLevelsStored
|
||||
// Remove the oldest by last touch
|
||||
oldestIdx = 0
|
||||
oldestBar = array.get(levelLastTouchBarIndex, 0)
|
||||
for i = 1 to array.size(levelPrices) - 1
|
||||
b = array.get(levelLastTouchBarIndex, i)
|
||||
if b < oldestBar
|
||||
oldestBar := b
|
||||
oldestIdx := i
|
||||
f_remove_level(oldestIdx)
|
||||
|
||||
f_add_or_update_level(price, isResistance) =>
|
||||
idx = f_find_level_index(price, clusterTolerance)
|
||||
if idx == -1
|
||||
f_ensure_capacity()
|
||||
array.push(levelPrices, price)
|
||||
array.push(levelTotalTouches, 1)
|
||||
array.push(levelLastTouchBarIndex, bar_index)
|
||||
array.push(levelSupportTouches, isResistance ? 0 : 1)
|
||||
array.push(levelResistanceTouches, isResistance ? 1 : 0)
|
||||
array.push(levelLines, na)
|
||||
array.push(levelLabels, na)
|
||||
else
|
||||
prevPrice = array.get(levelPrices, idx)
|
||||
touches = array.get(levelTotalTouches, idx)
|
||||
newTouches = touches + 1
|
||||
// Re-anchor price by averaging to stabilize the level
|
||||
newPrice = (prevPrice * touches + price) / newTouches
|
||||
array.set(levelPrices, idx, newPrice)
|
||||
array.set(levelTotalTouches, idx, newTouches)
|
||||
array.set(levelLastTouchBarIndex, idx, bar_index)
|
||||
if isResistance
|
||||
r = array.get(levelResistanceTouches, idx) + 1
|
||||
array.set(levelResistanceTouches, idx, r)
|
||||
else
|
||||
s = array.get(levelSupportTouches, idx) + 1
|
||||
array.set(levelSupportTouches, idx, s)
|
||||
|
||||
f_draw_levels() =>
|
||||
// Collect candidate indices
|
||||
var int[] candidates = array.new_int()
|
||||
array.clear(candidates)
|
||||
sz = array.size(levelPrices)
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
touches = array.get(levelTotalTouches, i)
|
||||
lastBar = array.get(levelLastTouchBarIndex, i)
|
||||
if touches >= minTouches and bar_index - lastBar <= lookbackBars
|
||||
array.push(candidates, i)
|
||||
|
||||
// Select up to maxVisibleLevels by distance to close
|
||||
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
|
||||
idx = array.get(candidates, pos)
|
||||
price = array.get(levelPrices, idx)
|
||||
d = math.abs(close - price)
|
||||
if d < bestDist
|
||||
bestDist := d
|
||||
bestPos := pos
|
||||
if bestPos != -1
|
||||
pickedIdx = array.get(candidates, bestPos)
|
||||
array.push(chosen, pickedIdx)
|
||||
array.remove(candidates, bestPos)
|
||||
|
||||
// Build a quick lookup for chosen to manage create/update/delete of lines
|
||||
var bool[] isChosen = array.new_bool()
|
||||
array.clear(isChosen)
|
||||
if sz > 0
|
||||
for _ = 0 to sz - 1
|
||||
array.push(isChosen, false)
|
||||
chosenSz = array.size(chosen)
|
||||
if chosenSz > 0 and sz > 0
|
||||
for j = 0 to chosenSz - 1
|
||||
chIdx = array.get(chosen, j)
|
||||
if chIdx >= 0 and chIdx < sz
|
||||
array.set(isChosen, chIdx, true)
|
||||
|
||||
// Create/Update lines for chosen, delete lines for non-chosen
|
||||
if sz > 0
|
||||
for i = 0 to sz - 1
|
||||
ln = array.get(levelLines, i)
|
||||
price = array.get(levelPrices, i)
|
||||
supTouches = array.get(levelSupportTouches, i)
|
||||
resTouches = array.get(levelResistanceTouches, i)
|
||||
isRes = resTouches >= supTouches
|
||||
col = isRes ? resistanceColor : supportColor
|
||||
if 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)
|
||||
// labels
|
||||
lb = array.get(levelLabels, i)
|
||||
if showPriceLabels
|
||||
touches = array.get(levelTotalTouches, i)
|
||||
lblTxt = (isRes ? "R " : "S ") + str.tostring(price, format.price) + (showTouchesInLbl ? " x" + str.tostring(touches) : "")
|
||||
// Always recreate to honor size/offset changes reliably
|
||||
if not na(lb)
|
||||
label.delete(lb)
|
||||
lb := na
|
||||
desiredSize = f_label_size(labelSizeOpt)
|
||||
lblX = bar_index + labelOffsetBars
|
||||
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
|
||||
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 on current timeframe
|
||||
ph = ta.pivothigh(high, leftBars, rightBars)
|
||||
pl = ta.pivotlow(low, leftBars, rightBars)
|
||||
|
||||
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()
|
||||
|
||||
// Optional: show nearest support/resistance prices
|
||||
var float nearestSupport = na
|
||||
var float nearestResistance = na
|
||||
// Scan chosen candidates quickly by direction
|
||||
float bestBelow = na
|
||||
float bestAbove = na
|
||||
szAll = array.size(levelPrices)
|
||||
if szAll > 0
|
||||
for i = 0 to szAll - 1
|
||||
touches = array.get(levelTotalTouches, i)
|
||||
lastBar = array.get(levelLastTouchBarIndex, i)
|
||||
if touches >= minTouches and bar_index - lastBar <= lookbackBars
|
||||
p = array.get(levelPrices, i)
|
||||
if p <= close
|
||||
bestBelow := na(bestBelow) ? p : math.max(bestBelow, p)
|
||||
if p >= close
|
||||
bestAbove := na(bestAbove) ? p : math.min(bestAbove, p)
|
||||
|
||||
nearestSupport := bestBelow
|
||||
nearestResistance := bestAbove
|
||||
|
||||
plot(nearestSupport, title="Nearest Support", color=color.new(supportColor, 60), linewidth=1, style=plot.style_linebr)
|
||||
plot(nearestResistance, title="Nearest Resistance", color=color.new(resistanceColor, 60), linewidth=1, style=plot.style_linebr)
|
||||
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
//@version=6
|
||||
indicator(title="趋势/震荡行情判定 (BTC 1H)", shorttitle="Trend/Range Regime", overlay=true, max_labels_count=500)
|
||||
|
||||
// ========================= 输入参数 =========================
|
||||
// 基础
|
||||
symbolTitle = input.string(defval="BTCUSD/USDT 1H", title="使用说明 (默认为 1 小时 BTC)", inline="hdr")
|
||||
showEma = input.bool(true, "显示EMA", inline="ema")
|
||||
emaLen = input.int(52, "长度", minval=1, inline="ema")
|
||||
|
||||
// 趋势强度/震荡指标参数
|
||||
adxLen = input.int(14, "ADX长度", minval=2, inline="adx")
|
||||
adxTrendThresh = input.float(22.0, "趋势阈值", minval=5, inline="adx")
|
||||
adxRangeThresh = input.float(18.0, "震荡阈值", minval=5, inline="adx")
|
||||
|
||||
chopLen = input.int(14, "CHOP长度", minval=2, inline="chop")
|
||||
chopTrendMax = input.float(45.0, "趋势上限(越小越趋势)", minval=10, maxval=100, inline="chop")
|
||||
chopRangeMin = input.float(55.0, "震荡下限(越大越震荡)", minval=10, maxval=100, inline="chop")
|
||||
|
||||
// 方向与确认
|
||||
slopePctThresh = input.float(0.05, "EMA斜率阈值(%/bar)", minval=0.0, step=0.01)
|
||||
distancePctThresh = input.float(0.10, "价格偏离EMA阈值(%)", minval=0.0, step=0.01)
|
||||
enterConfirmBars = input.int(2, "进入确认根数", minval=1)
|
||||
exitConfirmBars = input.int(2, "退出确认根数", minval=1)
|
||||
|
||||
// 灵敏度:>1 更灵敏,<1 更稳健
|
||||
sensitivity = input.float(1.2, "灵敏度(>1更灵敏,<1更稳健)", minval=0.5, maxval=2.0, step=0.05)
|
||||
|
||||
// 可视化与告警
|
||||
showBackground = input.bool(true, "背景着色")
|
||||
showLabels = input.bool(true, "标记切换点")
|
||||
|
||||
// 颜色
|
||||
colUp = color.new(color.teal, 80)
|
||||
colDn = color.new(color.red, 80)
|
||||
colRg = color.new(color.gray, 85)
|
||||
|
||||
// ========================= 指标计算 =========================
|
||||
ema = ta.ema(close, emaLen)
|
||||
plot(showEma ? ema : na, color=color.new(color.yellow, 0), linewidth=2, title="EMA")
|
||||
|
||||
// EMA 斜率(百分比/每根)
|
||||
emaSlopePct = ema != 0.0 and ema[1] != 0.0 ? 100.0 * (ema - ema[1]) / ema[1] : 0.0
|
||||
priceDistPct = ema != 0.0 ? 100.0 * (close - ema) / ema : 0.0
|
||||
|
||||
// ADX(手动实现,避免环境不支持 ta.adx)
|
||||
upMove = ta.change(high)
|
||||
downMove = -ta.change(low)
|
||||
plusDM = (upMove > downMove and upMove > 0) ? upMove : 0.0
|
||||
minusDM = (downMove > upMove and downMove > 0) ? downMove : 0.0
|
||||
trAdx = ta.tr(true)
|
||||
plusDI = 100.0 * ta.rma(plusDM, adxLen) / ta.rma(trAdx, adxLen)
|
||||
minusDI = 100.0 * ta.rma(minusDM, adxLen) / ta.rma(trAdx, adxLen)
|
||||
dx = (plusDI + minusDI > 0) ? 100.0 * math.abs(plusDI - minusDI) / (plusDI + minusDI) : 0.0
|
||||
adx = ta.rma(dx, adxLen)
|
||||
|
||||
// CHOP (Choppiness Index)
|
||||
var float log10 = math.log(10.0)
|
||||
tr = ta.tr(true)
|
||||
sumTr = ta.sma(tr, chopLen) * chopLen
|
||||
hh = ta.highest(high, chopLen)
|
||||
ll = ta.lowest(low, chopLen)
|
||||
rangeHL = math.max(hh - ll, 1e-10)
|
||||
chop = 100.0 * (math.log(sumTr / rangeHL) / log10) / (math.log(chopLen) / log10)
|
||||
|
||||
// ========================= 阈值动态调整(按灵敏度) =========================
|
||||
sens = sensitivity
|
||||
adxTrendThreshAdj = adxTrendThresh / sens
|
||||
adxRangeThreshAdj = adxRangeThresh * sens
|
||||
chopTrendMaxAdj = math.min(100.0, chopTrendMax * sens)
|
||||
chopRangeMinAdj = math.max(10.0, chopRangeMin / sens)
|
||||
slopePctThreshAdj = slopePctThresh / sens
|
||||
distancePctThreshAdj = distancePctThresh / sens
|
||||
enterConfirmAdj = math.max(1, int(math.round(enterConfirmBars / sens)))
|
||||
exitConfirmAdj = math.max(1, int(math.round(exitConfirmBars / sens)))
|
||||
|
||||
// 条件
|
||||
isTrendStrength = adx > adxTrendThreshAdj and chop < chopTrendMaxAdj
|
||||
isRangeWeak = adx < adxRangeThreshAdj or chop > chopRangeMinAdj
|
||||
|
||||
dirUpRaw = emaSlopePct > slopePctThreshAdj and priceDistPct > distancePctThreshAdj
|
||||
dirDnRaw = emaSlopePct < -slopePctThreshAdj and priceDistPct < -distancePctThreshAdj
|
||||
|
||||
upReady = isTrendStrength and dirUpRaw
|
||||
dnReady = isTrendStrength and dirDnRaw
|
||||
|
||||
// 连续确认
|
||||
upConsec = ta.barssince(not upReady)
|
||||
dnConsec = ta.barssince(not dnReady)
|
||||
upConfirmed = upConsec >= enterConfirmAdj - 1
|
||||
dnConfirmed = dnConsec >= enterConfirmAdj - 1
|
||||
|
||||
// 退出确认(从趋势转入震荡或反向)
|
||||
exitWeak = isRangeWeak or (not isTrendStrength)
|
||||
exitUpConsec = ta.barssince(not exitWeak) >= exitConfirmAdj - 1
|
||||
exitDnConsec = ta.barssince(not exitWeak) >= exitConfirmAdj - 1
|
||||
|
||||
// ========================= 状态机 =========================
|
||||
// 0: 震荡, 1: 上涨趋势, -1: 下跌趋势
|
||||
var int regime = 0
|
||||
prevRegime = nz(regime[1], 0)
|
||||
|
||||
// 选择方向时的冲突消解:优先单边确认,避免同根双向
|
||||
chooseUp = upConfirmed and not dnConfirmed
|
||||
chooseDn = dnConfirmed and not upConfirmed
|
||||
|
||||
// 状态更新(仅在收盘确认时变更)
|
||||
calcRegime = prevRegime == 0 ? (chooseUp ? 1 : (chooseDn ? -1 : 0)) :
|
||||
prevRegime == 1 ? ((exitWeak and not upReady) ? 0 : ((dnConfirmed and isTrendStrength) ? -1 : 1)) :
|
||||
((exitWeak and not dnReady) ? 0 : ((upConfirmed and isTrendStrength) ? 1 : -1))
|
||||
|
||||
regime := barstate.isconfirmed ? calcRegime : nz(regime[1], prevRegime)
|
||||
|
||||
// ========================= 可视化 =========================
|
||||
bgcolor(showBackground ? (regime == 1 ? colUp : regime == -1 ? colDn : colRg) : na, title="背景")
|
||||
|
||||
// 切换点标记
|
||||
enteredUp = barstate.isconfirmed and regime == 1 and prevRegime != 1
|
||||
enteredDn = barstate.isconfirmed and regime == -1 and prevRegime != -1
|
||||
enteredRg = barstate.isconfirmed and regime == 0 and prevRegime != 0
|
||||
|
||||
// 离开点(结束点)
|
||||
leftUp = barstate.isconfirmed and prevRegime == 1 and regime != 1
|
||||
leftDn = barstate.isconfirmed and prevRegime == -1 and regime != -1
|
||||
leftRg = barstate.isconfirmed and prevRegime == 0 and regime != 0
|
||||
|
||||
plotchar(showLabels and enteredUp, title="↑ Up开始", char="↑", location=location.belowbar, color=color.new(color.teal, 0), size=size.tiny)
|
||||
plotchar(showLabels and leftUp, title="↓ Up结束", char="↓", location=location.abovebar, color=color.new(color.gray, 0), size=size.tiny)
|
||||
plotchar(showLabels and enteredDn, title="↓ Down开始", char="↓", location=location.abovebar, color=color.new(color.red, 0), size=size.tiny)
|
||||
plotchar(showLabels and leftDn, title="↑ Down结束", char="↑", location=location.belowbar, color=color.new(color.gray, 0), size=size.tiny)
|
||||
plotchar(showLabels and enteredRg, title="≈ Range开始",char="≈", location=location.top, color=color.new(color.silver, 0), size=size.tiny)
|
||||
plotchar(showLabels and leftRg, title="≠ Range结束",char="≠", location=location.top, color=color.new(color.silver, 0), size=size.tiny)
|
||||
|
||||
// 辅助输出
|
||||
plotchar(regime == 1, title="UpTrend", char="U", location=location.top, color=color.teal, size=size.tiny)
|
||||
plotchar(regime == -1, title="DownTrend", char="D", location=location.top, color=color.red, size=size.tiny)
|
||||
plotchar(regime == 0, title="Range", char="R", location=location.top, color=color.gray, size=size.tiny)
|
||||
|
||||
// ========================= 告警 =========================
|
||||
alertcondition(enteredUp, title="上涨趋势开始", message="上涨趋势开始 (BTC 1H)")
|
||||
alertcondition(leftUp, title="上涨趋势结束", message="上涨趋势结束 (BTC 1H)")
|
||||
alertcondition(enteredDn, title="下跌趋势开始", message="下跌趋势开始 (BTC 1H)")
|
||||
alertcondition(leftDn, title="下跌趋势结束", message="下跌趋势结束 (BTC 1H)")
|
||||
alertcondition(enteredRg, title="震荡开始", message="震荡开始 (BTC 1H)")
|
||||
alertcondition(leftRg, title="震荡结束", message="震荡结束 (BTC 1H)")
|
||||
|
||||
// ========================= 说明 =========================
|
||||
// 建议用于 1 小时 BTC;参数已做温和默认值以区分趋势/震荡。
|
||||
// 如需更敏感:降低 adxTrendThresh、chopTrendMax,降低 slopePctThresh/confirm;反之亦然。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user