4. 成交量与换手率分析:成交量分布特征、换手率计算与滚动窗口分析、活跃券与非活跃券的识别
各位同学,咱们今天聊一个实操性很强的话题——成交量与换手率。说实话,我刚入行那会儿,觉得利率债流动性分析就是看看成交笔数、算算买卖价差。后来被市场狠狠教育了几次,才明白成交量分布和换手率才是真正的核心指标。
为什么这么说?你想想看,一只债一天成交了100笔,但90笔都集中在开盘后10分钟,这能叫流动性好吗?显然不能。所以,我们不仅要看「量」,还要看「分布」;不仅要算「换手率」,还要用滚动窗口去捕捉它的动态变化。今天我就把这几年的实战经验拆开揉碎了讲给你听。
4.1 成交量分布特征:别只看总量
我个人习惯,拿到一只券的成交数据后,第一件事不是算均值,而是画分布图。为什么?因为成交量分布能告诉你市场的「真实温度」。
举个例子,我曾在分析某只10年国债时发现,它的日均成交量有50亿,看似不错。但拉出日内分布一看,好家伙,80%的成交集中在上午10点到11点之间,下午几乎无人问津。这种「脉冲式」成交,说明这只券的流动性其实很脆弱——一旦你想在下午大额卖出,可能就要承受不小的冲击成本。
成交量分布特征,我一般关注三个维度:
- 日内分布:成交是否集中在特定时段?比如早盘、尾盘。
- 周内分布:周一和周五的成交量是否有显著差异?
- 事件分布:数据发布日、缴税期、季末等特殊时点,成交量是否异常放大或萎缩?
下面这张图,是我自己常用的分析框架,帮你快速理解成交量分布的核心逻辑:
groupby配合dt.hour,可以快速画出日内成交量热力图。我曾经用这个方法发现某只券的「下午2点半效应」——每到这个时间点,成交量就突然放大,后来一查,是某家机构在定时调仓。
4.2 换手率计算与滚动窗口分析
换手率,说白了就是「成交量 / 流通量」。但利率债的流通量怎么定义?这里有个坑。
我记得刚开始做分析时,直接用「发行量」当分母,结果算出来的换手率低得离谱。后来才明白,利率债的「可交易量」远小于发行量——因为大量债券被银行持有至到期、被央行作为抵押品、被保险机构锁仓。所以,我建议用「托管量中剔除持有至到期和质押部分后的余额」作为分母,更贴近真实情况。
换手率的计算公式很简单:
# 换手率计算示例
def calc_turnover_rate(trade_volume, outstanding_amount, holding_to_maturity=0, pledged_amount=0):
"""
计算换手率
:param trade_volume: 当日成交量(面值)
:param outstanding_amount: 发行总量
:param holding_to_maturity: 持有至到期量
:param pledged_amount: 质押量
:return: 换手率(%)
"""
available_amount = outstanding_amount - holding_to_maturity - pledged_amount
if available_amount <= 0:
return 0.0
turnover_rate = trade_volume / available_amount * 100
return round(turnover_rate, 2)
# 举个例子
print(calc_turnover_rate(10e8, 100e8, 30e8, 20e8)) # 输出: 20.0%
但光算一天的换手率意义不大。我更关注的是「滚动窗口换手率」——用过去N天的累计成交量除以流通量。这样能平滑掉单日的异常波动,看到更稳定的趋势。
我一般用20个交易日(约一个月)的滚动窗口:
import pandas as pd
def rolling_turnover_rate(df, window=20):
"""
计算滚动窗口换手率
:param df: DataFrame,包含'date'和'volume'列
:param window: 滚动窗口天数
:return: 添加滚动换手率列的DataFrame
"""
df = df.sort_values('date')
# 假设流通量不变,用第一个有效值
available_amount = df['available_amount'].iloc[0]
# 滚动求和成交量
df['rolling_volume'] = df['volume'].rolling(window=window, min_periods=1).sum()
df['rolling_turnover'] = df['rolling_volume'] / available_amount * 100
return df
# 使用示例
# df = rolling_turnover_rate(df, window=20)
4.3 活跃券与非活跃券的识别
好了,前面铺垫了这么多,终于到核心了——怎么用成交量和换手率来识别活跃券?
说实话,市场上对「活跃券」的定义五花八门。有人看成交笔数,有人看做市商报价宽度,有人看日内价格波动。我个人习惯,用三个指标综合打分:
| 指标 | 计算方式 | 活跃券阈值 | 权重 |
|---|---|---|---|
| 日均成交量 | 过去20日平均成交量 | > 同类券均值 + 1倍标准差 | 40% |
| 滚动换手率 | 过去20日累计成交量 / 可交易量 | > 20% | 35% |
| 成交连续性 | 过去20日中有成交的天数占比 | > 80% | 25% |
这三个指标各有侧重:日均成交量看「体量」,滚动换手率看「周转速度」,成交连续性看「交易频率」。三者结合,基本能筛出真正的活跃券。
我曾在2023年用这套方法做过一次全市场利率债的活跃度排名。结果发现,10年期国债活跃券的滚动换手率通常在30%-50%之间,而一些老券(比如2018年发行的10年国债)换手率不到5%,而且经常连续3-5天没有成交——这种券,说白了就是「僵尸券」,做交易时一定要避开。
下面是一个简单的识别代码:
def identify_active_bonds(df, threshold_volume=None, threshold_turnover=20, threshold_continuity=0.8):
"""
识别活跃券
:param df: DataFrame,包含bond_code, avg_volume, rolling_turnover, trade_days_ratio
:return: 活跃券列表
"""
# 如果没有给定成交量阈值,用均值+1倍标准差
if threshold_volume is None:
mean_vol = df['avg_volume'].mean()
std_vol = df['avg_volume'].std()
threshold_volume = mean_vol + std_vol
# 打分
df['score'] = 0.0
df.loc[df['avg_volume'] > threshold_volume, 'score'] += 40
df.loc[df['rolling_turnover'] > threshold_turnover, 'score'] += 35
df.loc[df['trade_days_ratio'] > threshold_continuity, 'score'] += 25
# 总分超过80分的视为活跃券
active_bonds = df[df['score'] >= 80]['bond_code'].tolist()
return active_bonds
4.4 实战中的几个判断技巧
最后,分享几个我在实战中总结的小技巧,希望能帮你少走弯路:
- 成交量突然放大,不一定是好事。如果伴随价格大幅波动,可能是大机构在调仓或止损,这种「被动成交」不代表流动性好。
- 换手率持续高于30%的券,要警惕「过度交易」。我记得2022年有一只5年国债,换手率一度冲到80%,后来发现是某家机构在做高频交易策略,一旦策略停止,流动性瞬间枯竭。
- 非活跃券也有价值。如果你做配置型策略,非活跃券的收益率通常比活跃券高10-20bp,这就是「流动性溢价」。但前提是你能承受持有期内无法顺利卖出的风险。
- 滚动窗口的周期要随市场调整。震荡市用20日窗口,趋势市可以缩短到10日,极端行情(比如2020年3月)用5日窗口才能捕捉到变化。
嗯,关于成交量与换手率分析,今天就聊到这儿。这套方法我用了好几年,在实盘交易中帮我避开了不少坑。你可以在自己的数据上跑一遍,看看能不能发现一些有意思的规律。
公众号:蓝海资料掘金营,微信deep3321