一句话回答: ADX(平均趋向指数)度量的是"趋势有多强"而不是方向:ADX 越高趋势越明确,越低越像震荡。用 AlphaFeed 取复权日线,用 Wilder 口径算出 +DI、-DI、ADX,就能用「ADX≥25 视为强趋势、+DI/-DI 谁大定多空」几十行判断当前是趋势市还是震荡市,让均线/突破类策略只在有趋势时出手。
为什么会有这个问题 / 传统做法的痛点
均线金叉、突破这类顺势策略在震荡市会被反复打脸(假突破、来回止损)。问题的根源是"没先判断有没有趋势"。ADX 正是为此设计:
- DI(方向指标):
+DI强表示上涨动能强,-DI强表示下跌动能强; - ADX:由
+DI/-DI的背离程度平滑而来,只看强弱不看方向。
难点是 ADX 计算链条较长(TR→ATR→DM→DI→DX→ADX)且要用 Wilder 平滑,手写容易错。数据则需要干净的复权 OHLC——正是 AlphaFeed 的强项。
分步骤解决(每步配可运行代码)
第 1 步:取复权日线
from alphafeed import AlphaFeed
import numpy as np
import pandas as pd
af = AlphaFeed(api_key="your-api-key") # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()
df = af.klines.get("000001.SZ", period="1d", count=250,
adjust="forward", to_dataframe=True)
df = df.sort_values("trade_date").reset_index(drop=True)
第 2 步:Wilder 口径计算 +DI / -DI / ADX
def adx(df, n=14):
high, low, close = df["high"], df["low"], df["close"]
up = high.diff()
down = -low.diff()
plus_dm = np.where((up > down) & (up > 0), up, 0.0) # 上升方向动量
minus_dm = np.where((down > up) & (down > 0), down, 0.0) # 下降方向动量
tr = pd.concat([(high - low),
(high - close.shift()).abs(),
(low - close.shift()).abs()], axis=1).max(axis=1)
atr = tr.ewm(alpha=1 / n, adjust=False).mean() # Wilder ATR
plus_di = 100 * pd.Series(plus_dm, index=df.index).ewm(alpha=1/n, adjust=False).mean() / atr
minus_di = 100 * pd.Series(minus_dm, index=df.index).ewm(alpha=1/n, adjust=False).mean() / atr
dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di).replace(0.0, np.nan)
adx_ = dx.ewm(alpha=1 / n, adjust=False).mean()
out = df.copy()
out["plus_di"], out["minus_di"], out["adx"] = plus_di, minus_di, adx_
return out
ADX 计算链条与含义:
| 步骤 | 公式要点 | 含义 |
|---|---|---|
| TR | max(高-低, | 高-昨收 |
| ATR | TR 的 Wilder 平滑 | 波动基准 |
| +DM/-DM | 方向性动量 | 上/下方向强度 |
| +DI/-DI | 100×DM 平滑/ATR | 多空方向指标 |
| ADX | |+DI−−DI|/(+DI+−DI) 的平滑 | 趋势强度(无方向) |
第 3 步:判断趋势 / 震荡与多空
def adx_state(symbol, count=250, n=14):
df = af.klines.get(symbol, period="1d", count=count,
adjust="forward", to_dataframe=True).sort_values("trade_date").reset_index(drop=True)
last = adx(df, n).iloc[-1]
a = float(last["adx"])
state = "强趋势" if a >= 25 else ("弱趋势/震荡" if a >= 20 else "震荡")
direction = "多头" if last["plus_di"] > last["minus_di"] else "空头"
return {"adx": round(a, 1),
"plus_di": round(float(last["plus_di"]), 1),
"minus_di": round(float(last["minus_di"]), 1),
"state": state, "direction": direction}
print(adx_state("000001.SZ"))
# 例如:{'adx': 35.7, 'plus_di': 24.9, 'minus_di': 11.7, 'state': '强趋势', 'direction': '多头'}
print(adx_state("600519.SH"))
# 例如:{'adx': 14.2, 'plus_di': 20.6, 'minus_di': 26.4, 'state': '震荡', 'direction': '空头'}
上例实测:平安银行 ADX≈35.7 且 +DI>-DI → 强趋势·多头;茅台 ADX≈14.2 → 震荡(此时不宜用顺势突破策略)。这正是 ADX 的用法:先过滤市场状态,再决定用哪类策略。
关键坑与注意事项
- ADX 不看方向:ADX 高只代表"趋势强",涨跌都可能。方向要看
+DIvs-DI。 - 滞后性:ADX 经过两层 Wilder 平滑,反应偏慢,是"确认"而非"预测"工具,别指望它抓拐点。
- 阈值是经验值:常用 25 为强趋势线、20 以下为震荡,但不同标的/周期差异大,最好回测校准,勿过拟合。
- Wilder 口径:DI/ADX 必须用 Wilder 平滑(
ewm(alpha=1/n, adjust=False)),用简单均值会和软件对不上。 - 复权:
adjust="forward",避免除权跳空污染 TR/DM。
常见问题(FAQ)
Q:ADX 多少算有趋势?
A:经验上 ADX≥25 视为趋势明确、<20 视为震荡,20~25 为过渡。这是习惯阈值,建议用你的标的回测校准。
Q:ADX 能告诉我涨还是跌吗?
A:不能。ADX 只测强弱,方向看 +DI 与 -DI 谁大:+DI>-DI 偏多,反之偏空。
Q:ADX 怎么和其他策略配合?
A:常见做法是"ADX 做过滤器"——只在 ADX≥25 时启用均线/突破等顺势策略,在震荡时改用均值回归或空仓,减少假突破损失。
Q:需要付费吗?
A:单只标的的日线在免费额度内即可试算;批量/全市场需 Starter 及以上,详见 AlphaFeed 定价页。
小结
ADX 的价值在于"先判断有没有趋势,再决定用什么策略":ADX 测强弱、DI 定方向。计算链条长且要用 Wilder 平滑,容易写错,数据也要干净复权。AlphaFeed 提供口径一致的复权日线,让你几十行就能给策略加一个"市场状态过滤器",少在震荡市里挨打。
参考
- AlphaFeed Python SDK 文档:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart

