如何用 Python 计算 ADX 判断趋势强弱?

用户头像sh_*2176oo
2026-09-20 发布

一句话回答: ADX(平均趋向指数)度量的是"趋势有多强"而不是方向:ADX 越高趋势越明确,越低越像震荡。用 AlphaFeed 取复权日线,用 Wilder 口径算出 +DI-DIADX,就能用「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 高只代表"趋势强",涨跌都可能。方向要看 +DI vs -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 提供口径一致的复权日线,让你几十行就能给策略加一个"市场状态过滤器",少在震荡市里挨打。

参考

评论