如何用Python找到股票的支撑位和压力位?——成交量剖面

大家好,我是花姐🎉。

今天我们来聊一个量化交易中非常实用但小白容易忽略的工具------成交量剖面(Volume Profile / Volume by Price)。它能帮我们直观地找到股票的支撑位和压力位,是选股和择时的利器。

本文我会从概念、原理、Python实现到实操策略全方位展开,让你即便是量化小白,也能快速上手。


开始前的准备

我这里用的行情数据源是 xtquant + miniQMT 。 后续示例里会用到一些常见的 Python 库:pandas, numpy, matplotlib,进阶部分还会涉及 scipy, sklearn。在实际运行代码之前,记得先把环境配置好:

bash 复制代码
pip install pandas numpy matplotlib scipy scikit-learn xtquant

这样就能避免因为依赖缺失导致的报错啦。

以下是一个基于xtquant + miniQMT获取股票行情的方法,后面的行情Dataframe数据都会通过这个方法来获取:

python 复制代码
def get_hq(code,start_date='19900101',period='1d',dividend_type='front_ratio',count=-1):
    '''
    基于xtquant下载某个股票的历史行情
    盘中运行最后一个K里存了最新的行情
    period 1d 1w 1mon
    dividend_type - 除权方式,用于K线数据复权计算,对tick等其他周期数据无效
    none 不复权
    front 前复权
    back 后复权
    front_ratio 等比前复权
    back_ratio 等比后复权
    '''
    xtdata.enable_hello = False
    xtdata.download_history_data(stock_code=code, period='1d',incrementally=True)
    history_data = xtdata.get_market_data_ex(['open','high','low','close','volume','amount','preClose','suspendFlag'],[code],period=period,start_time=start_date,count=count,dividend_type=dividend_type)
    print(history_data)
    df = history_data[code]

    df.index = pd.to_datetime(df.index.astype(str), format='%Y%m%d')
    df['date'] = df.index
    return df

一、成交量剖面到底是什么?

简单说,成交量剖面就是把成交量按价格分布绘制出来,而不是像常规K线图那样按时间分布。 换句话说,它告诉你:某个价格区间里有多少人买进卖出过。

为什么重要?

  • 高成交量区域 :意味着价格在这里交易活跃,买卖双方力量相对均衡 → 支撑或压力位
  • 低成交量区域 :意味着价格在这里交易稀少 → 价格可能快速通过 → 突破区间

💡 举个通俗例子:

想象一条小河流,河床上有一些大石头和一些沙子。

  • 大石头:水流冲过去很难,水就容易在石头前堆积,形成涌动的漩涡。
  • 沙子或空地:水流很容易冲过去,没有阻力。

在股市里:

  • 高成交量区就像大石头 → 股价遇到这里容易"卡住",形成支撑或压力
  • 低成交量区就像沙地或空地 → 股价容易快速突破

换句话说,成交量剖面帮你找到河床的"大石头",知道价格哪里容易停、哪里容易流动。


二、成交量剖面的核心指标

  1. POC(Point of Control)

    • 最高成交量对应的价格,通常是支撑或压力最强的位置。
  2. VAH / VAL(Value Area High / Low)

    • 成交量剖面中,覆盖约70%的成交量区间的上下界。
    • VAH → 上轨压力
    • VAL → 下轨支撑
  3. Volume Node(成交量节点)

    • 高低成交量形成的峰谷节点,高峰 → 支撑/压力,低谷 → 突破区

这些概念可能有点抽象,我们就用"成交量剖面"的计算过程来做一次实操演示,帮助大家更直观地理解整个流程。

三、成交量剖面计算步骤

步骤 1:原始数据

每一天我们有如下行情数据:

日期 开盘价 最高价 最低价 收盘价 成交量
2025-09-11 10 12 9 11 1000

步骤 2:划分价格区间

假设整个历史区间最低价 8,最高价 14,我们将价格区间分成 6 个区间(简化示例):

csharp 复制代码
价格区间:
[8-9), [9-10), [10-11), [11-12), [12-13), [13-14]

步骤 3:把每一天的价格范围切分小段

比如2025-09-11这一天,最高价 12,最低价 9,把这个区间切成 4 小段(steps_per_day = 4):

复制代码
小段价格:
9.0, 9.75, 10.5, 11.25, 12.0

每个小段的成交量 = 当天成交量 ÷ 小段数量 = 1000 ÷ 4 = 250


步骤 4:映射到价格区间

将每个小段价格对应到上面划分的价格区间:

小段价格 对应区间
9.0 [9-10)
9.75 [9-10)
10.5 [10-11)
11.25 [11-12)

然后把每个小段的成交量累加到对应区间:

区间 成交量
[8-9) 0
[9-10) 500
[10-11) 250
[11-12) 250
[12-13) 0
[13-14) 0

步骤 5:对所有天累加

每天都做同样的操作,把所有小段成交量累加到对应区间,得到最终的成交量剖面:

yaml 复制代码
区间成交量柱状图(横向):
[8-9): 200, [9-10): 1800, [10-11): 2300, [11-12): 1700, [12-13): 900, [13-14): 300

对,你理解得很对。步骤5以后,成交量剖面的计算已经完成,本质上就是把每日的小段成交量累加到每个价格区间 ,得到一个完整的成交量分布图(横向柱状图)。这个阶段应该是用来得出结论和分析的,而不是再做计算。


步骤 6:结论

  1. 高成交量区(HVN, High Volume Node)

    • 柱子最高的几个价格区([10-11): 2300),说明市场最活跃。
    • 通常对应支撑/压力位的潜在区域。
  2. 低成交量区(LVN, Low Volume Node)

    • 柱子很低的价格区([8-9): 200),说明市场交易稀少。
    • 价格穿越这些区域可能会很快,因为阻力/支撑弱("空档区")。
  3. 支撑/压力分析

    • 当前价格在高成交量区上方 → 高成交量区可能形成支撑。
    • 当前价格在高成交量区下方 → 高成交量区可能形成压力。
  4. 市场心理与策略参考

    • 高成交量区 = 市场认可的价格 → 量化策略可在此设置止盈/止损或建仓区间。
    • 低成交量区 = 市场不认可的价格 → 突破可能性大,可作为突破策略参考。

所以在步骤5完成后,结论就是:

"根据成交量剖面,价格在 [10-11): 2300 区间的成交量最高,是市场最关注的区域,可能形成支撑或压力;而 [8-9): 200 区间成交量最低,价格可能快速穿越。"


三、Python实操:计算+绘制成交量剖面

示例代码如下:

python 复制代码
from xtquant import xtdata
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import mplfinance as mpf

def get_hq(code,start_date='19900101',period='1d',dividend_type='front_ratio',count=-1):
    '''
    基于xtquant下载某个股票的历史行情
    盘中运行最后一个K里存了最新的行情
    period 1d 1w 1mon
    dividend_type - 除权方式,用于K线数据复权计算,对tick等其他周期数据无效
    none 不复权
    front 前复权
    back 后复权
    front_ratio 等比前复权
    back_ratio 等比后复权
    '''
    xtdata.enable_hello = False
    xtdata.download_history_data(stock_code=code, period='1d',incrementally=True)
    history_data = xtdata.get_market_data_ex(['open','high','low','close','volume','amount','preClose','suspendFlag'],[code],period=period,start_time=start_date,count=count,dividend_type=dividend_type)
    df = history_data[code]

    df.index = pd.to_datetime(df.index.astype(str), format='%Y%m%d')
    df['date'] = df.index
    return df


def compute_volume_profile(df, price_bins=50, steps_per_day=10):
    """
    计算成交量剖面
    参数:
        df : DataFrame, 必须包含 ['date', 'open', 'high', 'low', 'close', 'volume']
        price_bins : int, 成交量剖面的价格区间数量
        steps_per_day : int, 每日高低价划分的小段数量
    返回:
        volume_profile : np.array, 每个价格区间的成交量
        bins : np.array, 价格区间边界
    """
    df = df.copy()
    
    # 1. 设置价格区间
    price_min = df['low'].min()
    price_max = df['high'].max()
    bins = np.linspace(price_min, price_max, price_bins)
    
    # 2. 计算每个价格区间的成交量
    volume_profile = np.zeros(len(bins)-1)
    for i in range(len(df)):
        price_range = np.linspace(df['low'].iloc[i], df['high'].iloc[i], steps_per_day)
        vol_per_step = df['volume'].iloc[i] / len(price_range)
        idx = np.digitize(price_range, bins) - 1
        for j in idx:
            if 0 <= j < len(volume_profile):
                volume_profile[j] += vol_per_step
                
    return volume_profile, bins

def plot_volume_profile(volume_profile, bins):
    """
    绘制成交量剖面
    """
    plt.rcParams['font.sans-serif'] = ['SimHei']  
    plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
    plt.figure(figsize=(16,9))
    plt.barh((bins[:-1]+bins[1:])/2, volume_profile, height=(bins[1]-bins[0]), color=plt.cm.viridis(volume_profile/volume_profile.max()))
    plt.xlabel('成交量')
    plt.ylabel('价格')
    plt.title('成交量剖面')
    plt.show()
    
def plot_kline_with_support_resistance(df, volume_profile, bins, top_n=5):
    """
    在K线图上标记潜在支撑/压力位
    参数:
        df : DataFrame, 包含 ['date','open','high','low','close','volume']
        volume_profile : np.array, 成交量剖面
        bins : np.array, 价格区间
        top_n : int, 标记成交量最高的前N个价格区间
    """
    # 设置K线样式
    mc = mpf.make_marketcolors(up='r', down='g', inherit=True)
    s = mpf.make_mpf_style(marketcolors=mc)
    
    # 找成交量top_n价格区
    top_idx = np.argsort(volume_profile)[-top_n:]
    bottom_idx = np.argsort(volume_profile)[:top_n]
    support_resistance_lines = [(bins[idx] + bins[idx+1])/2 for idx in top_idx]
    
    # 添加水平线
    hlines = dict(hlines=support_resistance_lines, colors=['orange']*len(support_resistance_lines),
                  linestyle='--', linewidths=2, alpha=1)
    
    # 绘制K线+成交量+支撑/压力位
    mpf.plot(df, type='candle', style=s, volume=True, hlines=hlines)
    
    

if __name__ == "__main__":
    code = '600519.SH'  # 贵州茅台
    df = get_hq(code, start_date='20200101', period='1d', count=200)
    volume_profile, bins = compute_volume_profile(df, price_bins=20, steps_per_day=4)
    
    plot_volume_profile(volume_profile, bins)
    plot_kline_with_support_resistance(df, volume_profile, bins, top_n=1)
    

茅台的成交量不是特别集中,看起来不够明显,我换了一个近期放量的股票同样绘制了2个图


四、量化分析支撑/压力位的方法

1. POC法

python 复制代码
poc_index = np.argmax(volume_profile)
poc_price = (bins[poc_index] + bins[poc_index+1]) / 2
print(f"POC支撑/压力位: {poc_price}")

POC就是成交量最多的价格,通常是股价反弹或回落的关键点。

2. VAH/VAL法

python 复制代码
total_vol = volume_profile.sum()
cum_vol = np.cumsum(volume_profile)
val_index = np.where(cum_vol <= 0.15*total_vol)[0][-1]
vah_index = np.where(cum_vol <= 0.85*total_vol)[0][-1]
val_price = (bins[val_index] + bins[val_index+1])/2
vah_price = (bins[vah_index] + bins[vah_index+1])/2
print(f"支撑位 VAL: {val_price}, 压力位 VAH: {vah_price}")

这里我们用15%-85%的累积成交量来定义区间,覆盖约70%的交易量,比较稳妥。


五、策略思路与实战小技巧

  1. 短线反弹策略

    • 股价跌到VAL附近 → 可考虑买入
    • 股价涨到VAH附近 → 可考虑卖出或减仓
  2. 突破策略

    • 股价突破低成交量区 → 跟随趋势进场
    • 高成交量区突破 → 谨慎,可能是假突破
  3. 多时间周期分析

    • 日线成交量剖面 → 中期趋势
    • 周线成交量剖面 → 长期支撑压力
    • 同时参考,支撑/压力更可靠
  4. 成交量剖面结合均线/指标

    • 例如POC附近碰到20日均线 → 支撑更强
    • 形成"成交量+价格+趋势"的三维支撑/压力分析

六、总结

成交量剖面是量化交易里非常直观的支撑压力分析工具。 核心思路很简单:

  • 高成交量区 → 支撑或压力
  • 低成交量区 → 价格容易突破

Python实现也不复杂,通过分区累积成交量就能画出图,并快速计算POC、VAH、VAL,辅助策略决策。

今天的文章就到这里了,下一篇我们讲通过VWAP / Anchored VWAP(成交量加权价格) 找到压力与支撑。

相关推荐
minh_coo3 小时前
Spring框架接口之RequestBodyAdvice和ResponseBodyAdvice
java·后端·spring·intellij idea
AI量化投资实验室3 小时前
今日策略:年化436%,回撤7%,夏普比5.28, deap因子挖掘重构,附python代码
开发语言·python·重构
吾当每日三饮五升3 小时前
RapidJSON 自定义内存分配器详解与实战
c++·后端·性能优化·json
shark_chili3 小时前
Linux IO性能瓶颈排查全攻略:从理论到实战的系统性解决方案
后端
一粒马豆3 小时前
excel表格通过前端fetch上传至后端flask处理流程示例
前端·python·flask·excel·h5·js·fetch
Q_Q5110082853 小时前
python+django/flask哈利波特书影音互动科普网站
spring boot·python·django·flask·node.js·php
卷福同学3 小时前
#去深圳了~
后端·面试·架构
CodeSheep3 小时前
稚晖君公司最新合伙人,公开了!
前端·后端·程序员
绝无仅有3 小时前
面试MySQL基础20题(一)
后端·面试·github