
本文详解如何使用 pandas 正确计算成交量加权的控制点(point of control, poc),即市场剖面中累计成交量最大的价格水平,并支持静态全局poc与滚动时间窗口poc两种实现方式。
本文详解如何使用 pandas 正确计算成交量加权的控制点(point of control, poc),即市场剖面中累计成交量最大的价格水平,并支持静态全局poc与滚动时间窗口poc两种实现方式。
在交易分析与市场剖面(Market Profile)建模中,控制点(Point of Control, POC) 是核心指标之一,定义为指定时间段内成交量最大的价格水平(而非出现频次最多的收盘价)。原始方法仅对 close 列求众数(mode),忽略了每笔成交对应的 volume 差异——例如同一价格上 10 笔 × 1 手 vs. 1 笔 × 10 手,后者实际市场意义更重。因此,必须基于 价格分组 + 成交量加总 + 取最大值索引 的逻辑实现。
✅ 正确计算静态POC:按价格聚合总成交量
关键在于:以 close(或更严谨的 price 字段,如 bid_price/ask_price)为分组键,对 volume 求和,再取总和最大的价格:
import pandas as pd
def calculate_poc(df):
"""
计算全局静态POC:返回成交量最大的价格水平
参数:
df: 包含 'close' 和 'volume' 列的DataFrame
返回:
float: POC价格值;若无数据则返回None
"""
if df.empty or 'close' not in df.columns or 'volume' not in df.columns:
return None
volume_by_price = df.groupby('close')['volume'].sum()
return volume_by_price.idxmax() if not volume_by_price.empty else None
# 示例数据构建(模拟高频逐笔成交)
data = {
'timestamp': ["2024-02-05 01:00:01.383985+00:00"] * 18 +
["2024-02-05 01:00:01.385840+00:00"] * 4,
'close': [4968.5] * 22,
'security_code': ['ES'] * 22,
'volume': [1] * 18 + [2, 1, 1, 1], # 后4笔中首笔volume=2,其余为1
'bid_volume': [1] * 18 + [0] * 4,
'ask_volume': [0] * 18 + [2, 1, 1, 1]
}
df = pd.DataFrame(data)
poc = calculate_poc(df)
print(f"全局POC价格: {poc}") # 输出: 全局POC价格: 4968.5
⚠️ 注意事项:
- 若存在多个价格并列最高成交量,idxmax() 默认返回第一个;如需全部,可改用 volume_by_price[volume_by_price == volume_by_price.max()].index.tolist()。
- 实际场景中建议使用 round(close, decimals) 对价格做合理精度截断(如期货报价常为0.25步长),避免浮点误差导致同一价位被拆分为多个近似值。
? 扩展:滚动时间窗口POC(Rolling POC)
静态POC反映整体区间特征,而滚动POC能捕捉市场焦点的动态迁移。Pandas 原生 rolling 不支持按价格分组聚合,需手动切片实现:
def rolling_poc(df, window='5T', price_col='close', volume_col='volume'):
"""
计算滚动时间窗口POC(推荐使用pd.Grouper替代手动循环)
更高效写法(推荐):
"""
if df.index.dtype != 'datetime64[ns]':
raise ValueError("DataFrame索引必须为datetime类型")
# 使用resample + groupby实现向量化滚动(按窗口重采样后分组)
# 注意:此处采用左闭右开窗口,且每个窗口独立计算POC
poc_series = (
df
.groupby(pd.Grouper(freq=window, origin='start')) # 按固定频率分桶
.apply(lambda x: x.groupby(price_col)[volume_col].sum().idxmax()
if not x.empty else None)
.rename('poc')
)
# 将结果映射回原始索引(前向填充至窗口结束)
result = pd.Series(index=df.index, dtype='float64')
for idx, poc_val in poc_series.items():
mask = (df.index >= idx) & (df.index <p>✅ <strong>推荐优化方案</strong>:利用 pd.Grouper + apply 实现简洁、可读性强的滚动逻辑,避免显式 while 循环,提升性能与可维护性。</p><h3>? 总结</h3>
- POC本质是成交量加权模式,绝非简单价格众数,必须 groupby(price).volume.sum().idxmax()。
- 静态POC适用于复盘整段行情;滚动POC适用于实时监控市场重心转移,窗口大小需结合品种流动性与数据频率设定(如股指期货常用1–5分钟)。
- 生产环境中建议增加异常处理(空数据、NaN价格、volume为负等),并考虑使用 numpy.select 或 pd.cut 对价格进行离散化分箱,提升计算稳定性。
掌握这一计算逻辑,是构建专业级市场剖面分析工具链的关键一步。











