先说我的判断:做股票研究的人,很多都踩过数据整理的坑。某券商研报里的数据、某个交易软件的指标,口径经常对不上,回测一跑全乱套。换手率这个指标看似简单——成交量除以流通股本,但真要拉一个1990年到2024年、覆盖所有上市企业的完整序列,中间全是细节。我前阵子刚好整理完一份这个时间跨度的换手率数据,把全过程记录下来,给准备自己做数据的同行一个参考。
因为每只股票的上市日期不同,所以这个数据集不是简单的"全市场34年逐日面板",而是按各自的上市时间截断。我统计了一下,覆盖了5300多只A股标的,最早的是1990年12月首批挂牌的那8只,最晚的是2024年12月刚上市的次新股。单看时间跨度可能没感觉,但你要想:1990年那会儿,全市场一天的成交额可能还不如现在一只中盘股一分钟的成交额。数据跨度拉这么长,最大的挑战不是"有没有数据",而是"数据的口径在34年里变了好几次"。
1. 换手率数据为什么值得单独做一版:不是简单算个除法那么简单
先把这个指标本身说透。换手率的标准公式是当日成交量除以当日流通股本,代表"这只股票有多少比例的流通筹码在今天换了主人"。听起来简单,但作为研究指标,它有几个别的指标替代不了的作用。
第一,换手率是判断"股票活跃度"最直接的代理变量。成交额会被股价放大——茅台涨到1700块,一手就17万,成交额看着吓人,但实际换手的股数可能并不多。换手率剔除了股价绝对水平的影响,直接反映市场参与者对这个标的的关注度。同样是5个亿的成交额,一只5块钱的票和一只200块钱的票,换手率能差出十几倍,市场意义完全不一样。
第二,换手率是许多因子模型里"流动性因子"的原材料。异质波动率、Amihud非流动性指标、个股波动率、最大日收益率这些经典量价因子,构建的时候基本都要用换手率做基准或者做过滤条件。没有一套干净的历史换手率序列,这些因子全都跑不了。
第三,换手率在行为金融研究里是"投资者情绪"的度量。A股市场散户占比高,换手率长期高于成熟市场,这里面既有制度因素,也有情绪因素。研究市场情绪周期、波动聚集效应、崩盘风险,换手率都是绕不开的变量。
但关键问题来了:从Wind、同花顺、Tushare这些终端直接导出的换手率数据,往往看着能用,实际上隐患很多。最大的问题是历史口径不一致。早年交易所披露的流通股本不是现在这个定义,中间经历过股权分置改革、限售股解禁、机构配售股上市,每一次股本结构变化,换手率序列都会出现"断裂式跳变"。你如果不处理这些跳变,直接拿去做回测,策略很可能在股改完成、大盘股集中解禁的那几个时间点出现莫名其妙的异常收益,查了半天不知道问题出在哪。
所以我做这个数据集时,给自己定的标准是三条:覆盖全、口径统一、可复现。覆盖全意味着1990年至今A股所有上市企业的所有交易日都要有;口径统一意味着不管历史上股本结构怎么变,换手率的计算基准都要校正到同一套规则;可复现意味着拿到数据的人能清楚地知道每一列是怎么算出来的,不会一遇到异常值就抓瞎。
2. 数据集的字段结构和时间口径:拿到的每一列都有出处
很多人下载完数据直接就开跑,根本不看字段说明,结果拿"总股本换手率"当"流通股本换手率"用,写论文被审稿人一问就露馅。我这里把最终交付的字段结构完整列出来,每一列为什么存在、怎么算的,都交代清楚。
| 字段名 | 含义 | 计算说明 |
|---|---|---|
| symbol | 证券代码 | 6位数字代码,不带市场后缀(如需区分沪深可自行拼接.SH/.SZ) |
| trade_date | 交易日期 | YYYY-MM-DD格式,只保留实际交易日,非交易日不在序列中 |
| short_name | 股票简称 | 该交易日的简称快照,含ST、*ST标识 |
| industry | 所属行业 | 按申万一级行业分类,用于分组统计 |
| turnover_rate | 日换手率 | 当日成交量/当日流通股本×100%,注意是"流通股本"口径 |
| turnover_rate_total | 总股本换手率 | 当日成交量/当日总股本×100%,用于对比分析 |
| volume | 成交量 | 当日成交股数(手×100),单位:股 |
| amount | 成交额 | 当日成交金额,单位:元 |
| circ_mv | 流通市值 | 当日流通股本×当日收盘价,单位:元 |
| total_mv | 总市值 | 当日总股本×当日收盘价,单位:元 |
| listing_date | 上市日期 | 用于识别次新股区间 |
| is_st | 是否ST | 1=ST或*ST,0=正常交易,辅助过滤极端样本 |
把两个换手率口径同时放进去,是故意为之。我见过一个做得挺漂亮的横截面研究,用的"换手率"数据其实混着两种口径——部分股票是流通股本算的,部分因为数据源缺失用了总股本补齐,结果因子分析里出现了严重的分组误差。所以我在交付时干脆两个口径都给,宁可让用户自己选,也不能替他做决定。
时间口径上有一个容易忽略的点:早年的交易制度跟现在不一样,1990-1995年那会儿没有严格的10%涨跌停限制,也没有T+1制度(最早是T+0),所以最早的换手率天然偏高。这不算数据错误,但做跨时期对比时必须知道这个制度背景,否则你会以为1993年的市场疯掉了,其实是规则不同。
上市区间的处理逻辑是这样:每只股票从上市日次日开始纳入数据,上市首日本身不纳入。原因是A股新股上市首日不设涨跌幅限制(注册制前是44%涨停限制),首日换手率动辄百分之七八十甚至更高,跟正常交易日完全不是一个分布,放进日度面板里会严重扰动横截面统计量。但如果用户需要研究"炒新股"现象,我把上市日期字段提供了,自己过滤一下就能把首日数据捞回来。
3. 数据清洗和口径校正:三分靠采集,七分靠对账
3.1 从公开数据源拉取到统一入库的完整链路
我处理数据主要依赖Tushare Pro的接口和聚宽、米筐等平台的历史快照,再配合CSMAR和Wind的抽查校验。流程通常是这样的:
先用Tushare Pro的daily_basic接口拉全部历史日线,这个接口自带换手率字段(turnover_rate),但它给的是流通股本口径,而且早年数据存在大量缺失。再把聚宽的行情数据和米筐的复权因子数据跑一遍,把缺失的部分补上来。最后用CSMAR的数据库做抽样比对,重点抽查1997-2005年这段数据质量最不稳定的区间。
整个流程跑完大约需要几个通宵的机器时间,但真正麻烦的不是拉数,是拉完之后的对账。我按年份抽样,每年随机抽10只股票,把从Tushare、聚宽、CSMAR三个源拿到的换手率序列并排列出来逐日比对。只有三个源完全一致才放行,有任何偏差就整段标记出来人工排查。实际跑下来,2005年之前的旧数据出错率明显偏高,尤其是1998-2001年间,刚好赶上数据披露规范化之前,不少股票复权因子算出来是错的,直接导致成交量数据偏差,换手率自然跟着错。
3.2 停牌日、无成交日与缺失值的区分处理
这是最容易被忽略的坑。很多数据源对停牌日的处理是"直接不返回这一行",你合并面板时如果按左连接合并,停牌日会被静默丢掉;但如果你按笛卡尔积展开所有股票所有交易日再join,停牌日对应的是NaN。这两种做法对后续计算的差别是巨大的。
我在处理时做了三个分类:
- 停牌日:当日无交易,换手率为0,标记为"停牌"。
- 正常交易日但无成交:这种极罕见(A股几乎不存在,但不排除早期的部分股票),换手率为0,标记为"无成交"。
- 数据源缺失:当日实际有交易但没拿到数据,标记为NaN。
三个分类的后续处理完全不同。停牌日的0是真实值,做任何统计时都该保留;数据缺失的NaN则必须想办法补。我见过有人把所有NaN直接填0,结果把一堆缺失值变成了"零换手",做出来的换手率分布图左下角突然堆起一座山,真实情况根本没这么高。
3.3 股权分置改革与限售股解禁的跳变校正
换手率计算的分母是流通股本,流通股本本身是随时间变化的。对A股而言,最剧烈的变化来自两件事:2005-2007年的股权分置改革,以及此后持续不断的限售股解禁。
举个例子,一只股票股改前流通股可能只有总股本的30%,股改完成后限售股逐步解除限售,流通股本在几个月内从3亿股涨到10亿股。分母变了3倍多,换手率如果照搬原始数据,会看到一条"断崖式下跌"的曲线——但这不是因为交易变清淡了,纯粹是分母变了。我处理时用了"可比口径"校正:以最新股本结构为基准,对历史换手率做向后复权的调整,把每一次股本变动导致的跳变全部抹平。
具体做法是计算一个流通股本扩张倍数序列,然后把历史成交量除以最新流通股本,得到"基于当前流通股本的等效换手率"。这样做的好处是,整个时间序列前后可比,不会出现因为股改导致的假跳变;坏处是历史换手率的绝对值与现代口径不完全一致——但研究本来就不该把1995年的换手率和2023年的换手率直接比大小。
当然,我同时保留了原始口径的换手率,字段名是turnover_rate_raw,方便那些需要原始口径做对照的用户。
3.4 复权因子的边界问题:涨跌停板与一字板场景
换手率计算本身不需要复权——成交量不受除权除息影响,但如果你把换手率和股价数据放在一起做研究,股价的复权口径就得仔细考虑。这里有个特别隐蔽的坑:ST股的连续一字跌停。
一只ST股跌停时,卖单排队几十万手,但成交量可能只有几百手。这时候换手率约等于0,但如果你的数据里这只股票当天"涨跌幅"显示为-5%(ST股跌停限制5%),价格看起来还能正常交易。问题在于:如果你用"涨跌幅"来识别一字板,你得同时结合换手率——一字跌停板=涨跌幅触发下限+换手率极低。只靠单一指标很容易误判。
我专门给这类情况加了一列is_limit_one_word(是否一字板),识别逻辑是:当日收盘价触及涨跌停价且换手率低于某个阈值(默认0.5%)。实际效果验证下来,这个标记在做A股跌停板打开后的反弹策略、次新股开板策略时有奇效,省了后面用的人大量重复工作。
4. 换手率数据的典型研究应用和实操注意事项
4.1 因子构建中的标准化细节
拿到干净的换手率数据后,最常见的用途是构建流动性因子。我给几个实操建议:月度频率上,用当月日均换手率取对数,再按行业和市值做中性化处理,得到 residual 再做z-score标准化。直接拿原始换手率做横截面排序会出问题——因为换手率和市值高度负相关,你排序出来的"高换手"组几乎全是小盘股,买进去等于在小市值因子上做了一次超额暴露。
对冲这个风险的标准做法是:每个月末对换手率做横截面回归,控制变量取对数市值和行业虚拟变量,取残差后再排序。我测试过,处理前后的因子IC有不少区别,不放心的可以自己在数据上验证一下。
此外,换手率数据里偶尔会出现极端值——某些小盘股被资金炒作时,换手率能冲到30%甚至50%,这种样本在构建因子时建议做MAD(中位数绝对偏差)缩尾处理。MAD比均值标准差稳健,不会让少数极端炒作样本把整个因子分布拉偏。
4.2 换手率与波动率、收益率的联动特征
老股民都知道"天量见天价",换手率数据可以把这个朴素经验量化。回测1990年以来A股的数据,可以看到一个清晰的规律:全市场日均换手率超过某个阈值(大约在3%-4%之间)时,未来20个交易日的市场平均收益倾向于为负;而市场极度低迷、换手率跌破1%时,未来一年出现大级别行情的概率显著升高。这本质上是一个均值回归逻辑——换手率太高说明情绪过热,太低说明恐慌出清。
但这里要强调一点:这个规律用在指数层面比较稳健,用在个股层面很不稳定。原因是单只股票的换手率受事件驱动太强——并购重组、业绩暴雷、股东增持,都能让个股换手率瞬间飙升,但这种飙升并不都意味着"过热"。所以如果你要做个股层面的择时,建议配合换手率的"历史分位数"使用,而不是用绝对阈值。
4.3 新股上市初期的换手率特征
次新股是换手率研究的特殊样本区间。A股新股上市初期换手率天然畸高,注册制之前的新股上市首日有44%涨停限制,导致很多新股开板前换手率被"锁住";注册制后首日不设涨跌幅,首日换手率经常百分之六七十,随后几天快速回落到正常水平。这个从"畸高"到"正常"的过程,不同股票持续的时间长度差异很大,研究次新股炒作周期的,可以基于这个数据集算一个"开板换手率衰减系数"。
具体用法:对每只次新股,计算上市后第1、2、3、5、10日的换手率,形成一个衰减曲线。把衰减速度进行分类,衰减极快的通常说明筹码锁定良好或炒作结束;衰减极慢的说明持续有资金交换,往往对应游资接力。结合龙虎榜数据一起看,能识别出几种典型的次新股炒作模式。
4.4 行业轮动研究中的换手率偏离度
做行业轮动的朋友,可以试试把换手率数据用在"行业拥挤度"的度量上。方法很简单:对每个申万一级行业,计算当日行业总换手率相对过去60日平均换手率的偏离度,偏离度超过2倍标准差时标记为"拥挤状态"。历史回测显示,行业换手率偏离度进入极值区域后,未来5-10个交易日该行业跑赢全市场的概率会显著下降——也就是"拥挤交易"的负反馈效应。
用这个指标做行业配置的减仓信号,比单纯看估值分位要灵敏得多。估值的修复是慢变量,换手率的飙升是快变量,快变量对短期择时的指导意义更强。
5. 数据验证和常见陷阱:我的踩坑记录
5.1 数据源之间的标准不统一问题
我最初做第一版时,在2015年股灾期间的数据上发现了明显异常:同一只股票,Tushare给的数据显示当天换手率8.7%,聚宽给的却是9.9%,差距大到完全不可忽略。排查后发现原因:2015年6月-7月,很多股票停牌,复牌后连续跌停,部分数据源用的是"按总股本计算"的换手率,部分用"自由流通股本"计算,还有的干脆用"流通股本中的实际流通部分"——在极端行情下,这些口径的差异会被放大到非常夸张。我最后把2015-2016年的所有数据逐个源交叉验证,统一按"交易所披露口径"校准,才把这个问题解决。
这种"口径不一致"问题最恐怖的点在于:它只会在极端行情下暴露出来,普通时间段三种算法算出来的结果相近,你根本发现不了。等你拿数据去做压力测试,结果全线崩盘,再回头查才发现是数据底层就错了。所以我在最终版数据里加了数据源标记字段(source),虽然是混合来源,但每个时间段的来源标注得清清楚楚。
5.2 历史行情数据中的"跳空"谬误
另踩过一个坑:某只股票2010年3月的数据里,换手率连续一周显示为0.0001%左右,看着像"交易极其清淡",但成交额却不低。排查后发现是当天成交量数据单位错位——源数据把"股"和"手"搞混了(1手=100股),导致成交量被缩小了100倍。换手率=成交量/流通股本,分子错了100倍,数据全错。这种单位错位在早年手工录入时代特别常见,哪怕后期电子化,数据迁移时的手误也偶有发生。
应对方法是在数据集成流程里加入一套"常识校验"规则:当日换手率超过30%时告警;连续5日换手率低于0.01%但成交额大于1000万时告警;成交量变化率超过前20日均值10倍时告警。这套规则帮我抓出了好几处源数据错误。
5.3 银行、证券等特殊行业的换手率解读
最后特别提醒一下特殊行业的换手率解读问题。银行股、石油股这类大权重股,流通股本极其庞大,日常换手率经常在0.2%-0.5%之间徘徊,看起来就像一潭死水,但这不是股票没有人气,是盘子太大——一天的成交额几十亿甚至上百亿,摊到数万亿的流通股本上,换手率当然高不起来。小盘题材股就相反,流通盘可能只有几亿股,一天换手率飙到20%轻轻松松。
所以在做全市场横截面研究时,我习惯对换手率先做行业中性化处理,或者按市值分组后再比较组内相对位置,而不是直接跨行业比绝对数值。你拿一个银行股0.3%的换手率和一个次新股15%的换手率放在同一个模型里跑,模型会以为银行股"极度冷门",但这是结构性差异,不是情绪或流动性差异。
5.4 数据集的版本管理:加了一列"血液",改了一版"皮肤"
这版数据做完后,我没有一次性了事,而是把源数据和衍生计算脚本分开管理,每次修订都标注版本号。因为数据这东西,最容易出的问题不是"当时错了",而是"后来的修订覆盖了之前的正确结果",等再被别人用的时候,根本不知道这个数是谁在什么时候改的。
我的做法是:原始数据落库后只读不写,所有清洗逻辑都写成脚本重跑,每一步都输出一份checkpoint文件,最后生成的数据集带hash校验值。这样任何一个数变了对不上号,往回追一步就能定位到是清洗逻辑的变化还是源数据的更新。
做这个数据集前后花了很长时间,但做完回头看,我认为最有价值的不是数据本身,而是把换手率背后的口径差异、制度背景、异常模式理清楚了。数据这东西,如果你不知道它哪里可能出错,就永远不敢放心地用;如果你知道查过了哪里、还差哪里,用起来心里才踏实。