2. 成交量分层与股票池构建

上一节我们明确了执行成本的构成,也看到成交量是决定冲击成本与滑点大小的关键变量。但“成交量大小”不能凭感觉判断,必须落到可量化、可复现的分层标准上。本节要解决两个问题:用什么指标把股票按成交量分层,以及如何实操构建高、中、低成交量三组可对比的股票池。只有股票池构建得干净、可比,后续的执行成本对比才有意义。

一、分层指标:为什么不能只看成交额

最直观的指标是日均成交额,但它有一个明显缺陷:受股价影响大。一只高价股即使换手很低,成交额也可能很大;一只低价股换手活跃,成交额却未必高。因此实务中通常采用双指标交叉:

  • 日均成交额(ADV):过去 N 个交易日成交金额的均值,反映“资金容量”,直接对应一笔订单相对市场的占比。常用 N=20 或 60。
  • 日均换手率:成交量 ÷ 流通股本,反映“交易活跃度”,剔除股价与股本规模的干扰。
  • 辅助过滤:上市天数、ST 状态、停牌天数、涨跌停频率,用于剔除不可交易或数据失真的样本。

这里要强调一个参照约定:本节所有“日均”均指截至分层基准日往前推 N 个交易日的算术平均,基准日之后的数据一律不参与分层,避免引入未来信息。这一点在回测类研究中尤其重要,否则股票池会带有前视偏差。

二、分层方法:分位数切分而非固定阈值

固定阈值(如“日成交额 10 亿以上算高量”)的问题是会随市场整体水位漂移——牛市里 10 亿很普通,熊市里 10 亿已是头部。更稳健的做法是横截面分位数切分:在每个基准日,把满足过滤条件的全市场股票按 ADV 排序,取前 30% 为高成交量组、中间 40% 为中成交量组、后 30% 为低成交量组。换手率可作为二次校验,观察分组内换手分布是否合理。

下面的图示展示了从原始行情数据到三组股票池的完整构建链路,包括过滤、排序、切分与校验四个环节,以及各环节的关键参数:

① 原始行情数据 日线:成交额/成交量 流通股本、上市日期 停牌与涨跌停标记 ② 样本过滤 上市 ≥ 250 交易日 剔除 ST / 退市风险 近 20 日停牌 ≤ 2 天 ③ 计算分层指标 ADV20 = 近20日均额 换手率 = 量 ÷ 流通股 基准日:T,仅用 T 前数据 ④ 横截面切分 按 ADV 排序 前30% / 中40% / 后30% 高成交量组(前30%) ADV20 分位 ≥ 70% 示例:ADV20 > 8 亿元 换手率中位数约 1.5% 冲击成本低、滑点小 适合大单分批建仓 中成交量组(中40%) ADV20 分位 30%–70% 示例:1.5 亿–8 亿元 换手率中位数约 2.5% 成本居中、需控制节奏 适合算法拆单执行 低成交量组(后30%) ADV20 分位 ≤ 30% 示例:ADV20 < 1.5 亿元 换手率中位数约 3.5% 冲击成本高、滑点大 小单也易推动价格 ⑤ 分组校验(构建后必做) 检查三组 ADV 中位数是否单调递减 · 组内换手率分布是否重叠过大 · 每组样本数是否 ≥ 30 只 若中位数差距不足 3 倍,说明分层区分度不够,需调整分位阈值或延长 ADV 窗口

三、实操:用一段可复现的流程构建股票池

下面给出一个基于日线数据的构建流程示例。假设我们使用某 A 股全市场日线表 daily,字段包括 code, trade_date, amount, vol, float_share, is_st, list_date, is_suspend,基准日为 2024-06-28,ADV 窗口 N=20。以下为伪代码,参数与结果均为教学示例,非实测数据。

# 1. 取基准日前 20 个交易日窗口
window = trade_days_before("2024-06-28", n=20)

# 2. 过滤:上市满250日、非ST、窗口内停牌≤2天
df = daily[daily.trade_date.isin(window)]
df = df[df.list_date <= "2023-06-30"]
df = df[~df.is_st]
suspend_cnt = df.groupby("code").is_suspend.sum()
valid_codes = suspend_cnt[suspend_cnt <= 2].index

# 3. 计算 ADV20 与日均换手率
g = df[df.code.isin(valid_codes)].groupby("code")
adv20 = g.amount.mean()                      # 单位:元
turnover = (g.vol.mean() / g.float_share.mean()) * 100  # 百分比

# 4. 横截面分位切分
adv_rank = adv20.rank(pct=True)
high = adv_rank[adv_rank >= 0.70].index
mid  = adv_rank[(adv_rank > 0.30) & (adv_rank < 0.70)].index
low  = adv_rank[adv_rank <= 0.30].index

运行后得到三组代码列表。以示例数据为例,可能得到高量组 ADV 中位数约 12 亿元、中量组约 3.5 亿元、低量组约 0.8 亿元,三组中位数比值约为 15:4:1,区分度充分。若发现中量组与低量组中位数仅差 1.5 倍,说明市场成交额分布偏集中,此时可考虑改用换手率作为主排序指标,或把分位阈值调整为 25/50/25。

四、构建股票池时必须避开的三个坑

第一,幸存者偏差。如果只取当前仍在上市的股票,历史上退市或长期停牌的样本被系统性剔除,低成交量组会偏“干净”,低估真实执行成本。正确做法是按基准日时点的全样本构建,而不是用今天的股票列表回溯。

第二,分组不稳定。成交量分层若每日重算,个股会在组间频繁跳变,导致后续成本对比的样本不一致。实务中通常按季度或半年度重构股票池,组内成员在持有期内固定,这样每组执行成本的统计才有稳定口径。

第三,忽略组内换手差异。高成交量组里可能混入“低价高换手”的题材股,其换手率远高于同组蓝筹,冲击成本特征并不相同。建议在每组内再按换手率做一次简单的中位数检查,若组内换手率四分位距过大,应拆分或标注,避免把不同微观结构的股票混为一谈。