Volume:PVO(百分比成交量震荡指标)技术指标详解
一、PVO的定义
PVO(Percentage Volume Oscillator,百分比成交量震荡指标) 是一种基于成交量的动量指标,通过计算两条不同周期成交量移动平均线之间的百分比差异来衡量成交量的动能变化。
核心思想
PVO的设计理念与PPO(价格百分比震荡指标)完全平行,但作用于成交量而非价格。其核心逻辑是:当短期成交量均线高于长期均线时,PVO为正,表明成交量动能增强,市场交易活跃度提高;反之则为负,表明成交量动能减弱,市场可能进入调整期。
PVO的核心特征
| 特征 | 说明 |
|---|---|
| 理论基础 | 成交量动量的标准化测量 |
| 输出形式 | 百分比值,便于跨品种比较 |
| 数值范围 | 无固定上限,围绕0轴波动 |
| 默认参数 | (12, 26, 9) --- 与MACD/PPO相同 |
| 指标类型 | 成交量动量震荡指标 |
| 三线结构 | PVO线 + 信号线 + 柱状图 |
PVO与PPO/MACD的对比
| 对比维度 | PVO | PPO | MACD |
|---|---|---|---|
| 输入数据 | 成交量(Volume) | 价格(Close) | 价格(Close) |
| 计算公式 | (EMAfast−EMAslow)/EMAslow×100(\mathrm{EMA_{fast} - EMA_{slow})/EMA_{slow}} × 100(EMAfast−EMAslow)/EMAslow×100 | (MAfast−MAslow)/MAslow×100\mathrm{(MA_{fast} - MA_{slow})/MA_{slow}} × 100(MAfast−MAslow)/MAslow×100 | EMAfast−EMAslow\mathrm{EMA_{fast }- EMA_{slow}}EMAfast−EMAslow |
| 输出形式 | 百分比 | 百分比 | 绝对差值 |
| 主要用途 | 成交量动能分析 | 价格动量分析 | 价格趋势分析 |
PVO的价值定位
成交量是金融市场的"燃料"------没有成交量的价格变动往往不可持续。PVO的价值在于:
- 趋势确认:价格突破时若PVO同步上升,验证突破的有效性
- 动能预判:PVO提前于价格变化反映资金流向
- 跨品种比较:百分比形式使不同规模股票的成交量动能可直接比较
二、PVO的计算方法
1. 核心公式
PVO的计算包含三个层次:
第一步:计算成交量EMA
首先计算成交量的快速和慢速指数移动平均:
EMAfast=EMA(Volume,fast)EMAslow=EMA(Volume,slow) \begin{aligned} \mathrm{EMA_{fast}} &= \mathrm{EMA(Volume,fast)} \\ \mathrm{EMA_{slow}} &= \mathrm{EMA(Volume,slow)} \\ \end{aligned} EMAfastEMAslow=EMA(Volume,fast)=EMA(Volume,slow)
其中:
- fast\mathrm{fast}fast:快速EMA周期(默认12)
- slow\mathrm{slow}slow:慢速EMA周期(默认26)
第二步:计算PVO线(主线)
PVO=EMAfast−EMAslowEMAslow×100 \mathrm{PVO = \frac{EMA_{fast} − EMA_{slow}}{EMA_{slow}} \times 100} PVO=EMAslowEMAfast−EMAslow×100
- 当PVO = 5时,表示短期成交量均线比长期均线高出 5%
- 当PVO = -3时,表示短期成交量均线比长期均线低 3%
第三步:计算信号线
Signal=EMA(PVO,signal) \mathrm{Signal=EMA(PVO,signal)} Signal=EMA(PVO,signal)
其中 signal 为信号线平滑周期(默认9)。
第四步:计算柱状图
Histogram=PVO−Signal \mathrm{Histogram = PVO − Signal} Histogram=PVO−Signal
2. 参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
fast |
12 | 快速成交量EMA周期 |
slow |
26 | 慢速成交量EMA周期 |
signal |
9 | 信号线EMA周期 |
scalar |
100 | 放大倍数(转换为百分比) |
说明:PVO使用EMA而非SMA作为移动平均类型,这是与PPO的一个重要区别(PPO默认可使用SMA)。
3. 计算示例
假设某股票最近几日的成交量数据如下,使用默认参数(12,26,9):
| 日期 | 成交量 | EMA₁₂ | EMA₂₆ | PVO计算 | PVO值 | 信号线 |
|---|---|---|---|---|---|---|
| Day1-12 | 初始期 | 计算中 | --- | --- | --- | --- |
| Day26 | 稳定期 | 1,200,000 | 1,000,000 | (1.2M-1M)/1M×100 | 20.0 | --- |
| Day27 | 新值 | 1,210,000 | 1,005,000 | (1.21M-1.005M)/1.005M×100 | 20.4 | 计算中 |
计算过程(以Day26为例):
- EMA₁₂ = 1,200,000
- EMA₂₆ = 1,000,000
- PVO = (1,200,000 - 1,000,000) / 1,000,000 × 100 = 20%
该PVO值为20%,表示短期成交量均线比长期均线高出20%,成交量动能显著增强。
三、PVO的使用方法
1. 零轴穿越------成交量多空分界
PVO围绕0轴波动,是判断成交量相对强弱的最直观方法:
| PVO状态 | 市场含义 | 操作倾向 |
|---|---|---|
| PVO > 0 | 短期成交量均线高于长期均线 | 成交量相对活跃,市场参与度高 |
| PVO < 0 | 短期成交量均线低于长期均线 | 成交量相对低迷,市场参与度低 |
| 上穿0轴 | 成交量动能从弱转强 | 资金开始活跃,关注突破机会 |
| 下穿0轴 | 成交量动能从强转弱 | 资金趋于谨慎,警惕回调 |
2. 信号线交叉------核心交易信号
PVO线与信号线的交叉是PVO最核心的交易信号:
| 信号类型 | 触发条件 | 含义 | 操作建议 |
|---|---|---|---|
| 看涨交叉(金叉) | PVO线从下方上穿信号线 | 成交量动能开始增强 | 确认价格趋势的有效性 |
| 看跌交叉(死叉) | PVO线从上方下穿信号线 | 成交量动能开始减弱 | 警惕价格趋势可能衰竭 |
3. 柱状图------动能强度判断
PVO柱状图反映了PVO线与信号线的距离,直观显示动能变化:
| 柱状图状态 | 含义 | 预期走势 |
|---|---|---|
| 正值且扩大 | 成交量动能持续增强 | 趋势确认度提高 |
| 正值但缩小 | 成交量动能开始衰竭 | 趋势可能面临回调 |
| 负值且扩大 | 成交量动能持续减弱 | 观望为主 |
| 负值但缩小 | 成交量动能开始恢复 | 关注企稳信号 |
4. 趋势确认------PVO的核心价值
PVO最重要的用途是验证价格信号的可靠性:
买入确认:
- 价格向上突破关键阻力位
- PVO同时上穿0轴或处于正值区域
- 成交量动能支持上涨 → 突破可信度高
卖出警示:
- 价格创出新高
- 但PVO呈下降趋势或处于负值区域
- 成交量不支持上涨 → 警惕假突破
顶部背离:
- 价格创出新高
- PVO未能创出新高(形成更低高点)
- 上涨动能与成交量脱节 → 见顶风险
5. 趋势方向判断
PVO的走势方向反映成交量动能的变化趋势:
| PVO走势 | 含义 | 操作策略 |
|---|---|---|
| PVO持续上升 | 成交量水平持续放大 | 市场活跃度提升,趋势健康 |
| PVO持续下降 | 成交量水平持续萎缩 | 市场活跃度下降,谨慎操作 |
| PVO高位走平 | 成交量动能趋于稳定 | 可能进入盘整 |
6. 与其他指标的配合策略
| 配合指标 | 作用 | 具体用法 |
|---|---|---|
| 价格指标(MACD/PPO) | 趋势方向判断 | 价格指标发出买入信号 + PVO确认 → 信号更可靠 |
| OBV(平衡成交量) | 资金流向确认 | 两个成交量指标方向一致时可信度更高 |
| 布林带 | 价格边界判断 | 价格触及边界时用PVO验证突破有效性 |
7. 注意事项与局限性
使用PVO前需了解以下要点:
| 局限性 | 说明 |
|---|---|
| 依赖成交量数据质量 | 成交量数据的准确性直接影响指标有效性 |
| 滞后性质 | 基于移动平均计算,本质上是滞后指标 |
| 震荡市假信号 | 横盘市场中PVO可能频繁穿越0轴和信号线 |
| 不能单独使用 | PVO是成交量确认工具,应与价格分析结合使用 |
| 不同市场阈值不同 | 不同品种的PVO正常波动范围存在差异 |
四、使用pandas_ta计算PVO(附示例代码)
1. pandas_ta中的PVO函数
pandas_ta库内置了PVO指标的完整实现,函数位于volume模块中。
2. 函数完整参数
python
pandas_ta.volume.pvo(volume, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs)
参数详解:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
volume |
pd.Series | 必需 | 成交量序列 |
fast |
int | 12 | 快速EMA周期 |
slow |
int | 26 | 慢速EMA周期 |
signal |
int | 9 | 信号线EMA周期 |
scalar |
float | 100 | 放大倍数(转换为百分比) |
offset |
int | 0 | 结果偏移周期数 |
返回值 :pd.DataFrame------包含三列:
PVO_{fast}_{slow}_{signal}:PVO主线(百分比值)PVOh_{fast}_{slow}_{signal}:柱状图(PVO - 信号线)PVOs_{fast}_{slow}_{signal}:信号线(PVO的EMA平滑)
3. 示例代码
python
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='2023-01-01', end='2024-12-31', freq='D')
n = len(dates)
# 生成价格序列(用于展示配合关系)
trend = np.linspace(0, 30, n)
cycle = np.sin(np.linspace(0, 6 * np.pi, n)) * 12
noise = np.random.randn(n) * 2
price_series = 100 + trend + cycle + noise
# 生成成交量序列(与价格变动有一定相关性)
volume_series = np.random.randint(5000000, 30000000, n)
# 在价格上涨时成交量倾向于放大
volume_series = volume_series * (1 + 0.5 * (np.diff(price_series, prepend=price_series[0]) / price_series[0]).clip(-0.3, 0.5))
df = pd.DataFrame(index=dates)
df['close'] = price_series
df['high'] = df['close'] + np.abs(np.random.randn(n)) * 3 + 1.5
df['low'] = df['close'] - np.abs(np.random.randn(n)) * 3 - 1.5
df['volume'] = volume_series.astype(int)
df['volume'] = df['volume'].clip(lower=0)
print("=" * 60)
print("数据预览:")
print(df.head())
print("\n" + "=" * 60 + "\n")
# ========== 第二步:计算PVO(基础用法) ==========
# 使用默认参数 fast=12, slow=26, signal=9
pvo_df = ta.pvo(df['volume'])
# 查看返回的DataFrame结构
print("PVO返回列名称:")
print(pvo_df.columns.tolist())
print("\n" + "=" * 60 + "\n")
# 将计算结果添加到原DataFrame
df['PVO'] = pvo_df.iloc[:, 0] # PVO主线
df['PVOh'] = pvo_df.iloc[:, 1] # 柱状图
df['PVOs'] = pvo_df.iloc[:, 2] # 信号线
print("PVO计算结果(最近10行):")
print(df[['close', 'volume', 'PVO', 'PVOs', 'PVOh']].tail(10))
print("\n" + "=" * 60 + "\n")
# ========== 第三步:手动验证PVO计算 ==========
def manual_pvo(volume, fast=12, slow=26, signal=9):
"""手动计算PVO验证pandas_ta结果"""
# 计算快速和慢速EMA
ema_fast = volume.ewm(span=fast, adjust=False).mean()
ema_slow = volume.ewm(span=slow, adjust=False).mean()
# 计算PVO主线
pvo = 100 * (ema_fast - ema_slow) / ema_slow
# 计算信号线
signal_line = pvo.ewm(span=signal, adjust=False).mean()
# 计算柱状图
histogram = pvo - signal_line
return pvo, signal_line, histogram
df['PVO_manual'], df['PVOs_manual'], df['PVOh_manual'] = manual_pvo(df['volume'])
# 验证一致性
diff_pvo = (df['PVO'] - df['PVO_manual']).abs().max()
print(f"PVO主线手动验证最大差异:{diff_pvo:.10f}")
print("\n" + "=" * 60 + "\n")
# ========== 第四步:自定义参数 ==========
# 短线参数
pvo_fast = ta.pvo(df['volume'], fast=9, slow=21, signal=7)
df['PVO_fast'] = pvo_fast.iloc[:, 0]
# 长线参数
pvo_slow = ta.pvo(df['volume'], fast=20, slow=50, signal=13)
df['PVO_slow'] = pvo_slow.iloc[:, 0]
print("不同参数PVO对比(最近5行):")
print(df[['close', 'PVO', 'PVO_fast', 'PVO_slow']].tail())
print("\n" + "=" * 60 + "\n")
# ========== 第五步:生成信号线交叉信号 ==========
df['cross_above_signal'] = (df['PVO'] > df['PVOs']) & (df['PVO'].shift(1) <= df['PVOs'].shift(1))
df['cross_below_signal'] = (df['PVO'] < df['PVOs']) & (df['PVO'].shift(1) >= df['PVOs'].shift(1))
df['signal'] = ''
df.loc[df['cross_above_signal'], 'signal'] = '金叉(成交量动能增强)'
df.loc[df['cross_below_signal'], 'signal'] = '死叉(成交量动能减弱)'
print("信号线交叉信号统计:")
print(f"金叉数量:{df['cross_above_signal'].sum()}")
print(f"死叉数量:{df['cross_below_signal'].sum()}")
print("\n最近10个信号:")
signals = df[df['signal'] != ''].tail(10)
if not signals.empty:
print(signals[['close', 'volume', 'PVO', 'PVOs', 'signal']])
print("\n" + "=" * 60 + "\n")
# ========== 第六步:零轴穿越信号 ==========
df['cross_above_zero'] = (df['PVO'] > 0) & (df['PVO'].shift(1) <= 0)
df['cross_below_zero'] = (df['PVO'] < 0) & (df['PVO'].shift(1) >= 0)
df['zero_signal'] = ''
df.loc[df['cross_above_zero'], 'zero_signal'] = '上穿零轴(成交量放量)'
df.loc[df['cross_below_zero'], 'zero_signal'] = '下穿零轴(成交量缩量)'
print("零轴穿越信号统计:")
print(f"上穿零轴数量:{df['cross_above_zero'].sum()}")
print(f"下穿零轴数量:{df['cross_below_zero'].sum()}")
print("\n" + "=" * 60 + "\n")
# ========== 第七步:价格与PVO配合验证 ==========
# 检测价格突破时PVO是否确认
df['price_above_ma20'] = df['close'] > df['close'].rolling(20).mean()
df['pvo_positive'] = df['PVO'] > 0
# 价格在均线上方且PVO为正 = 上涨趋势确认
df['bullish_confirm'] = df['price_above_ma20'] & df['pvo_positive']
# 检测顶背离(简化版:价格新高但PVO未新高)
rolling_max_price_20d = df['close'].rolling(20).max()
df['price_new_high'] = df['close'] == rolling_max_price_20d
rolling_max_pvo_20d = df['PVO'].rolling(20).max()
df['pvo_not_new_high'] = df['PVO'] < rolling_max_pvo_20d.shift(1)
df['bearish_divergence'] = df['price_new_high'] & df['pvo_not_new_high'] & (df['PVO'] > 0)
print("价格与PVO配合分析:")
print(f"上涨趋势确认(价格>MA20 + PVO>0)交易日占比:{df['bullish_confirm'].mean():.2%}")
print(f"顶背离信号数量:{df['bearish_divergence'].sum()}")
print("\n" + "=" * 60 + "\n")
# ========== 第八步:策略回测(价格+PVO双重确认策略) ==========
# 策略:价格在MA20上方 且 PVO > 0 时持仓
df['ma20'] = df['close'].rolling(20).mean()
df['position'] = ((df['close'] > df['ma20']) & (df['PVO'] > 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
print("=" * 60)
print("策略绩效统计(价格+PVO双重确认策略回测):")
print(f"买入持有策略总收益率:{total_return_buyhold:.2%}")
print(f"双重确认策略总收益率:{total_return_strategy:.2%}")
print("\n注意:PVO是成交量确认工具,建议与价格策略配合使用")
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.plot(df.index[-100:], df['ma20'][-100:], label='MA20',
linewidth=1, color='orange', linestyle='--')
plt.title('Price Chart (Last 100 days)', fontsize=14)
plt.ylabel('Price')
plt.legend()
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()
plt.grid(True, alpha=0.3)
# 子图3:PVO指标
plt.subplot(3, 1, 3)
plt.plot(df.index[-100:], df['PVO'][-100:], label='PVO主线',
linewidth=1.5, color='blue')
plt.plot(df.index[-100:], df['PVOs'][-100:], label='信号线',
linewidth=1.5, color='orange', linestyle='--')
plt.bar(df.index[-100:], df['PVOh'][-100:], label='柱状图',
color='green', alpha=0.3, width=0.8)
plt.axhline(y=0, color='red', linestyle='-', linewidth=1, label='零轴')
# 标记金叉死叉
buy_signals = df[df['cross_above_signal']]
sell_signals = df[df['cross_below_signal']]
plt.scatter(buy_signals.index[-20:], buy_signals['PVO'][-20:],
color='green', marker='^', s=80, label='金叉', alpha=0.8)
plt.scatter(sell_signals.index[-20:], sell_signals['PVO'][-20:],
color='red', marker='v', s=80, label='死叉', alpha=0.8)
plt.title('PVO (Percentage Volume Oscillator) - 成交量动能指标', fontsize=14)
plt.xlabel('Date')
plt.ylabel('PVO (%)')
plt.legend(loc='upper left')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ========== 第十步:数据清洗提示 ==========
nan_count = df['PVO'].isna().sum()
print(f"\nPVO初始NaN数量:{nan_count}")
print("原因:PVO需要至少slow周期(默认26)个数据点才能计算EMA")
print("处理建议:df_clean = df.iloc[26:].copy()")
print("\n" + "=" * 60)
print("PVO使用提示:")
print("1. PVO是成交量确认工具,不应单独作为买卖信号")
print("2. 价格突破时若PVO同步上升,验证突破有效性")
print("3. 金叉/死叉反映成交量动能变化,而非价格方向")
print("4. 建议与价格指标(MACD/PPO)配合使用")
print("=" * 60)

五、总结
PVO(百分比成交量震荡指标)是一种将成交量动能标准化的技术工具,其核心价值与定位如下:
| 维度 | 特点 |
|---|---|
| 核心创新 | 将成交量分析转化为百分比动量指标,结构与PPO平行 |
| 核心公式 | (EMAfast(Volume)−EMAslow(Volume))/EMAslow(Volume)×100(\mathrm{EMA_{fast}(Volume) - EMA_{slow}(Volume))/EMA_{slow}(Volume)} × 100(EMAfast(Volume)−EMAslow(Volume))/EMAslow(Volume)×100 |
| 三线结构 | PVO主线 + 信号线 + 柱状图 |
| 三大核心信号 | 信号线交叉、零轴穿越、背离预警 |
| 默认参数 | (12, 26, 9) |
| 最佳应用场景 | 价格信号确认、成交量动能判断、趋势验证 |
| 主要局限 | 滞后性、依赖数据质量、需配合价格分析 |
实战使用三原则:
- PVO是确认工具,非独立信号:PVO的核心价值在于验证价格信号的可靠性,而非单独产生买卖信号
- 信号线交叉反映动能变化:金叉表示成交量动能增强,死叉表示动能减弱,这是PVO最灵敏的信号
- 价格与PVO配合使用:价格突破时若PVO同步上升并处于正值区域,突破的可信度显著提高;价格新高而PVO未能新高时,警惕顶背离
最后提醒:PVO的价值在于帮助交易者回答"当前的成交量是否支持价格变动"这一问题。成交量是市场的"燃料",没有成交量支持的价格变动往往难以持续。将PVO与MACD、PPO等价格指标配合使用,可以形成更完整的"价量配合"分析体系。然而,PVO本质上是滞后指标,其信号出现时趋势可能已经运行一段时间。因此,建议将其作为趋势确认工具而非入场触发器使用。