4、成交量分布:Volume Profile概念、TPO图绘制、价值区域(VA)计算、POC识别

成交量分布,英文叫 Volume Profile。说实话,这玩意儿是我入行第三年才真正搞明白的。之前一直用传统成交量,觉得够用了。直到有一次做股指期货的日内策略,怎么调参数都不对劲。后来一个老交易员跟我说:「你试试看价格在哪个位置成交最多,而不是单纯看总量。」

嗯,这句话点醒了我。传统成交量告诉你「今天成交了多少」,而 Volume Profile 告诉你「在什么价格成交了多少」。这个区别,说白了就是维度上的降维打击。

4.1 Volume Profile 的核心概念

Volume Profile 的核心思想很简单:把成交量按价格水平分布开来。你想想看,如果某个价格区间成交了巨量,说明多空双方在这个位置博弈激烈。这个位置要么是强支撑,要么是强阻力。

我个人习惯把 Volume Profile 拆成三个核心要素:

  • TPO(Time Price Opportunity):时间-价格-机会。每个时间周期内,价格扫过的区间,我们用一个字母标记。比如9:30-9:31这一分钟,价格从100.1到100.5,那就在这些价格上标记一个字母。
  • 价值区域(Value Area, VA):成交量最集中的那个价格带。通常占全天成交量的68%-70%。
  • POC(Point of Control):成交量最大的那个单一价格。这是整个分布图的「重心」。

核心公式:

价值区域上沿 = 找到累计成交量达到总成交量70%的价格区间上限

价值区域下沿 = 找到累计成交量达到总成交量70%的价格区间下限

POC = argmax(成交量) 在价格维度上

4.2 TPO图绘制:从零开始

TPO图,说白了就是一张「价格-时间」的热力图。每个时间切片,价格扫过的位置就画一个字母。我最早在期货公司实习时,老交易员都是手画TPO图的,一张A3纸画得密密麻麻。

现在当然用Python了。我来给你看看核心代码:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

def build_tpo_chart(df, price_col='close', time_col='datetime', bucket_size=0.1):
    """
    构建TPO图
    df: 包含时间和价格的DataFrame
    bucket_size: 价格桶大小,比如0.1表示每0.1元一个桶
    """
    # 价格分桶
    price_min = df[price_col].min()
    price_max = df[price_col].max()
    price_bins = np.arange(price_min, price_max + bucket_size, bucket_size)
    
    # 时间切片(每30分钟一个切片)
    df['time_slice'] = df[time_col].dt.floor('30min')
    time_slices = df['time_slice'].unique()
    
    # 构建TPO矩阵
    tpo_matrix = {}
    for idx, row in df.iterrows():
        price_bucket = int((row[price_col] - price_min) / bucket_size)
        time_idx = np.where(time_slices == row['time_slice'])[0][0]
        
        if price_bucket not in tpo_matrix:
            tpo_matrix[price_bucket] = []
        tpo_matrix[price_bucket].append(chr(65 + time_idx % 26))  # 用字母标记
    
    return tpo_matrix, price_bins, time_slices

# 使用示例
# tpo_matrix, price_bins, time_slices = build_tpo_chart(data)
# 然后绘制热力图

我的经验:TPO图的字母数量不要超过26个,否则字母不够用。我一般把一天分成13个30分钟切片,用A-M标记上午,N-Z标记下午。这样一眼就能看出价格在上午还是下午活跃。

4.3 价值区域(VA)计算:数学与直觉

价值区域的计算,本质上是一个「找重心」的过程。我见过很多新手直接取成交量最大的70%区间,这是不对的。正确的做法是:从POC开始,向两边扩展,直到累计成交量达到总成交量的70%。

为什么从POC开始?因为POC是博弈最激烈的位置,价值区域应该以它为中心。我曾经踩过这个坑,直接取中间70%的区间,结果算出来的VA完全偏离实际支撑位。

def calculate_value_area(volume_profile, total_volume, target_pct=0.70):
    """
    计算价值区域
    volume_profile: dict, key=价格, value=成交量
    total_volume: 总成交量
    target_pct: 目标占比,默认70%
    """
    # 找到POC
    poc_price = max(volume_profile, key=volume_profile.get)
    poc_volume = volume_profile[poc_price]
    
    # 从POC向两边扩展
    sorted_prices = sorted(volume_profile.keys())
    poc_idx = sorted_prices.index(poc_price)
    
    cum_volume = poc_volume
    target_volume = total_volume * target_pct
    
    left_idx = poc_idx
    right_idx = poc_idx
    
    while cum_volume < target_volume:
        # 向左扩展
        if left_idx > 0:
            left_idx -= 1
            cum_volume += volume_profile[sorted_prices[left_idx]]
        
        # 如果还不够,向右扩展
        if cum_volume < target_volume and right_idx < len(sorted_prices) - 1:
            right_idx += 1
            cum_volume += volume_profile[sorted_prices[right_idx]]
        
        # 边界保护
        if left_idx == 0 and right_idx == len(sorted_prices) - 1:
            break
    
    va_high = sorted_prices[right_idx]
    va_low = sorted_prices[left_idx]
    
    return va_low, va_high, poc_price

避坑指南:我曾经在计算VA时忘记处理边界情况。如果价格分布非常极端,比如某只股票全天都在一个窄幅区间内成交,那么VA可能会覆盖整个区间。这时候要设置一个最小宽度阈值,比如至少覆盖3个价格桶。

4.4 POC识别:找到市场的「重心」

POC,Point of Control,就是成交量最大的那个价格。听起来简单,但实际应用中有几个细节要注意。

第一,POC可能不止一个。如果两个价格成交量非常接近,就会出现「双POC」的情况。这时候我会取它们的平均值作为参考。

第二,POC是动态的。随着新数据的加入,POC会移动。我习惯用滚动窗口来计算POC,比如过去20个交易日的POC,这样能捕捉到市场重心的变化。

def identify_poc(volume_profile, multi_poc_threshold=0.95):
    """
    识别POC,支持多POC检测
    volume_profile: dict, key=价格, value=成交量
    multi_poc_threshold: 多POC阈值,成交量达到最大值的95%也算POC
    """
    max_volume = max(volume_profile.values())
    poc_candidates = []
    
    for price, volume in volume_profile.items():
        if volume >= max_volume * multi_poc_threshold:
            poc_candidates.append(price)
    
    if len(poc_candidates) == 1:
        return poc_candidates[0], poc_candidates
    else:
        # 多POC情况,返回加权平均和候选列表
        weighted_poc = sum(poc_candidates) / len(poc_candidates)
        return weighted_poc, poc_candidates

4.5 知识体系总览

下面这张图是我自己整理的Volume Profile知识体系,你可以把它当作一个快速索引:

Volume Profile 知识体系 Volume Profile TPO 图绘制 价值区域(VA) POC 识别 时间切片 价格分桶 POC为中心扩展 70%累计成交量 最大成交量价格 多POC检测 实战应用:支撑阻力识别 · 突破确认 · 订单流分析 核心逻辑:从价格-时间-成交量三维视角,找到市场的真实博弈区间

4.6 实战中的几个坑

讲完了理论,我来聊聊实战中容易踩的坑。这些可都是真金白银换来的教训。

  1. 数据频率问题:TPO图对数据频率很敏感。用1分钟数据和用5分钟数据,画出来的图差别很大。我个人习惯用1分钟数据做日内分析,用30分钟数据做日线级别分析。
  2. 价格桶大小:桶太大,细节丢失;桶太小,噪声太多。我一般用ATR(平均真实波幅)的1/10作为桶大小。比如ATR=2.0,那桶大小就是0.2。
  3. VA的时效性:价值区域不是一成不变的。开盘第一个小时的VA和收盘前最后一个小时的VA,意义完全不同。我习惯分时段计算VA,比如早盘VA、午盘VA、尾盘VA。
  4. POC的假突破:价格突破POC不代表趋势确立。我见过太多假突破了。我的经验是:价格突破POC后,需要在该位置停留至少3根K线,才算有效突破。

一个小技巧:把Volume Profile和传统成交量结合起来看。如果Volume Profile显示POC在某个价格,同时传统成交量也在放大,那这个位置的支撑/阻力强度会更高。我管这叫「双重确认」。

4.7 代码实战:完整流程

最后,我给你一个完整的实战流程。假设你有一份分钟级别的期货数据,你想画出当天的Volume Profile:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 1. 加载数据
df = pd.read_csv('future_minute_data.csv', parse_dates=['datetime'])
df.set_index('datetime', inplace=True)

# 2. 计算价格桶
price_min = df['close'].min()
price_max = df['close'].max()
bucket_size = 0.2  # 根据ATR调整
price_bins = np.arange(price_min, price_max + bucket_size, bucket_size)

# 3. 构建成交量分布
volume_profile = {}
for i in range(len(price_bins) - 1):
    mask = (df['close'] >= price_bins[i]) & (df['close'] < price_bins[i+1])
    volume_profile[price_bins[i]] = df.loc[mask, 'volume'].sum()

# 4. 计算VA和POC
total_volume = df['volume'].sum()
va_low, va_high, poc = calculate_value_area(volume_profile, total_volume)

# 5. 可视化
plt.figure(figsize=(10, 8))
prices = list(volume_profile.keys())
volumes = list(volume_profile.values())

plt.barh(prices, volumes, height=bucket_size*0.8, color='steelblue', alpha=0.7)
plt.axhline(y=poc, color='red', linestyle='--', linewidth=2, label=f'POC: {poc:.2f}')
plt.axhline(y=va_low, color='green', linestyle=':', linewidth=1.5, label=f'VA Low: {va_low:.2f}')
plt.axhline(y=va_high, color='green', linestyle=':', linewidth=1.5, label=f'VA High: {va_high:.2f}')

plt.xlabel('成交量')
plt.ylabel('价格')
plt.title('Volume Profile - 成交量分布图')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

print(f"POC: {poc:.2f}")
print(f"价值区域: {va_low:.2f} - {va_high:.2f}")
print(f"价值区域宽度: {va_high - va_low:.2f}")

嗯,代码跑起来之后,你会看到一张横向的柱状图。红色虚线是POC,绿色虚线是价值区域的上下沿。一眼就能看出今天市场的主要博弈区间在哪里。

我记得有一次用这个代码分析螺纹钢期货,发现POC在3800附近,但价格一直在3780-3820之间震荡。按照Volume Profile的逻辑,这个区间就是「价值区域」,价格大概率会在这里反复。果然,那天价格就在这个区间内来回走了4个小时,直到尾盘才突破。如果你当时追涨杀跌,大概率会被来回打脸。

这就是Volume Profile的价值——它告诉你市场真正的「舒适区」在哪里。价格在价值区域内,别瞎动;价格突破价值区域,才是真正的机会。


公众号:蓝海资料掘金营,微信deep3321