//@version=6 indicator(title="趋势/震荡行情判定 (优化版)", shorttitle="Trend/Range Pro", overlay=true, max_labels_count=500) // ========================= 输入参数 ========================= // 基础 symbolTitle = input.string(defval="BTCUSD/USDT 1H", title="使用说明 (默认为 1 小时)", 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(25.0, "趋势阈值", minval=10, inline="adx") // 提高避免灰色地带 adxRangeThresh = input.float(20.0, "震荡阈值", minval=5, inline="adx") // 缩小灰色地带 chopLen = input.int(14, "CHOP长度", minval=2, inline="chop") chopTrendMax = input.float(38.0, "趋势上限", minval=10, maxval=100, inline="chop") // 更严格 chopRangeMin = input.float(61.38, "震荡下限", 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, maxval=5) exitConfirmBars = input.int(3, "退出确认根数", minval=1, maxval=5) // 退出更慎重 // 成交量确认 useVolume = input.bool(true, "启用成交量确认", inline="vol") volMaLen = input.int(20, "成交量均线长度", minval=5, inline="vol") volMultiplier = input.float(1.2, "成交量倍数", minval=1.0, step=0.1, inline="vol") // 灵敏度:>1 更灵敏,<1 更稳健 sensitivity = input.float(1.0, "灵敏度(>1更灵敏,<1更稳健)", minval=0.5, maxval=2.0, step=0.1) // 高级选项 showMiddleZone = input.bool(true, "显示中间地带(不确定区域)") allowDirectSwitch = input.bool(false, "允许趋势直接切换(不经过震荡)") // 可视化与告警 showBackground = input.bool(true, "背景着色") showLabels = input.bool(true, "标记切换点") showDebugInfo = input.bool(false, "显示调试信息") // 颜色 colUp = color.new(color.teal, 80) colDn = color.new(color.red, 80) colRg = color.new(color.gray, 85) colMiddle = color.new(color.orange, 90) // ========================= 指标计算 ========================= 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 emaSlopePctSmooth = ta.sma(emaSlopePct, 3) // 3周期平滑,减少噪音 priceDistPct = ema != 0.0 ? 100.0 * (close - ema) / ema : 0.0 // 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) // 成交量确认 volMa = ta.sma(volume, volMaLen) volConfirmed = not useVolume or volume > volMa * volMultiplier // ========================= 阈值动态调整(按灵敏度) ========================= 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))) // 退出应该反向调整 // ========================= 条件判断 ========================= // 趋势强度分级 isTrendStrong = adx > adxTrendThreshAdj and chop < chopTrendMaxAdj // 强趋势 isRangeStrong = adx < adxRangeThreshAdj and chop > chopRangeMinAdj // 强震荡 isMiddleZone = not isTrendStrong and not isRangeStrong // 中间地带(不确定区域) // 方向判断 - 使用平滑后的斜率 dirUpRaw = emaSlopePctSmooth > slopePctThreshAdj and priceDistPct > distancePctThreshAdj dirDnRaw = emaSlopePctSmooth < -slopePctThreshAdj and priceDistPct < -distancePctThreshAdj // DI方向确认(增加可靠性) diUpStrong = plusDI > minusDI and plusDI > 20 diDnStrong = minusDI > plusDI and minusDI > 20 // 综合判断(趋势+方向+成交量) upReady = isTrendStrong and dirUpRaw and diUpStrong and volConfirmed dnReady = isTrendStrong and dirDnRaw and diDnStrong and volConfirmed // 连续确认计数 var int upReadyCount = 0 var int dnReadyCount = 0 var int rangeReadyCount = 0 upReadyCount := upReady ? upReadyCount + 1 : 0 dnReadyCount := dnReady ? dnReadyCount + 1 : 0 rangeReadyCount := isRangeStrong ? rangeReadyCount + 1 : 0 upConfirmed = upReadyCount >= enterConfirmAdj dnConfirmed = dnReadyCount >= enterConfirmAdj rangeConfirmed = rangeReadyCount >= enterConfirmAdj // 退出条件(更精细) exitUpWeak = isRangeStrong or (not isTrendStrong and not dirUpRaw) or (minusDI > plusDI * 1.2) // DI反转 exitDnWeak = isRangeStrong or (not isTrendStrong and not dirDnRaw) or (plusDI > minusDI * 1.2) var int exitUpCount = 0 var int exitDnCount = 0 exitUpCount := exitUpWeak ? exitUpCount + 1 : 0 exitDnCount := exitDnWeak ? exitDnCount + 1 : 0 exitUpConfirmed = exitUpCount >= exitConfirmAdj exitDnConfirmed = exitDnCount >= exitConfirmAdj // ========================= 状态机(优化版) ========================= // 0: 震荡, 1: 上涨趋势, -1: 下跌趋势, 2: 不确定区域 var int regime = 0 prevRegime = nz(regime[1], 0) // 状态转换逻辑(更清晰的条件判断) int newRegime = prevRegime // 从震荡状态转出 if prevRegime == 0 if upConfirmed and not dnConfirmed newRegime := 1 // 进入上涨趋势 else if dnConfirmed and not upConfirmed newRegime := -1 // 进入下跌趋势 else if showMiddleZone and isMiddleZone newRegime := 2 // 进入不确定区域 // 从上涨趋势转出 else if prevRegime == 1 if exitUpConfirmed newRegime := 0 // 退出到震荡 else if allowDirectSwitch and dnConfirmed and isTrendStrong newRegime := -1 // 直接切换到下跌(仅在允许时) // 从下跌趋势转出 else if prevRegime == -1 if exitDnConfirmed newRegime := 0 // 退出到震荡 else if allowDirectSwitch and upConfirmed and isTrendStrong newRegime := 1 // 直接切换到上涨(仅在允许时) // 从不确定区域转出 else if prevRegime == 2 if upConfirmed newRegime := 1 else if dnConfirmed newRegime := -1 else if isRangeStrong newRegime := 0 // 仅在K线收盘确认时更新状态 regime := barstate.isconfirmed ? newRegime : nz(regime[1], prevRegime) // ========================= 可视化 ========================= bgcolor(showBackground ? (regime == 1 ? colUp : regime == -1 ? colDn : regime == 2 ? colMiddle : 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 enteredMiddle = barstate.isconfirmed and regime == 2 and prevRegime != 2 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.small) 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.small) 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 enteredMiddle, title="? 不确定", char="?", location=location.top, color=color.new(color.orange, 0), size=size.tiny) // ========================= 调试信息 ========================= if showDebugInfo var table debugTable = table.new(position.top_right, 2, 10, bgcolor=color.new(color.black, 80), border_width=1) if barstate.islast table.cell(debugTable, 0, 0, "指标", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 0, "数值", text_color=color.white, text_size=size.small) table.cell(debugTable, 0, 1, "状态", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 1, regime == 1 ? "上涨" : regime == -1 ? "下跌" : regime == 2 ? "不确定" : "震荡", text_color=regime == 1 ? color.teal : regime == -1 ? color.red : regime == 2 ? color.orange : color.gray, text_size=size.small) table.cell(debugTable, 0, 2, "ADX", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 2, str.tostring(adx, "#.##"), text_color=adx > adxTrendThreshAdj ? color.lime : color.gray, text_size=size.small) table.cell(debugTable, 0, 3, "CHOP", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 3, str.tostring(chop, "#.##"), text_color=chop < chopTrendMaxAdj ? color.lime : chop > chopRangeMinAdj ? color.red : color.gray, text_size=size.small) table.cell(debugTable, 0, 4, "EMA斜率%", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 4, str.tostring(emaSlopePctSmooth, "#.####"), text_color=emaSlopePctSmooth > 0 ? color.lime : color.red, text_size=size.small) table.cell(debugTable, 0, 5, "价格偏离%", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 5, str.tostring(priceDistPct, "#.##"), text_color=priceDistPct > 0 ? color.lime : color.red, text_size=size.small) table.cell(debugTable, 0, 6, "+DI / -DI", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 6, str.tostring(plusDI, "#.#") + " / " + str.tostring(minusDI, "#.#"), text_color=plusDI > minusDI ? color.lime : color.red, text_size=size.small) table.cell(debugTable, 0, 7, "成交量倍数", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 7, str.tostring(volume / volMa, "#.##"), text_color=volConfirmed ? color.lime : color.gray, text_size=size.small) table.cell(debugTable, 0, 8, "确认计数", text_color=color.white, text_size=size.small) table.cell(debugTable, 1, 8, "↑" + str.tostring(upReadyCount) + " ↓" + str.tostring(dnReadyCount), text_color=color.white, text_size=size.small) // ========================= 告警 ========================= alertcondition(enteredUp, title="上涨趋势开始", message="{{ticker}} {{interval}} 上涨趋势开始 价格:{{close}}") alertcondition(leftUp, title="上涨趋势结束", message="{{ticker}} {{interval}} 上涨趋势结束 价格:{{close}}") alertcondition(enteredDn, title="下跌趋势开始", message="{{ticker}} {{interval}} 下跌趋势开始 价格:{{close}}") alertcondition(leftDn, title="下跌趋势结束", message="{{ticker}} {{interval}} 下跌趋势结束 价格:{{close}}") alertcondition(enteredRg, title="震荡开始", message="{{ticker}} {{interval}} 进入震荡 价格:{{close}}") alertcondition(enteredMiddle, title="不确定区域", message="{{ticker}} {{interval}} 进入不确定区域 价格:{{close}}") // ========================= 说明 ========================= // 优化版改进: // 1. 修复退出确认逻辑BUG // 2. 重构状态机,逻辑更清晰 // 3. 添加成交量确认 // 4. 添加DI方向确认 // 5. EMA斜率平滑处理 // 6. 缩小ADX/CHOP灰色地带 // 7. 添加不确定区域状态 // 8. 添加调试信息表格 // 9. 优化退出确认的灵敏度调整 // 10. 更智能的状态转换逻辑