Volume:EOM(简易波动指标)技术指标详解
一、EOM的定义
EOM(Ease of Movement,简易波动指标) 是由著名技术分析师Richard W. Arms, Jr.(阿姆斯)于20世纪70年代开发的独特技术指标。该指标通过将成交量与价格变动幅度相结合,来衡量价格变化的“轻松程度”。
核心思想
EOM的设计理念是:在成交量较小的情况下实现较大幅度的价格上涨,代表趋势的“轻松”程度较高,上涨动能强劲;反之,如果价格需要巨大的成交量才能小幅上涨,则表示趋势“费力”,动能可能正在衰竭。
阿姆斯希望通过这一指标帮助交易者判断:当前的价格变动是否伴随足够的成交量支持,以及趋势的强弱程度。
EOM的核心特征
| 理论基础 | 价格变动与成交量的比值关系 |
| 数值形式 | 围绕零轴波动的震荡指标 |
| 开发者 | Richard W. Arms, Jr.(阿姆斯) |
| 指标类型 | 成交量动量指标 |
| 与EMV的关系 | EOM = Ease of Movement,与EMV(简易波动值)是同一指标 |
EOM的核心逻辑
- 正值:价格上涨,且成交量相对较小 → 上涨“轻松”
- 负值:价格下跌,且成交量相对较小 → 下跌“轻松”
- 接近零:价格变动需要较大成交量支持 → 变动“费力”
这一逻辑可以用一句话概括:“价格走得远,成交量省得多” 是好事;“走得近,成交量消耗大” 是坏事。
二、EOM的计算方法
1. 核心计算公式
EOM的计算包含三个关键步骤:距离(Distance)、箱体比率(Box Ratio)和一周期EOM值。
2. 详细计算步骤
步骤一:计算中点距离
首先,计算当前周期的中点(最高价与最低价的平均值)与前一周期中点的差值:
Distancet=Hight+Lowt2−Hight−1+Lowt−12
\\mathrm{Distance}_t = \\frac{\\mathrm{High}_t + \\mathrm{Low}_t}{2} – \\frac{\\mathrm{High}_{t-1} + \\mathrm{Low}_{t-1}}{2}
Distancet=2Hight+Lowt−2Hight−1+Lowt−1
其中:
- Hight\\mathrm{High}_tHight:当前周期的最高价
- Lowt\\mathrm{Low}_{t}Lowt:当前周期的最低价
- Hight−1\\mathrm{High}_{t-1}Hight−1:前一周期的最高价
- Lowt−1\\mathrm{Low}_{t-1}Lowt−1:前一周期的最高价
这反映了价格重心的移动方向和距离。
步骤二:计算箱体比率(Box Ratio)
箱体比率将成交量标准化并与价格波动范围相结合:
BoxRatiot=Volumet/DivisorHight−Lowt
\\mathrm{BoxRatio}_t = \\frac{\\mathrm{Volume}_t / \\mathrm{Divisor}}{\\mathrm{High}_t – \\mathrm{Low}_t}
BoxRatiot=Hight−LowtVolumet/Divisor
其中:
- Volumet\\mathrm{Volume}_tVolumet:当前周期的成交量
- Divisor\\mathrm{Divisor}Divisor:标准化除数(pandas_ta默认使用1亿)
- Hight−Lowt\\mathrm{High}_t – \\mathrm{Low}_tHight−Lowt:当前周期的价格波动范围
步骤三:计算单周期EOM
EOMt=DistancetBoxRatiot
\\mathrm{EOM}_t = \\frac{\\mathrm{Distance}_t}{\\mathrm{BoxRatio}_t}
EOMt=BoxRatiotDistancet
步骤四:平滑处理
对单周期EOM值进行移动平均,以消除噪声:
EOMsmooth=SMA(EOM,length)
\\mathrm{EOM}_\\mathrm{smooth} = \\mathrm{SMA}(\\mathrm{EOM}, \\mathrm{length})
EOMsmooth=SMA(EOM,length)
其中 length\\mathrm{length}length 为平滑周期(默认14)。
3. 完整公式汇总
将上述步骤合并,EOM的完整计算公式为:
EOMt=Hight+Lowt2−Hight−1+Lowt−12Volumet/DHight−Lowt
\\mathrm{EOM}_t = \\cfrac{\\cfrac{\\mathrm{High}_t + \\mathrm{Low}_t}{2} – \\cfrac{\\mathrm{High}_{t-1} + \\mathrm{Low}_{t-1}}{2}}{\\cfrac{\\mathrm{Volume}_t / D}{\\mathrm{High}_t – \\mathrm{Low}_t}}
EOMt=Hight−LowtVolumet/D2Hight+Lowt−2Hight−1+Lowt−1
其中DDD为除数因子(默认100,000,000100,000,000100,000,000),整理后得:
EOMt=(Hight+Lowt−Hight−1−Lowt−1)×(Hight−Lowt)2×Volumet/D
\\mathrm{EOM}_t = \\frac{(\\mathrm{High}_t + \\mathrm{Low}_t – \\mathrm{High}_{t-1} – \\mathrm{Low}_{t-1}) \\times (\\mathrm{High}_t – \\mathrm{Low}_t)}{2 \\times \\mathrm{Volume}_t / D}
EOMt=2×Volumet/D(Hight+Lowt−Hight−1−Lowt−1)×(Hight−Lowt)
4. panda_ta的简化实现
根据pandas_ta的源代码,该库实现的计算步骤略有简化:
5. 参数说明
| high | pd.Series | 必需 | 最高价序列 |
| low | pd.Series | 必需 | 最低价序列 |
| close | pd.Series | 必需 | 收盘价序列 |
| volume | pd.Series | 必需 | 成交量序列 |
| length | int | 14 | 平滑周期,控制指标灵敏度和平滑度 |
| divisor | float | 100000000 (1e8) | 成交量标准化除数,用于将大成交量数值调整至合适范围 |
| drift | int | 1 | 计算距离时的价格间隔步长 |
| offset | int | 0 | 结果偏移周期数 |
关于divisor参数:由于股票成交量的数值通常非常大(动辄数百万、数千万),直接代入计算会导致EOM值过于庞大而失去参考意义。因此需要使用较大的除数将其标准化。默认值1e8(1亿)适用于A股等成交量较大的市场,对于成交量较小的品种可以适当调小,反之亦然。
三、EOM的使用方法
1. 零轴分界线——方向判断
EOM围绕零轴波动,零轴是多空力量的分界线:
| EOM > 0 | 价格上涨相对“轻松”,买方力量占优 | 顺势看多 |
| EOM < 0 | 价格下跌相对“轻松”,卖方力量占优 | 顺势看空 |
| EOM ≈ 0 | 价格变动“费力”,趋势不明显 | 观望 |
核心逻辑:EOM的正负取决于距离(Distance)的正负——价格上涨时距离为正,EOM为正;价格下跌时距离为负,EOM为负。
2. 数值大小——动量强度判断
EOM的绝对值大小反映了趋势的强度:
| 较大正值 | 价格上涨区间较大,成交量较小 → 上涨轻松,动能强劲 |
| 较小正值 | 价格小幅上涨但成交量较大 → 上涨费力,动能可能衰竭 |
| 较大负值 | 价格下跌区间较大,成交量较小 → 下跌轻松,空头强劲 |
| 较小负值 | 价格小幅下跌但成交量较大 → 下跌费力,空头可能衰竭 |
核心逻辑:箱体比率(Box Ratio)决定了EOM的强弱——成交量越小或波动范围越大,箱体比率越大,EOM的绝对值越大。
3. 与成交量配合使用
EOM不能替代成交量分析,建议与标准成交量指标配合使用:
| EOM正值上升 + 成交量温和放大 | 上涨趋势健康,动能持续 |
| EOM正值下降 + 成交量放大 | 上涨开始“费力”,动能可能衰竭 |
| EOM正值上升 + 成交量萎缩 | 上涨“轻松”,但需警惕缺乏成交量支持的上涨 |
4. 与其他技术指标的配合
| 移动平均线 | 确认趋势方向 | EOM零轴穿越方向与MA趋势一致时信号更可靠 |
| RSI | 验证超买超卖 | 结合RSI判断是否处于极端区域 |
| MACD | 确认动能变化 | 两个指标同时给出信号时可信度更高 |
| 标准成交量指标 | 验证EOM信号 | 用单纯成交量确认EOM反映的“轻松/费力”状态 |
5. 注意事项与局限性
使用EOM前需了解以下要点:
| 数值范围敏感 | EOM的数值受成交量标准化除数影响较大,不同市场的合适除数不同,需要手动调整 |
| 滞后性质 | 经过SMA平滑处理,本质上是滞后指标 |
| 震荡市假信号 | 在横盘整理市场中,EOM可能在零轴附近波动,产生假信号 |
| 不适合极端成交量 | 当成交量极大或极小时,EOM可能出现极端值,需结合其他指标判断 |
| 不能单独使用 | 建议与成交量、移动平均线等指标配合使用 |
关键提示:EOM对成交量的依赖是其最大的特色也是最大的局限。在使用前必须确保divisor参数的设置与市场成交量水平相匹配,否则指标将失去参考意义。
四、使用pandas_ta计算EOM(示例代码)
1. pandas_ta中的EOM函数
pandas_ta库内置了EOM指标的完整实现,函数位于volume模块中。
2. 函数完整参数
pandas_ta.volume.eom(high, low, close, volume, length=None, divisor=None, drift=None, offset=None, **kwargs)
参数详解:
| high | pd.Series | 必需 | 最高价序列 |
| low | pd.Series | 必需 | 最低价序列 |
| close | pd.Series | 必需 | 收盘价序列 |
| volume | pd.Series | 必需 | 成交量序列 |
| length | int | 14 | 平滑周期 |
| divisor | float | 100000000 | 成交量标准化除数 |
| drift | int | 1 | 价格变化步长 |
| offset | int | 0 | 结果偏移周期数 |
返回值:pd.Series——EOM值序列。
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
# 生成OHLC和成交量数据
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, 20000000, n) # 成交量以百万为单位
print("=" * 60)
print("数据预览:")
print(df.head())
print("\\n" + "=" * 60 + "\\n")
# ========== 第二步:计算EOM(基础用法) ==========
# 使用默认参数 length=14, divisor=100000000
df['EOM'] = ta.eom(df['high'], df['low'], df['close'], df['volume'])
print("EOM计算结果(最近10行):")
print(df[['close', 'volume', 'EOM']].tail(10))
print("\\n" + "=" * 60 + "\\n")
# ========== 第三步:手动验证EOM计算 ==========
def manual_eom(high, low, volume, divisor=100000000, length=14):
"""手动计算EOM验证pandas_ta结果"""
# 计算中点距离
hl2 = (high + low) / 2
distance = hl2 – hl2.shift(1)
# 计算箱体比率
hl_range = high – low
box_ratio = (volume / divisor) / hl_range
# 计算单周期EOM
eom_raw = distance / box_ratio
# 计算SMA平滑
eom_smooth = eom_raw.rolling(window=length).mean()
return eom_smooth
df['EOM_manual'] = manual_eom(df['high'], df['low'], df['volume'])
# 验证一致性
diff = (df['EOM'] – df['EOM_manual']).abs().max()
print(f"pandas_ta与手动计算的最大差异:{diff:.10f}")
print("\\n" + "=" * 60 + "\\n")
# ========== 第四步:不同参数的EOM对比 ==========
# 短周期 (9)
df['EOM_9'] = ta.eom(df['high'], df['low'], df['close'], df['volume'], length=9)
# 标准周期 (14)
df['EOM_14'] = ta.eom(df['high'], df['low'], df['close'], df['volume'], length=14)
# 长周期 (21)
df['EOM_21'] = ta.eom(df['high'], df['low'], df['close'], df['volume'], length=21)
# 调整除数(对于成交量较小的品种,可调小除数)
df['EOM_small_divisor'] = ta.eom(df['high'], df['low'], df['close'], df['volume'], divisor=10000000)
print("不同参数EOM对比(最近5行):")
print(df[['close', 'EOM_9', 'EOM_14', 'EOM_21', 'EOM_small_divisor']].tail())
print("\\n" + "=" * 60 + "\\n")
# ========== 第五步:生成零轴穿越信号 ==========
df['EOM_above_zero'] = df['EOM'] > 0
df['cross_above_zero'] = (df['EOM_above_zero'] == True) & (df['EOM_above_zero'].shift(1) == False)
df['cross_below_zero'] = (df['EOM_above_zero'] == False) & (df['EOM_above_zero'].shift(1) == True)
df['signal'] = ''
df.loc[df['cross_above_zero'], 'signal'] = '买入(上穿零轴)'
df.loc[df['cross_below_zero'], 'signal'] = '卖出(下穿零轴)'
print("零轴穿越信号(最近10个):")
signals = df[df['signal'] != ''].tail(10)
if not signals.empty:
print(signals[['close', 'EOM', 'signal']])
else:
print("近期无穿越信号")
print("\\n" + "=" * 60 + "\\n")
# ========== 第六步:生成EOM强度信号 ==========
# 定义强度阈值(需根据实际数据范围调整)
STRONG_POSITIVE = 0.5
STRONG_NEGATIVE = –0.5
df['strength_signal'] = ''
df.loc[df['EOM'] > STRONG_POSITIVE, 'strength_signal'] = '强上升(上涨轻松)'
df.loc[(df['EOM'] > 0) & (df['EOM'] <= STRONG_POSITIVE), 'strength_signal'] = '弱上升(上涨费力)'
df.loc[(df['EOM'] < 0) & (df['EOM'] >= STRONG_NEGATIVE), 'strength_signal'] = '弱下降(下跌费力)'
df.loc[df['EOM'] < STRONG_NEGATIVE, 'strength_signal'] = '强下降(下跌轻松)'
print("EOM强度信号统计:")
print(df['strength_signal'].value_counts())
print("\\n" + "=" * 60 + "\\n")
# ========== 第七步:策略回测(零轴穿越策略) ==========
# 策略:EOM > 0 时持仓多头
df['position'] = (df['EOM'] > 0).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("策略绩效统计(EOM零轴穿越策略回测):")
print(f"买入持有策略总收益率:{total_return_buyhold:.2%}")
print(f"EOM零轴穿越策略总收益率:{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[–100:], df['close'][–100:], label='Close Price',
linewidth=1.5, color='black')
plt.title('Price Chart (Last 100 days)', fontsize=14)
plt.ylabel('Price')
plt.legend(loc='upper left')
plt.grid(True, alpha=0.3)
# 子图2:成交量
plt.subplot(3, 1, 2)
plt.bar(df.index[–100:], df['volume'][–100:], label='Volume',
color='gray', alpha=0.6, width=0.8)
plt.title('Volume', fontsize=14)
plt.ylabel('Volume')
plt.legend(loc='upper left')
plt.grid(True, alpha=0.3)
# 子图3:EOM指标(含零轴)
plt.subplot(3, 1, 3)
plt.plot(df.index[–100:], df['EOM'][–100:], label='EOM (length=14)',
linewidth=1.5, color='blue')
plt.axhline(y=0, color='red', linestyle='-', linewidth=1, label='零轴')
# 强度参考线
plt.axhline(y=0.5, color='green', linestyle='–', linewidth=0.8, alpha=0.7, label='强上升参考线')
plt.axhline(y=–0.5, color='orange', linestyle='–', linewidth=0.8, alpha=0.7, label='强下降参考线')
# 填充区域
plt.fill_between(df.index[–100:], 0.5, df['EOM'][–100:], alpha=0.1, color='green',
where=(df['EOM'][–100:] > 0.5))
plt.fill_between(df.index[–100:], –0.5, df['EOM'][–100:], alpha=0.1, color='red',
where=(df['EOM'][–100:] < –0.5))
plt.title('EOM (Ease of Movement) – Volume-Based Oscillator', fontsize=14)
plt.xlabel('Date')
plt.ylabel('EOM Value')
plt.legend(loc='upper left')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ========== 第九步:不同周期EOM对比 ==========
plt.figure(figsize=(14, 6))
plt.plot(df.index[–100:], df['EOM_9'][–100:], label='EOM (length=9) – Fast',
linewidth=1, alpha=0.7)
plt.plot(df.index[–100:], df['EOM_14'][–100:], label='EOM (length=14) – Standard',
linewidth=1.5, color='blue')
plt.plot(df.index[–100:], df['EOM_21'][–100:], label='EOM (length=21) – Slow',
linewidth=1.5, color='green')
plt.axhline(y=0, color='red', linestyle='-', linewidth=0.8)
plt.title('EOM: Different Periods Comparison (9, 14, 21)', fontsize=14)
plt.xlabel('Date')
plt.ylabel('EOM Value')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# ========== 第十步:divisor参数影响分析 ==========
print("\\n" + "=" * 60)
print("divisor参数影响分析:")
print(f"默认divisor (1e8) – EOM范围:[{df['EOM'].min():.2f}, {df['EOM'].max():.2f}]")
print(f"较小divisor (1e7) – EOM范围:[{df['EOM_small_divisor'].min():.2f}, {df['EOM_small_divisor'].max():.2f}]")
print("建议:对于成交量较大的市场(如A股),使用默认divisor=1e8合适")
print("对于成交量较小的市场(如加密货币),可适当调小divisor")
print("=" * 60)
# ========== 第十一步:数据清洗提示 ==========
nan_count = df['EOM'].isna().sum()
print(f"\\nEOM初始NaN数量:{nan_count}")
print("原因:EOM需要至少length+1个周期的数据才能开始计算")
print("处理建议:df_clean = df.iloc[15:].copy()")
4. 关键注意事项
divisor参数的选择:
# 对于不同市场,建议调整divisor以获得合适的EOM数值范围
# 股票市场(成交量以百万、千万计)
df['EOM'] = ta.eom(high, low, close, volume, divisor=100000000)
# 加密货币市场(成交量较小)
df['EOM'] = ta.eom(high, low, close, volume, divisor=10000)
# 期货市场(根据合约大小调整)
df['EOM'] = ta.eom(high, low, close, volume, divisor=1000000)
# 观察EOM数值范围,一般应在[-1, 1]或[-2, 2]区间内较为理想
print(f"EOM范围:[{df['EOM'].min():.2f}, {df['EOM'].max():.2f}]")
初始NaN值处理:
# EOM需要足够的数据预热
df['EOM'] = ta.eom(df['high'], df['low'], df['close'], df['volume'])
# 查看NaN数量
nan_count = df['EOM'].isna().sum()
print(f"初始NaN数量:{nan_count}")
# 建议丢弃前length+1个不稳定数据点
df_clean = df.iloc[15:].copy()
五、总结
EOM(简易波动指标)是一种融合成交量与价格变动的独特技术指标,其核心价值与定位如下:
| 核心创新 | 将成交量纳入动量分析,反映价格变动的“轻松程度” |
| 核心公式 | 中点距离 ÷ 箱体比率 → SMA平滑 |
| 核心信号 | 零轴穿越、数值大小(强度判断) |
| 开发者 | Richard W. Arms, Jr.(阿姆斯) |
| 参数关键 | length(14)、divisor(1e8) |
| 最佳应用场景 | 趋势确认、成交量与价格关系的量化分析 |
| 主要局限 | 依赖divisor参数设置、滞后性、震荡市假信号 |
实战使用三原则:
最后提醒:EOM的核心价值在于回答一个关键问题:“当前的价格变动是否有足够的成交量支持?” 当价格上涨时,如果EOM的值也同时上升,说明上涨是“轻松的”,动能健康;反之,如果价格上涨而EOM值却在下降(甚至转为负值),说明上涨越来越“费力”,可能是趋势衰竭的前兆。然而,EOM的数值范围对divisor参数高度敏感,使用前必须根据具体市场的成交量水平进行参数优化。



