
SUPERTREND, ATR, ADX, EMA, AI
传统SuperTrend策略的最大痛点?固定参数在不同市场环境下表现差异巨大。这个AI增强版本通过动态调整ATR倍数,在高波动期间将倍数提升至基础值的2倍,低波动期间降至0.85倍。回测数据显示,这种自适应机制能够显著减少震荡市场中的假信号。
核心创新在于三层过滤系统:市场状态识别、AI信号评分、多重确认机制。不再是简单的价格突破SuperTrend线就入场,而是要求AI评分达到65分以上才触发交易信号。这个评分系统综合考虑成交量激增、价格偏离程度、趋势一致性等5个维度。
评分机制设计精妙:成交量激增占20分权重,价格偏离SuperTrend线距离占25分,EMA趋势一致性占20分,市场状态质量占15分,前期价格与趋势线距离占20分。总分100分,默认65分门槛意味着只有高质量信号才能通过筛选。
具体来说,当成交量超过20周期均值2.5倍时获得满分20分,价格偏离SuperTrend线1.5倍ATR以上获得25分满分。这种量化评分避免了主观判断,每个信号都有明确的数据支撑。实际使用中,建议根据不同品种的特性调整最低评分要求。
策略通过ATR比率和ADX指标识别三种市场状态:趋势期(regime=1)、高波动期(regime=2)、震荡期(regime=0)。当ATR比率超过1.4时判定为高波动期,ADX低于20且ATR比率低于0.9时为震荡期。
自适应倍数调整逻辑:高波动期倍数增加40%×(ATR比率-1.0),震荡期倍数降至基础值的85%。这意味着3.0的基础倍数在极端波动时可能调整至4.2,震荡期降至2.55。这种动态调整机制显著提升了策略在不同市场环境下的适应性。
ATR动态止损是首选方案,默认2.5倍ATR距离既能容忍正常波动又能及时止损。百分比止损适合波动率相对稳定的品种,SuperTrend模式则在趋势反转时立即平仓。
止盈设置支持风险收益比模式,默认2.5:1的风险收益比在统计上具有优势。启用追踪止损后,盈利头寸的止损线会根据2.5倍ATR距离动态调整,最大化趋势行情中的收益。
EMA趋势过滤器确保只在50周期EMA方向一致时入场,避免逆势交易。震荡期过滤直接跳过regime=0的信号,虽然可能错过部分机会但显著降低了假信号率。
成交量过滤要求入场时成交量高于20周期均值,确保有足够的市场参与度支撑价格突破。10个周期的冷却期防止频繁交易,降低交易成本。
对于加密货币建议将最低AI评分提升至70分,传统股票可降至60分。高频交易者可将冷却期缩短至5个周期,长线投资者建议延长至15个周期。
ATR长度参数10是经过优化的平衡点,过短会过度敏感,过长会滞后。基础倍数3.0适合大多数品种,高波动品种可调至3.5,低波动品种降至2.5。
重要风险提示:历史回测结果不代表未来收益表现。策略在极端市场条件下可能出现连续亏损,建议严格控制单笔仓位不超过总资金的30%。不同市场环境下策略表现存在显著差异,需要持续监控和调整参数。
/*backtest
start: 2026-01-01 00:00:00
end: 2026-02-24 00:00:00
period: 1h
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"PAXG_USDT","balance":500000}]
*/
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © DefinedEdge
//@version=6
strategy("SuperTrend AI Strategy [Adaptive]", "ST AI Strategy ◈",
overlay = true,
initial_capital = 10000,
default_qty_type = strategy.percent_of_equity,
default_qty_value= 30,
commission_type = strategy.commission.percent,
commission_value = 0.06,
slippage = 2,
pyramiding = 0,
calc_on_every_tick = false,
max_labels_count = 500)
// ============================================================================
// INPUTS
// ============================================================================
// --- SuperTrend Core ---
GRP_ST = "◈ SuperTrend"
i_atLen = input.int(10, "ATR Length", minval=1, maxval=50, group=GRP_ST)
i_bMult = input.float(3.0, "Base Multiplier", minval=0.5, maxval=10.0, step=0.1, group=GRP_ST)
i_src = input.source(hl2, "Source", group=GRP_ST)
// --- Regime Detection ---
GRP_REG = "◈ Regime Detection"
i_regLen = input.int(40, "Regime Lookback", minval=10, maxval=100, group=GRP_REG)
i_adxLen = input.int(14, "ADX Length", minval=5, maxval=50, group=GRP_REG)
i_adxThr = input.float(20, "ADX Trend Threshold", minval=10, maxval=40, step=1, group=GRP_REG)
i_adapt = input.bool(true, "Adaptive Multiplier", group=GRP_REG)
// --- AI Scoring ---
GRP_AI = "◈ AI Engine"
i_trendLen = input.int(50, "Trend EMA Length", minval=10, maxval=200, group=GRP_AI)
i_volLen = input.int(20, "Volume MA Length", minval=5, maxval=50, group=GRP_AI)
i_minSc = input.int(65, "Min Signal Score", minval=0, maxval=90, group=GRP_AI, tooltip="Only enter trades when the AI score meets this threshold.")
// --- Risk Management ---
GRP_RISK = "◈ Risk Management"
i_slMode = input.string("ATR", "Stop Loss Mode", options=["ATR", "Percent", "SuperTrend"], group=GRP_RISK, tooltip="ATR: dynamic SL based on volatility. Percent: fixed %. SuperTrend: exit on trend flip.")
i_slAtr = input.float(2.5, "SL ATR Multiplier", minval=0.5, maxval=8.0, step=0.1, group=GRP_RISK)
i_slPct = input.float(3.0, "SL Percent", minval=0.5, maxval=10.0, step=0.1, group=GRP_RISK)
i_tpMode = input.string("RR", "Take Profit Mode", options=["RR", "Percent", "None"], group=GRP_RISK, tooltip="RR: risk/reward ratio based on SL distance. Percent: fixed %. None: hold until SL or flip.")
i_tpRR = input.float(2.5, "TP Risk:Reward", minval=0.5, maxval=10.0, step=0.1, group=GRP_RISK)
i_tpPct = input.float(6.0, "TP Percent", minval=0.5, maxval=20.0, step=0.1, group=GRP_RISK)
i_trail = input.bool(true, "Trailing Stop", group=GRP_RISK, tooltip="When enabled, SL trails in the direction of the trade after entry.")
i_trailAtr = input.float(2.5, "Trail ATR Mult", minval=0.5, maxval=8.0, step=0.1, group=GRP_RISK)
// --- Filters ---
GRP_FLT = "◈ Trade Filters"
i_trendF = input.bool(true, "EMA Trend Filter", group=GRP_FLT, tooltip="Only take longs above EMA, shorts below EMA.")
i_regF = input.bool(true, "Skip Ranging", group=GRP_FLT, tooltip="Skip entries during ranging regime. Reduces whipsaws.")
i_volF = input.bool(true, "Volume Filter", group=GRP_FLT, tooltip="Only enter if volume is above average.")
i_sigCD = input.int(10, "Cooldown (bars)", minval=0, maxval=50, group=GRP_FLT)
// --- Backtest Window ---
GRP_BT = "◈ Backtest"
bool inWindow = true
// --- Visuals ---
GRP_VIS = "◈ Visuals"
i_sGlow = input.bool(true, "Band Glow Effect", group=GRP_VIS)
i_sRegBg = input.bool(true, "Regime Background", group=GRP_VIS)
// --- Colors ---
GRP_COL = "◈ Colors"
i_cBull = input.color(color.new(#089981, 0), "Bull", inline="c1", group=GRP_COL)
i_cBear = input.color(color.new(#f23645, 0), "Bear", inline="c1", group=GRP_COL)
// ============================================================================
// CORE CALCULATIONS
// ============================================================================
int n = bar_index
float atr = ta.atr(i_atLen)
float safeAtr = nz(atr, 0.001)
// ── Regime Detection ──
float atrMa = ta.sma(atr, i_regLen)
float atrRatio = atrMa > 0 ? atr / atrMa : 1.0
// ADX
float upMove = high - high[1]
float dnMove = low[1] - low
float plusDM = upMove > dnMove and upMove > 0 ? upMove : 0
float minusDM = dnMove > upMove and dnMove > 0 ? dnMove : 0
float trueR = ta.tr
float smoothTR = ta.rma(trueR, i_adxLen)
float smoothPD = ta.rma(plusDM, i_adxLen)
float smoothND = ta.rma(minusDM, i_adxLen)
float plusDI = smoothTR > 0 ? 100 * smoothPD / smoothTR : 0
float minusDI = smoothTR > 0 ? 100 * smoothND / smoothTR : 0
float diSum = plusDI + minusDI
float dx = diSum > 0 ? 100 * math.abs(plusDI - minusDI) / diSum : 0
float adx = ta.rma(dx, i_adxLen)
var int regime = 1
if atrRatio > 1.4
regime := 2
else if adx < i_adxThr and atrRatio < 0.9
regime := 0
else
regime := 1
// ── Adaptive Multiplier ──
float adaptMult = i_bMult
if i_adapt
if regime == 2
adaptMult := i_bMult * (1.0 + (atrRatio - 1.0) * 0.4)
else if regime == 0
adaptMult := i_bMult * 0.85
adaptMult := math.max(math.min(adaptMult, i_bMult * 2.0), i_bMult * 0.5)
// ── SuperTrend ──
var float stBand = na
var int stDir = 1
float upperBase = i_src + adaptMult * atr
float lowerBase = i_src - adaptMult * atr
float prevBand = nz(stBand[1], stDir == 1 ? lowerBase : upperBase)
if stDir == 1
stBand := math.max(lowerBase, prevBand)
if close < stBand
stDir := -1
stBand := upperBase
else
stBand := math.min(upperBase, prevBand)
if close > stBand
stDir := 1
stBand := lowerBase
bool trendFlip = stDir != stDir[1]
// ── Trend EMA ──
float trendMa = ta.ema(close, i_trendLen)
bool trendUp = close > trendMa
bool trendDn = close < trendMa
// ── Volume ──
float volMa = ta.sma(volume, i_volLen)
// ============================================================================
// AI SIGNAL SCORING
// ============================================================================
scoreSignal(bool isBull) =>
float score = 0
// Factor 1: Volume surge (0-20)
float vRat = volMa > 0 ? volume / volMa : 1.0
score += vRat >= 2.5 ? 20 : vRat >= 1.5 ? 14 : vRat >= 1.0 ? 8 : 3
// Factor 2: Displacement beyond band (0-25)
float disp = isBull ? (close - stBand) : (stBand - close)
float dispAtr = safeAtr > 0 ? disp / safeAtr : 0
score += dispAtr >= 1.5 ? 25 : dispAtr >= 0.8 ? 18 : dispAtr >= 0.3 ? 12 : dispAtr > 0 ? 5 : 0
// Factor 3: EMA trend alignment (0-20)
bool aligned = (isBull and trendUp) or (not isBull and trendDn)
float emaDist = math.abs(close - trendMa) / safeAtr
score += aligned and emaDist > 0.5 ? 20 : aligned ? 14 : emaDist < 0.3 ? 8 : 2
// Factor 4: Regime quality (0-15)
score += regime == 1 ? 15 : regime == 2 ? 8 : 3
// Factor 5: Band distance before flip (0-20)
float prevDist = not na(stBand[1]) ? math.abs(close[1] - stBand[1]) / safeAtr : 0
score += prevDist >= 2.0 ? 20 : prevDist >= 1.0 ? 14 : prevDist >= 0.5 ? 8 : 3
int(math.min(math.round(score), 100))
// ============================================================================
// TRADE LOGIC
// ============================================================================
var int lastEntryBar = 0
var float entryPrice = 0.0
var float slPrice = 0.0
var float tpPrice = 0.0
var int lastSigScore = 0
var int lastSigDir = 0
var bool lastSigBright = false
bool longEntry = false
bool shortEntry = false
int sigScore = 0
// Cooldown check
bool cdOk = (n - lastEntryBar) > i_sigCD
if trendFlip and inWindow and cdOk and barstate.isconfirmed
if stDir == 1
sigScore := scoreSignal(true)
// Apply filters
bool passScore = sigScore >= i_minSc
bool passTrend = not i_trendF or trendUp
bool passReg = not i_regF or regime != 0
bool passVol = not i_volF or (volume > volMa)
if passScore and passTrend and passReg and passVol
longEntry := true
else
sigScore := scoreSignal(false)
bool passScore = sigScore >= i_minSc
bool passTrend = not i_trendF or trendDn
bool passReg = not i_regF or regime != 0
bool passVol = not i_volF or (volume > volMa)
if passScore and passTrend and passReg and passVol
shortEntry := true
// ── Calculate SL/TP Levels ──
float slDist = 0.0
if i_slMode == "ATR"
slDist := atr * i_slAtr
else if i_slMode == "Percent"
slDist := close * i_slPct / 100
else // SuperTrend mode
slDist := math.abs(close - stBand)
float tpDist = 0.0
if i_tpMode == "RR"
tpDist := slDist * i_tpRR
else if i_tpMode == "Percent"
tpDist := close * i_tpPct / 100
// ── Execute Entries ──
if longEntry
// Close any existing short
if strategy.position_size < 0
strategy.close("Short")
entryPrice := close
slPrice := close - slDist
tpPrice := i_tpMode != "None" ? close + tpDist : na
strategy.entry("Long", strategy.long)
if not na(slPrice) and i_tpMode != "None" and not na(tpPrice)
strategy.exit("Long Exit", "Long", stop=slPrice, limit=tpPrice,
trail_points = i_trail ? slDist / syminfo.mintick : na,
trail_offset = i_trail ? (atr * i_trailAtr) / syminfo.mintick : na)
else if not na(slPrice)
strategy.exit("Long SL", "Long", stop=slPrice,
trail_points = i_trail ? slDist / syminfo.mintick : na,
trail_offset = i_trail ? (atr * i_trailAtr) / syminfo.mintick : na)
lastEntryBar := n
lastSigScore := sigScore
lastSigDir := 1
lastSigBright := sigScore >= 70
if shortEntry
// Close any existing long
if strategy.position_size > 0
strategy.close("Long")
entryPrice := close
slPrice := close + slDist
tpPrice := i_tpMode != "None" ? close - tpDist : na
strategy.entry("Short", strategy.short)
if not na(slPrice) and i_tpMode != "None" and not na(tpPrice)
strategy.exit("Short Exit", "Short", stop=slPrice, limit=tpPrice,
trail_points = i_trail ? slDist / syminfo.mintick : na,
trail_offset = i_trail ? (atr * i_trailAtr) / syminfo.mintick : na)
else if not na(slPrice)
strategy.exit("Short SL", "Short", stop=slPrice,
trail_points = i_trail ? slDist / syminfo.mintick : na,
trail_offset = i_trail ? (atr * i_trailAtr) / syminfo.mintick : na)
lastEntryBar := n
lastSigScore := sigScore
lastSigDir := -1
lastSigBright := sigScore >= 70
// ── SuperTrend Flip Exit (if SL mode is SuperTrend) ──
if i_slMode == "SuperTrend" and trendFlip
if strategy.position_size > 0 and stDir == -1
strategy.close("Long", comment="ST Flip")
if strategy.position_size < 0 and stDir == 1
strategy.close("Short", comment="ST Flip")
// ============================================================================
// VISUALS
// ============================================================================
// ── Band Color ──
color bandCore = stDir == 1 ? i_cBull : i_cBear
color bandColor = regime == 2 ? color.new(#ffab00, 0) : regime == 0 ? color.new(#78909c, 20) : bandCore
// ── Glow ──
plot(i_sGlow ? stBand : na, "Glow Outer", color=color.new(bandColor, 85), linewidth=6, style=plot.style_linebr)
plot(i_sGlow ? stBand : na, "Glow Mid", color=color.new(bandColor, 70), linewidth=4, style=plot.style_linebr)
// ── Band ──
plot(regime != 0 ? stBand : na, "Band (Solid)", color=bandColor, linewidth=2, style=plot.style_linebr)
plot(regime == 0 ? stBand : na, "Band (Ranging)", color=bandColor, linewidth=2, style=plot.style_linebr)
// ── Flip Dot ──
plot(trendFlip ? stBand : na, "Flip Dot", color=bandCore, style=plot.style_circles, linewidth=5, join=false)
// ── Regime Background ──
bgcolor(i_sRegBg and regime == 2 ? color.new(#ffab00, 95) : na, title="Volatile BG")
bgcolor(i_sRegBg and regime == 0 ? color.new(#78909c, 96) : na, title="Ranging BG")
// ── Trend EMA ──
plot(trendMa, "Trend EMA", color=color.new(trendUp ? i_cBull : i_cBear, 65), linewidth=1)
// ============================================================================
// HIDDEN PLOTS
// ============================================================================
plot(sigScore > 0 ? sigScore : na, "Signal Score", display=display.none)
plot(adaptMult, "Adaptive Mult", display=display.none)
plot(adx, "ADX", display=display.none)
plot(float(regime), "Regime", display=display.none)
plot(float(stDir), "Trend Direction", display=display.none)