ADX/DMI 怎么判断「现在有没有趋势」?
当成其他策略的开关使用
你的突破策略在爆走行情里三天赚了 6%,接着市场走平两周,同一组进场条件把那 6% 又还回去,代码没改过一行。你已经在怀疑,是不是有办法让策略在盘整期间直接不进场。
ADX(Average Directional Index)本来就是为了这件事被造出来的——它是 Wilder 1978 年在《New Concepts in Technical Trading Systems》里跟 RSI 一起提出的一组指标。跟 RSI、MACD 不一样,它不是拿来当进场信号,它是拿来回答一个更前面的问题:现在这套策略该不该跑。
ADX 在算什么——先看机制
ADX 这条线本身没什么直观意义。要看懂它,得先看它下面那两条——`+DI` 跟 `−DI`。这三条合起来叫 DMI(Directional Movement Index),Wilder 的原设计是三条一起看,只看 ADX 反而漏掉了方向信息。
机制大略是这样。每根 K 线跟上一根比,如果今天高点比昨天高、而且高过的幅度比低点低过的幅度多,就算一个 `+DM`(正向动能);反过来就算 `−DM`。把过去 14 根 K 线的 `+DM` 加起来、除以同期的 True Range 做 smoothing,得到 `+DI`;`−DM` 同样处理得到 `−DI`。ADX 是 `+DI` 跟 `−DI` 差距的绝对值再 smoothing 一次。
结论是:ADX 只告诉你「趋势的强度」,不告诉你方向。方向要看 `+DI` 跟 `−DI` 谁在上面。三条线各自代表的东西看下面这张。
| 线 | 回答的问题 | 数值范围 |
|---|---|---|
+DI | 多方力道有多强 | 0–100,通常 10–40 |
−DI | 空方力道有多强 | 0–100,通常 10–40 |
ADX | 多空之中优势方的强度(不含方向) | 0–100,通常 10–60 |
来源:Pine Script v6 语言参考手册的 ta.dmi 说明;Wilder 1978《New Concepts in Technical Trading Systems》原始定义(查证日期 2026 年 8 月)。
默认周期 14 是 Wilder 自己的原始参数,之后被大部分平台沿用。TradingView 的 ta.dmi() 就是这样:ta.dmi(14, 14),第一个 14 是 DI 的长度、第二个 14 是 ADX 的 smoothing 长度。这两个参数可以分开调,但没有先实测就不要动——大多数讨论 ADX 阈值的资料都是默认 14 为前提写的,动了之后那些阈值就不再对得起来。
为什么 ADX 不能单独拿来进场
只看 ADX 这条线很容易得出「ADX > 25 就是有趋势,我进场」这种规则,然后在实盘上被打回来。原因就在上面那张表的第三栏——ADX 没有方向。ADX 从 15 爬到 30,可能是价格在往上走的趋势在强化,也可能是在往下崩的趋势在强化,只看那条线分不出来。
第二个问题是延迟。ADX 是两层 smoothing 叠出来的(先 smooth DM/TR、再 smooth 差距的绝对值),等它爬到 25 以上、你判断「有趋势」的时候,价格已经走了一段。用它当进场信号,你永远是在趋势的中后段才上车。
ta.dmi(14, 14) 叠到任何一段明显的趋势上都会看到这个延迟,但这是「机制推论 + 图表观察」,不是官方白纸黑字的一句话。所以拿 ADX 进场的做法——不管是「ADX 突破 20」还是「+DI 上穿 −DI」——你都得先接受两件事:进的是趋势的中后段,而且会被骗线常态性地扫出去。这条路不是没有人走通,但走通的人几乎都在 ADX 之外还叠了别的过滤条件(时间框架、成交量、突破的 wick)。ADX 单独扛不起进场这件事。
那 ADX 到底该多少算「有趋势」
Wilder 原文其实只给两个阈值:ADX 低于 20 没有明确趋势、高于 25 有明确趋势——StockCharts 的 ChartSchool 就是这样引 Wilder。常见教材再往上补几段更细的分级(25–50 明确趋势、50 以上极强趋势)方便对应直觉,但那是后来的通用写法,Wilder 原书没有把 50 这个数字写进去。这两个原始阈值被沿用了将近 50 年,大部分教科书讲「有没有趋势」时用的就是这一组。
但这组数字是他在 1970 年代的商品与外汇市场上观察出来的。日内加密永续是另一个世界:波动大、假突破多、24 小时交易没有收盘缺口,同样的 14 周期在 5 分钟图上,ADX 几乎不会低于 20。硬套 25 的阈值等于整天都在「有趋势」,开关等于没开。
ta.dmi(14, 14) 叠到不同时间框架、看过去 90 天 ADX 分布拉出来的参考数字(查证日期 2026 年 8 月)。实际做开关前建议你自己去图表上叠一次——不同标的、不同期间会漂移,尤其是牛熊转换时整体数值会整段上移或下移。| 时间框架 | ADX 大致中位数 | 当开关的参考阈值 |
|---|---|---|
| 5m | 22–28 | 看 90 天分布的 70% 分位数 |
| 1H | 18–24 | 20–22 |
| 4H | 16–22 | 20–25(比较接近 Wilder 原文) |
| 1D | 15–20 | 25(接近 Wilder 原文) |
以 BTCUSDT.P 过去 90 天为例的观察数值(查证日期 2026 年 8 月)。这是快照不是保证,实际套用前自己在图表上验一次。
比起硬记数字,更耐用的做法是用分位数而不是绝对值——ADX 大于「过去 200 根 K 线 ADX 分布的 70% 分位数」时算有趋势。这样一来,同一份 Pine Script 可以跨时间框架跟不同标的用,不用每换一个对就手动改阈值。
把它接上策略:两种叠法
ADX 当开关的核心逻辑只有一种,方向两种:趋势类策略在 ADX 高的时候开、在 ADX 低的时候关;均值回归类策略反过来。这件事的直觉很简单,但写进策略时要对的字段不少。
| 趋势类(突破、动能) | 均值回归类(布林通道回归) | ||
|---|---|---|---|
| ADX 开关条件 | ADX > 阈值时进场 | ADX < 阈值时进场 | |
| 在盘整期表现 | 被锯来锯去、胜率掉 | 本来就是它的主场 | |
| 在爆走期表现 | 本来就是它的主场 | 会被辗——回归假设失效 | |
| 加上 ADX 开关后的效果 | 盘整期少进场、少亏 | 爆走期少进场、少亏 | |
| 开关关掉时要不要平仓现有仓位 | 看 stop,不强制平 | 强烈建议平——回归假设没了 |
最后那一列是最容易漏的:「开关关掉」跟「平仓」不是同一件事。开关关掉只挡住新单,已经在场内的仓位怎么处理是另一个决策。趋势策略靠自己的 stop 出场没问题;均值回归策略如果进场后 ADX 从 15 飙到 35,代表你当初赌的「会回来」的前提已经被打破,这时候比较安全的做法是主动平掉,不要等 stop。
用 Pine Script 把这个开关写出来
下面是最小可用的骨架。这段本身不能直接拿去回测,因为进场逻辑留白了——重点是那个 regimeOn 变量怎么算、怎么跟你的进场条件相接。你把 longSig 跟 shortSig 换成自己的逻辑就是一个完整策略。
//@version=6
strategy("Trend Strategy with ADX Regime Filter",
overlay = true,
default_qty_type = strategy.percent_of_equity,
default_qty_value= 10,
initial_capital = 10000)
// ─── ADX 参数 ───
diLen = input.int(14, "DI Length")
adxLen = input.int(14, "ADX Smoothing")
useAdaptive = input.bool(true,"用分位数而不是固定阈值")
fixedThr = input.float(20, "固定阈值(useAdaptive=false 时用)")
pctLookback = input.int(200, "分位数回看根数")
pctRank = input.float(70, "分位数(%)")
// ─── DMI + 开关 ───
[diPlus, diMinus, adx] = ta.dmi(diLen, adxLen)
adaptiveThr = ta.percentile_linear_interpolation(adx, pctLookback, pctRank)
threshold = useAdaptive ? adaptiveThr : fixedThr
regimeOn = adx > threshold三个地方值得展开一下。ta.dmi() 一次回传三个值——`+DI`、`−DI`、ADX——用数组解构接。ta.percentile_linear_interpolation(adx, 200, 70) 是 Pine v5 之后才有的内建函数,直接算「过去 200 根 K 线 ADX 分布的 70% 分位数」,不用自己排序。useAdaptive 那个开关留着是为了让你能拿分位数版跟固定阈值版做同一段回测的 A/B——不然争论哪种好比较没有意义。
上面只算了开关本身。下面示范怎么把它接到你自己的进场条件——这里用最基础的 Donchian 突破当范例,你把 longSig 跟 shortSig 换成自己的就是一个完整策略。
// 承上,接续同一份 strategy() 底下
// ─── 进场条件(换成你自己的) ───
lookback = 20
hh = ta.highest(high, lookback)[1]
ll = ta.lowest(low, lookback)[1]
longSig = close > hh
shortSig = close < ll
// ─── 只有开关开启才进场 ───
if regimeOn and longSig
strategy.entry("Long", strategy.long)
if regimeOn and shortSig
strategy.entry("Short", strategy.short)
// 图表视觉化:开关开启时背景绿色
bgcolor(regimeOn ? color.new(color.green, 90) : na, title = "Regime On")`[1]` 那个位移是刻意的——`ta.highest(high, 20)[1]` 是「不包含这根 K 线」的过去 20 根最高点,避免用未来数据算进场条件(`repainting` 的其中一种)。这是Pine Script 自动化陷阱里最经典的一种。
用 ADX 当开关最常见的两个错误
这两个错误几乎是新做滤网的人一定踩的,都不会报错,也都不会在回测数字上明显露馅。
request.security——它的 lookahead 从 v3 起默认就是 barmerge.lookahead_off(安全值),只要你没有显式传 barmerge.lookahead_on,回测就不会偷用未来数据。percentile_linear_interpolation)而不是固定阈值可以缓解,但不能根治——最好的做法是先做 walk-forward,不要一次跑全部历史。第一个错误——时间框架错配——写错不会编译不过,只会回测跟实盘对不上。第二个错误更阴险,它会在回测期间「安静地变好」:你在 2022 年到 2024 年的数据上把阈值调到 22,看起来很棒;但 2022 年整年是明显的下行趋势,那组阈值本质上是为那段行情调的,换到 2025 年横盘三个月的时候就完全失效。
诚实的一段:ADX 过滤不了的东西
ADX 这个工具再怎么用,也有几件事它结构上就做不到。这里先列出来,免得你花时间去试——我们自己也试过,答案是不行。
- 短促的 news spike。Wilder 的 ADX 有两层 smoothing,反应速度天生就慢。CPI 公布后 3 根 K 线内的爆走,等 ADX 亮起来已经回吐一半了。
- 低波动的缓慢趋势。价格慢慢涨、但每根 K 线振幅都很小,True Range 小、DM 也小,ADX 可以整段爬升期间都在 15 以下。这种行情 ADX 开关会全程关着,你会错过它。
- 「假趋势」的头尾。ADX 从 15 爬到 22 的过程你分不出是真趋势在启动、还是假突破在制造动能。要靠 ADX 确认就得等 25 以上,那时候通常已经接近趋势中段。
- 「这是涨的趋势还是跌的趋势」。ADX 没有方向。要方向就得看 `+DI` 跟 `−DI` 谁在上,或者搭 均线 之类的方向判断。
任何滤网——不只 ADX——都是在「少进亏单」跟「错过真信号」之间做选择,你把滤网收紧就一定会漏。ADX 滤网收得越紧,漏的越明显的是那种「安静的多头趋势」,价格默默上去但 ADX 从来没破 20 过。这种型态在牛市末段特别多,是这个工具结构上的盲点。
常见问题
ADX 的周期 14 可以改吗?
ADX 跟 RSI、MACD 可以叠在一起用吗?
TradingView Alert 要不要把 ADX 的当前值传出去?
regimeOn 为 false 时 Pine Script 根本不会触发 strategy.entry,也就不会发出 alert。传出去反而增加 payload 大小跟解析工作量。真的要 debug 有没有触发,用 plotshape 在图表上标开关转态的时刻比较直接。回测绩效加了 ADX 开关明显变好,可以直接上实盘吗?
把 ADX 开关关掉的那些期间,我的资金应该做什么?
Get started
把加了 ADX 开关的 Pine 策略接上 webhook,交给 TVSBot——用你自己的交易所 API key、先 dry-run 验开关真的有关掉、风控自己设。
免费开始使用