Momentum:DM(动向指标)技术指标详解
一、DM的定义
DM(Directional Movement,动向指标) 是J. Welles Wilder于1978年在《技术交易系统新概念》一书中提出的趋向分析系统(Directional Movement System) 的核心基础概念。
核心思想
DM的本质是量化价格向某一方向运动的“有效力度”。它不是简单地看涨跌了多少,而是通过比较每一根K线突破前一个K线价格范围的能力,来判断当前是买方还是卖方主导了市场的新动向。
为了排除日内无意义的随机波动,DM只提取能够“创新高”或“创新低”的部分作为有效动向。
DM的核心特征
| 组成部分 | +DM(上升动向)和 -DM(下降动向),两者通常成对出现 |
| 数值范围 | 非负值,通常取平滑后的均值进行计算 |
| 数学含义 | 正DM代表买方将价格推至新高的“力量”;负DM代表卖方将价格压至新低的“力量” |
| 主要用途 | 作为DMI指标(ADX、+DI、-DI) 的基础数据 |
DM与DMI/ADX的关系
DM是DMI指标体系的原始数据层:
| 第一层(原始数据) | +DM / -DM | 价格突破的原始力度 |
| 第二层(方向指标) | +DI / -DI | DM除以ATR(真实波幅),反映相对趋势强度 |
| 第三层(趋势强度) | ADX / DX | 基于DI差值计算,衡量趋势有无 |
二、DM的计算方法
1. 核心公式
首先计算两个中间变量——上升动向值
U
p
M
o
v
e
\\mathrm{UpMove}
UpMove 和下降动向值
D
o
w
n
M
o
v
e
\\mathrm{DownMove}
DownMove:
U
p
M
o
v
e
t
=
H
i
g
h
t
−
H
i
g
h
t
−
1
D
o
w
n
M
o
v
e
t
=
L
o
w
t
−
1
−
H
i
g
h
t
\\begin{aligned} \\mathrm{UpMove}_t &= \\mathrm{High}_t – \\mathrm{High}_{t-1} \\\\ \\mathrm{DownMove}_t &= \\mathrm{Low}_{t-1} – \\mathrm{High}_{t} \\end{aligned}
UpMovetDownMovet=Hight−Hight−1=Lowt−1−Hight 其中
H
i
g
h
t
\\mathrm{High}_t
Hight和
L
o
w
t
\\mathrm{Low}_t
Lowt 分别为
t
t
t 期的最高价和最低价。
2. 原始+DM与-DM的判定规则
根据
U
p
M
o
v
e
\\mathrm{UpMove}
UpMove 和
D
o
w
n
M
o
v
e
\\mathrm{DownMove}
DownMove 的比较结果,判定原始
+
D
M
r
a
w
+\\mathrm{DM}_\\mathrm{raw}
+DMraw和
−
D
M
r
a
w
-\\mathrm{DM}_\\mathrm{raw}
−DMraw:
+
D
M
r
a
w
,
t
=
{
U
p
M
o
v
e
t
若
U
p
M
o
v
e
t
>
D
o
w
n
M
o
v
e
t
且
U
p
M
o
v
e
t
>
0
0
其他情况
+\\mathrm{DM}_{\\mathrm{raw},t} = \\begin{cases} \\mathrm{UpMove}_t & \\text{若 } \\mathrm{UpMove}_t > \\mathrm{DownMove}_t \\text{ 且 } \\mathrm{UpMove}_t > 0 \\\\ 0 & \\text{其他情况} \\end{cases}
+DMraw,t={UpMovet0若 UpMovet>DownMovet 且 UpMovet>0其他情况
−
D
M
r
a
w
,
t
=
{
D
o
w
n
M
o
v
e
t
若
D
o
w
n
M
o
v
e
t
>
U
p
M
o
v
e
t
且
D
o
w
n
M
o
v
e
t
>
0
0
其他情况
-\\mathrm{DM}_{\\mathrm{raw},t} = \\begin{cases} \\mathrm{DownMove}_t & \\text{若 } \\mathrm{DownMove}_t > \\mathrm{UpMove}_t \\text{ 且 } \\mathrm{DownMove}_t > 0 \\\\ 0 & \\text{其他情况} \\end{cases}
−DMraw,t={DownMovet0若 DownMovet>UpMovet 且 DownMovet>0其他情况 判定规则的通俗解释:
| 上升动向 | >0 且 > DownMove | — | = UpMove | 0 |
| 下降动向 | — | >0 且 > UpMove | 0 | = DownMove |
| 内移日(无动向) | ≤0 | ≤0 | 0 | 0 |
| 两力均衡 | = DownMove >0 | = UpMove >0 | 0 | 0 |
3. 平滑处理(pandas_ta的实现)
原始
+
D
M
r
a
w
+\\mathrm{DM}_\\mathrm{raw}
+DMraw和
−
D
M
r
a
w
-\\mathrm{DM}_\\mathrm{raw}
−DMraw 波动较大,通常需要进行平滑。pandas_ta默认使用RMA(Wilder平滑移动平均) 对原始值进行平滑:
+
D
M
t
=
R
M
A
(
+
D
M
r
a
w
,
l
e
n
g
t
h
)
t
−
D
M
t
=
R
M
A
(
−
D
M
r
a
w
,
l
e
n
g
t
h
)
t
\\begin{aligned} +\\mathrm{DM}_t &= \\mathrm{RMA}(+\\mathrm{DM}_\\mathrm{raw}, \\mathrm{length})_t \\\\ -\\mathrm{DM}_t &= \\mathrm{RMA}(-\\mathrm{DM}_\\mathrm{raw}, \\mathrm{length})_t \\\\ \\end{aligned}
+DMt−DMt=RMA(+DMraw,length)t=RMA(−DMraw,length)t 其中length为计算周期(默认14),RMA的递推公式为:
R
M
A
t
=
R
M
A
t
−
1
×
(
l
e
n
g
t
h
−
1
)
+
V
a
l
u
e
t
l
e
n
g
t
h
\\mathrm{RMA}_t = \\frac{\\mathrm{RMA}_{t-1} \\times(\\mathrm{length} – 1) + \\mathrm{Value}_t}{\\mathrm{length}}
RMAt=lengthRMAt−1×(length−1)+Valuet
4. 参数说明
| length | 14 | 平滑周期(Wilder推荐值) |
| mamode | “rma” | 移动平均类型(默认使用Wilder平滑) |
| drift | 1 | 计算差值的周期步长,通常为1 |
5. 计算示例
假设某股票连续两日的数据如下:
| 最高价 | 100 | 105 |
| 最低价 | 98 | 102 |
计算过程:
- UpMove = 105 – 100 = 5
- DownMove = 98 – 102 = -4(≤0,取0)
- UpMove > DownMove 且 UpMove > 0 → +DM_raw = 5,-DM_raw = 0
注意:当UpMove和DownMove均为正值且不相等时,取较大者作为DM,较小者归零。
三、DM的使用方法
DM通常不单独使用,而是作为DMI指标的基础数据。以下是最常见的应用方法:
1. 计算方向线DI(核心应用)
DM平滑后,除以ATR(真实波幅)得到方向线DI:
+
D
I
=
100
×
+
D
M
(
平滑
)
A
T
R
−
D
I
=
100
×
−
D
M
(
平滑
)
A
T
R
\\begin{aligned} +\\mathrm{DI} = 100 \\times \\frac{+\\mathrm{DM}(平滑)}{\\mathrm{ATR}} \\\\[1.5ex] -\\mathrm{DI} = 100 \\times \\frac{-\\mathrm{DM}(平滑)}{\\mathrm{ATR}} \\\\ \\end{aligned}
+DI=100×ATR+DM(平滑)−DI=100×ATR−DM(平滑)
- +DI 数值越高,表示在给定的平滑周期内,上升力量越强
- -DI 数值越高,表示在给定的平滑周期内,下降力量越强
2. DI交叉信号(买入/卖出信号)
| 买入(金叉) | +DI从下方上穿-DI | 上升力量开始主导市场 |
| 卖出(死叉) | +DI从上方下穿-DI | 下降力量开始主导市场 |
3. 计算ADX(趋势强度)
基于+DI和-DI计算DX,再平滑得到ADX: $$ \\begin{aligned} \\mathrm{DX}_t &= \\left| \\frac{(+\\mathrm{DI}_t) – (-\\mathrm{DI}_t)}{(+\\mathrm{DI}_t) + (-\\mathrm{DI}_t)} \\right| \\times 100 \\[1.5ex]
\\mathrm{ADX}_t &= \\mathrm{RMA}(\\mathrm{DX}, \\mathrm{length})_t \\end{aligned} $$ ADX的数值范围0-100,用于判断趋势强度:
| < 20 | 无趋势 / 盘整 | 观望,不宜使用趋势策略 |
| 20 – 25 | 趋势形成中 | 可关注DI交叉信号 |
| 25 – 50 | 强趋势 | 顺势操作,DI交叉信号可信度高 |
| > 50 | 极端趋势 | 注意趋势可能过度延伸 |
4. 与ATR的关系
DM必须与ATR(真实波幅均值) 结合才能转化为+DI和-DI。ATR的计算公式为:
T
R
t
=
max
(
H
i
g
h
t
−
L
o
w
t
,
∣
H
i
g
h
t
−
C
l
o
s
e
t
−
1
∣
,
∣
L
o
w
t
−
C
l
o
s
e
t
−
1
∣
)
A
T
R
t
=
A
T
R
t
−
1
×
(
l
e
n
g
t
h
−
1
)
+
T
R
t
l
e
n
g
t
h
\\begin{aligned} \\mathrm{TR}_t &= \\max(\\mathrm{High}_t – \\mathrm{Low}_t, |\\mathrm{High}_t – \\mathrm{Close}_{t-1}|,|\\mathrm{Low}_t – \\mathrm{Close}_{t-1}|) \\\\ \\mathrm{ATR}_t &= \\frac{\\mathrm{ATR}_{t-1} \\times (\\mathrm{length} – 1) + \\mathrm{TR}_t}{\\mathrm{length}} \\end{aligned}
TRtATRt=max(Hight−Lowt,∣Hight−Closet−1∣,∣Lowt−Closet−1∣)=lengthATRt−1×(length−1)+TRt
- 当ATR很小、DM很大时,DI值会很大,代表趋势非常强劲
- 当ATR很大、DM很小时,DI值会很小,代表当前方向虽在延续但力度不足
5. 注意事项与局限性
使用DM/DMI前需了解以下要点:
| 本质是滞后指标 | DM经过两次平滑处理,对价格变化反应较慢,属于中长线指标,不适合短线交易 |
| 震荡市表现差 | 盘整行情中DI线反复交叉,信号频繁失效 |
| 不单独使用 | DM原始值必须与ATR结合转化为DI才能用于交易决策 |
| 参数固定 | Wilder原始设定的14周期通常不建议随意改动 |
四、使用pandas_ta计算DM(附示例代码)
1. pandas_ta中的DM函数
pandas_ta库内置了DM指标的完整实现,函数位于momentum模块中。
2. 函数完整参数
pandas_ta.momentum.dm(high, low, length=None, mamode=None, talib=None, drift=None, offset=None, **kwargs)
参数详解:
| high | pd.Series | 必需 | 最高价序列 |
| low | pd.Series | 必需 | 最低价序列 |
| length | int | 14 | 平滑周期(Wilder推荐) |
| mamode | str | “rma” | 移动平均类型(默认使用Wilder平滑) |
| talib | bool | True | 若安装TA-Lib则使用其版本 |
| drift | int | 1 | 价格变化的步长 |
| offset | int | 0 | 结果偏移周期数 |
返回值:pd.DataFrame——包含两列:DMP_{length}(+DM)和DMN_{length}(-DM)。
3. 示例代码
import pandas as pd
import pandas_ta as ta
import numpy as np
import matplotlib.pyplot as plt
# ========== 第一步:准备数据 ==========
np.random.seed(42)
dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
n = len(dates)
# 生成带趋势和周期波动的价格序列
trend = np.linspace(0, 25, n)
cycle = np.sin(np.linspace(0, 6 * np.pi, n)) * 10
noise = np.random.randn(n) * 2
price_series = 100 + trend + cycle + noise
df = pd.DataFrame(index=dates)
df['close'] = price_series
df['high'] = df['close'] + np.abs(np.random.randn(n)) * 2 + 1
df['low'] = df['close'] – np.abs(np.random.randn(n)) * 2 – 1
df['volume'] = np.random.randint(1000000, 10000000, n)
print("=" * 60)
print("数据预览:")
print(df.head())
print("\\n" + "=" * 60 + "\\n")
# ========== 第二步:计算DM(基础用法) ==========
# 使用默认参数 length=14, mamode='rma'
dm_df = ta.dm(df['high'], df['low'])
# 将计算结果添加到原DataFrame
df['DMP'] = dm_df['DMP_14']
df['DMN'] = dm_df['DMN_14']
print("DM计算结果(最近10行):")
print(df[['high', 'low', 'DMP', 'DMN']].tail(10))
print("\\n" + "=" * 60 + "\\n")
# ========== 第三步:手动验证DM计算 ==========
def manual_dm(high, low, length=14):
"""手动计算DM验证pandas_ta结果"""
# 计算UpMove和DownMove
up_move = high – high.shift(1)
down_move = low.shift(1) – low
# 判定原始DM
pos_raw = pd.Series(0, index=high.index)
neg_raw = pd.Series(0, index=high.index)
# 上升动向条件:up_move > down_move and up_move > 0
condition_pos = (up_move > down_move) & (up_move > 0)
pos_raw[condition_pos] = up_move[condition_pos]
# 下降动向条件:down_move > up_move and down_move > 0
condition_neg = (down_move > up_move) & (down_move > 0)
neg_raw[condition_neg] = down_move[condition_neg]
# 使用RMA平滑
def rma(series, length):
alpha = 1 / length
rma_values = series.ewm(alpha=alpha, adjust=False).mean()
return rma_values
pos_smoothed = rma(pos_raw, length)
neg_smoothed = rma(neg_raw, length)
return pos_smoothed, neg_smoothed
df['DMP_manual'], df['DMN_manual'] = manual_dm(df['high'], df['low'])
# 验证一致性
diff_pos = (df['DMP'] – df['DMP_manual']).abs().max()
diff_neg = (df['DMN'] – df['DMN_manual']).abs().max()
print(f"pandas_ta与手动计算+DM的最大差异:{diff_pos:.10f}")
print(f"pandas_ta与手动计算-DM的最大差异:{diff_neg:.10f}")
print("\\n" + "=" * 60 + "\\n")
# ========== 第四步:基于DM计算DI和ADX ==========
# 计算ATR(真实波幅均值)
df['ATR'] = ta.atr(df['high'], df['low'], df['close'], length=14)
# 计算+DI和-DI
df['PLUS_DI'] = 100 * df['DMP'] / df['ATR']
df['MINUS_DI'] = 100 * df['DMN'] / df['ATR']
# 计算DX
df['DI_DIFF'] = abs(df['PLUS_DI'] – df['MINUS_DI'])
df['DI_SUM'] = df['PLUS_DI'] + df['MINUS_DI']
df['DX'] = 100 * df['DI_DIFF'] / df['DI_SUM']
# 使用RMA计算ADX
def rma(series, length):
alpha = 1 / length
return series.ewm(alpha=alpha, adjust=False).mean()
df['ADX'] = rma(df['DX'], length=14)
print("DI和ADX计算结果(最近10行):")
print(df[['PLUS_DI', 'MINUS_DI', 'ADX']].tail(10))
print("\\n" + "=" * 60 + "\\n")
# ========== 第五步:生成DI交叉信号 ==========
# DI金叉:+DI上穿-DI
df['DI_cross_buy'] = (df['PLUS_DI'] > df['MINUS_DI']) & (df['PLUS_DI'].shift(1) <= df['MINUS_DI'].shift(1))
# DI死叉:+DI下穿-DI
df['DI_cross_sell'] = (df['PLUS_DI'] < df['MINUS_DI']) & (df['PLUS_DI'].shift(1) >= df['MINUS_DI'].shift(1))
# ADX趋势强度判断
df['strong_trend'] = df['ADX'] > 25
df['no_trend'] = df['ADX'] < 20
# 综合信号
df['signal'] = ''
df.loc[df['DI_cross_buy'] & df['strong_trend'], 'signal'] = '买入(金叉+强趋势)'
df.loc[df['DI_cross_buy'] & ~df['strong_trend'], 'signal'] = '买入信号弱(ADX不足)'
df.loc[df['DI_cross_sell'] & df['strong_trend'], 'signal'] = '卖出(死叉+强趋势)'
df.loc[df['DI_cross_sell'] & ~df['strong_trend'], 'signal'] = '卖出信号弱(ADX不足)'
print("DI交叉信号统计:")
print(f"金叉数量:{df['DI_cross_buy'].sum()}")
print(f"死叉数量:{df['DI_cross_sell'].sum()}")
print(f"强趋势交易日占比:{df['strong_trend'].mean():.2%}")
print("\\n最近10个交易信号:")
signals = df[df['signal'] != ''].tail(10)
if not signals.empty:
print(signals[['close', 'PLUS_DI', 'MINUS_DI', 'ADX', 'signal']])
else:
print("近期无信号")
print("\\n" + "=" * 60 + "\\n")
# ========== 第六步:策略回测(DI交叉+ADX过滤策略) ==========
# 策略:+DI > -DI 且 ADX > 25 时持仓多头
df['position'] = ((df['PLUS_DI'] > df['MINUS_DI']) & (df['ADX'] > 25)).astype(int)
# 计算收益
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['position'].shift(1) * df['returns']
total_return_buyhold = (1 + df['returns']).prod() – 1
total_return_strategy = (1 + df['strategy_returns']).prod() – 1
win_rate = (df['strategy_returns'] > 0).sum() / (df['strategy_returns'] != 0).sum() if (df['strategy_returns'] != 0).sum() > 0 else 0
print("=" * 60)
print("策略绩效统计(DI交叉+ADX过滤策略回测):")
print(f"买入持有策略总收益率:{total_return_buyhold:.2%}")
print(f"趋势过滤策略总收益率:{total_return_strategy:.2%}")
print(f"策略胜率:{win_rate:.2%}")
print("\\n⚠️ 注意:此为简化回测,仅供参考")
print("=" * 60 + "\\n")
# ========== 第七步:可视化 ==========
plt.figure(figsize=(14, 12))
# 子图1:价格走势
plt.subplot(3, 1, 1)
plt.plot(df.index[–150:], df['close'][–150:], label='Close Price',
linewidth=1.5, color='black')
plt.title('Price Chart (Last 150 days)', fontsize=14)
plt.ylabel('Price')
plt.legend()
plt.grid(True, alpha=0.3)
# 子图2:+DI、-DI和DM值
plt.subplot(3, 1, 2)
plt.plot(df.index[–150:], df['DMP'][–150:], label='+DM (Bullish Power)',
linewidth=1, alpha=0.7, color='green')
plt.plot(df.index[–150:], df['DMN'][–150:], label='-DM (Bearish Power)',
linewidth=1, alpha=0.7, color='red')
plt.plot(df.index[–150:], df['PLUS_DI'][–150:], label='+DI',
linewidth=1.5, color='darkgreen')
plt.plot(df.index[–150:], df['MINUS_DI'][–150:], label='-DI',
linewidth=1.5, color='darkred')
plt.axhline(y=25, color='blue', linestyle='–', linewidth=0.8, alpha=0.5)
plt.title('Directional Movement: +DM/-DM (raw) and +DI/-DI', fontsize=14)
plt.ylabel('Value')
plt.legend(loc='upper left')
plt.grid(True, alpha=0.3)
# 子图3:ADX趋势强度
plt.subplot(3, 1, 3)
plt.plot(df.index[–150:], df['ADX'][–150:], label='ADX (Trend Strength)',
linewidth=1.5, color='purple')
plt.axhline(y=25, color='blue', linestyle='–', linewidth=1, label='ADX > 25: 趋势强')
plt.axhline(y=20, color='orange', linestyle='–', linewidth=1, label='ADX < 20: 无趋势')
plt.fill_between(df.index[–150:], 20, 25, alpha=0.2, color='yellow')
plt.title('ADX – Average Directional Index', fontsize=14)
plt.xlabel('Date')
plt.ylabel('ADX Value')
plt.ylim(0, 60)
plt.legend(loc='upper left')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ========== 第八步:不同参数对比 ==========
# 对比不同length参数对DM的影响
dm_9 = ta.dm(df['high'], df['low'], length=9)
dm_14 = ta.dm(df['high'], df['low'], length=14)
dm_20 = ta.dm(df['high'], df['low'], length=20)
df['DMP_9'] = dm_9['DMP_9']
df['DMP_14'] = dm_14['DMP_14']
df['DMP_20'] = dm_20['DMP_20']
plt.figure(figsize=(14, 6))
plt.plot(df.index[–100:], df['DMP_9'][–100:], label='DMP (length=9) – Fast',
linewidth=1, alpha=0.7)
plt.plot(df.index[–100:], df['DMP_14'][–100:], label='DMP (length=14) – Standard',
linewidth=1.5, color='blue')
plt.plot(df.index[–100:], df['DMP_20'][–100:], label='DMP (length=20) – Slow',
linewidth=1.5, color='green')
plt.title('+DM: Different Periods Comparison (9, 14, 20)', fontsize=14)
plt.xlabel('Date')
plt.ylabel('+DM Value')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# ========== 第九步:数据清洗提示 ==========
nan_count = df['DMP'].isna().sum()
print(f"\\nDM初始NaN数量:{nan_count}")
print("原因:DM需要至少length个周期的数据才能计算平滑值")
print("处理建议:df_clean = df.iloc[14:].copy()")
5. 关键注意事项
DM是DMI指标系统的基础: DM并不是最终用于交易的指标,而是计算+DI、-DI和ADX的中间变量。完整的DMI指标分析需要结合这三条线。
参数名称注意: DM函数返回的DataFrame包含DMP_{length}(+DM)和DMN_{length}(-DM)两列,列名中的length会被替换为实际周期值。
pandas_ta与TA-Lib的差异: pandas_ta的开发文档明确指出,其原生实现的DM与TA-Lib的PLUS_DM、MINUS_DM在数值上可能存在差异。用户可通过talib=True参数强制使用TA-Lib版本(需预先安装TA-Lib库)。
五、总结
DM是J. Welles Wilder趋向分析系统的基础数据层,其核心价值与定位如下:
| 核心创新 | 只提取“突破前高/前低”的部分作为有效动向,过滤无关波动 |
| 两个关键值 | +DM(上升动向)和 -DM(下降动向),非负值 |
| 主要用途 | 与ATR结合转化为+DI和-DI,进而计算ADX |
| 最佳应用场景 | 中长线趋势识别、趋势强度判断 |
| 主要局限 | 滞后性(两次平滑)、震荡市假信号多 |
实战使用三原则:
最后提醒:DM作为DMI指标系统的基石,其核心价值在于帮助交易者回答两个核心问题:当前市场是否有趋势?趋势的方向是什么? 然而,一个完整的交易决策不应仅依靠单一指标。将DMI系统与K线形态、移动平均线等其他工具结合使用,可以显著提高信号的可靠性。


