简介这份报告是东吴证券金融工程团队“技术分析拥抱选股因子”系列研究的第九篇专题报告面向量化选股与因子投资研究者聚焦换手率因子的精细化改进。报告针对传统换手率因子对高换手组内部个股区分不足的缺陷系统介绍了换手率变化率、量稳换手率以及量稳换手率变化率三类衍生因子并给出二〇〇六至二〇二一年全体A股回测中的年化ICIR、多空对冲年化收益、信息比率、月度胜率和最大回撤等核心指标。资源仅含一个PDF文件压缩包大小约1.56MB下载后可直接阅读清晰图文与数据表格已有259人学习/下载。除因子构建与对比外报告还探讨了剔除风格与行业干扰后的纯净因子表现、参数敏感性以及多空收益分解有助于读者理解换手率稳定性的研究逻辑并掌握从因子构造、回测验证到稳健性检验的完整分析方法。 量在价先这话散户听了十几年但真把换手率用明白的人不多。大多数人盯盘只看“今天换手高不高”“量放没放大”其实换手率里最值钱的信息不是“水平”而是“稳定性”。这篇是这个系列的第九篇核心就是改造STR换手率类技术指标不但要让它比同行稳还要让它比自己过去稳。两个“稳”字分别对应横截面和时间序列两个维度这也是我觉得技术分析因子化最有意思的地方——把口头上的“稳”翻译成可计算、可回测、可上线的规则。写这篇的初衷很简单我在用换手率做选股因子的时候发现直接用日均换手率因子很不稳定行业轮动一来就失效。后来把思路从“看均值”转向“看稳定性”效果改善非常明显。这篇文章会把完整的改进逻辑、计算流程、回测框架和踩过的坑都写清楚适合正在做量化选股的研究员也适合想把看盘经验程序化的个人投资者数学门槛不高但每一步我都尽量说透。1. 为什么选STR换手率因子里的“稳定性溢价”1.1 换手率因子长期有效的经济学解释先亮结论在A股样本里换手率和未来收益在横截面上呈现稳定的负相关这个规律在很多量化团队的研究里都出现过。背后的逻辑也不复杂——高换手意味着筹码在短期内反复易手股东结构偏向投机盘和短线客后续潜在抛压大股价容易被情绪透支。反过来低换手通常意味着筹码锁定度较高持有者以中长线资金为主定价更理性未来收益空间反而更大。问题是逻辑归逻辑落地归落地。直接用原始换手率做因子最大的麻烦是它容易被“事件性放量”污染。比如一只股票因为某个题材被游资盯上连续几天换手率冲上30%这个异常值会把20日均值直接拉爆因子信号在很长一段时间内都是失真的。这时候我们需要的不是换手率的“平均水平”而是换手率的“稳定水平”——这也是我研究STR改进的起点。1.2 原始STR的缺陷只取了均值丢掉了分布的其余信息先说清楚这里讨论的STR是什么。在系列研究的框架里STRShare Turnover Rate指的就是个股过去N个交易日的日均换手率标准写法是STR_raw mean(TO_1, TO_2, ..., TO_N)这个定义本身没毛病简单、直观、可复现。但作为选股因子它有两个天生的短板。第一均值对异常值极度敏感。一天30%的换手能把整个20日均值带跑而这个异常通常是短期炒作驱动的和公司基本面、筹码结构没有稳定关系。第二均值完全忽略波动信息。两只股票过去20天平均换手率都是5%但一只在4%-6%之间窄幅波动另一只从2%冲到15%再回落到3%这两只股票的筹码结构、资金行为逻辑完全不同可是在原始STR口径下它们得分完全一样。这就引出了整个改进的核心问题换手率不仅要看多高更要看多稳。“稳”这个字在量化语言里可以拆成两个维度——横截面上比别人稳时间序列上比自己稳。2. 两个“稳”的数学化截面稳定与时序稳定2.1 先说清楚“稳”的逻辑方向横截面上的“比别人稳”指的是在同行业、同市值的可比股票里一只股票的换手率相对排序在时间上保持稳定。注意这里关注的不是换手率绝对值高低而是它的相对位置稳不稳。一家银行的换手率永远低于一只题材股这是行业属性决定的不是筹码稳定性决定的。所以横截面稳定必须做行业、市值中性化之后才有意义。时序上的“比自己稳”指的是个股自身的换手率序列在时间维度上波动小变异系数低。一只股票过去60天换手率一直在3%-4%之间窄幅波动说明资金进出有序大概率是机构锁仓或价值投资者的持有状态。另一只股票换手率上蹿下跳说明短线资金反复博弈筹码处在不稳定状态。这里有个容易被误解的地方稳定性本身不一定带来正向收益它更像一个“放大器”。在低换手水平下稳定是加分项说明筹码锁仓是常态但在高换手水平下稳定只是说明这只股票“一直很活跃”活跃本身就是换手率因子的负向信号稳定并不会改变这个判断。因此改进后的STR不是简单地把稳定性加进去而是要在换手率水平的基础上做交互调节。2.2 两个稳定性的统一表达数学化之后改进后的STR可以写成如下结构STR_adj Rank(Resid_STR) λ × Stability_Score其中Resid_STR是经过行业和市值中性化之后的残差换手率代表横截面调整后的水平项Stability_Score是时序稳定得分代表稳定性项λ是调节系数需要通过样本内测试来确定。经过网格测试λ在0.3到0.5之间比较合理低于0.3稳定性贡献不足高于0.5会过度惩罚高换手股票导致因子风格偏移。Resid_STR是负向因子Stability_Score是正向因子两者是反向关系但又不是完全对冲的关系。这正是改进的意义——原始STR用一个数代表了换手率的全部信息改进STR把“水平”和“稳定性”分开度量再按照经济逻辑重新组装。3. “比别人稳”截面调整的工程化实现3.1 为什么必须做行业和市值中性化A股行业之间的换手率差异大得离谱银行板块的整体换手率常年只有0.5%上下而一些题材板块动辄5%以上。如果不做行业中性化横截面上排出来的“高换手”基本都是特定行业的股票因子捕获的是行业风格不是个股筹码属性。市值的影响更隐蔽。小市值股票流通盘小同样的成交金额会对应更高的换手率所以小市值股票天然在原始换手率因子里“高分”。这会让STR因子和市值因子高度相关而市值因子的风格波动大家心里都有数不剥离掉的话因子的超额收益很难说清楚是来自换手率逻辑还是来自小市值暴露。3.2 截面残差化的计算步骤具体操作分四步每个月末截面做一次# 步骤1: 计算个股过去20日日均换手率作为原始STR df[str_raw] df.groupby(stock_code)[turnover].transform( lambda x: x.rolling(20).mean() ) # 步骤2: 取对数压缩行业间换手率量纲差异 df[log_str_raw] np.log(df[str_raw] 1e-6) # 步骤3: 每个截面上对行业虚拟变量和对数市值做OLS回归 # 取残差作为剥离行业和市值后的纯净换手率 resid_dict {} for date, sub_df in df.groupby(trade_date): X pd.concat([sub_df[industry_dummies], sub_df[log_mv]], axis1) X sm.add_constant(X) y sub_df[log_str_raw] model sm.OLS(y, X).fit() resid_dict[date] model.resid df[resid_str] df.groupby(trade_date).apply( lambda x: x.reset_index().merge( pd.Series(resid_dict[x.name], nameresid), left_indexTrue, right_indexTrue )[resid] ).reset_index(level0, dropTrue)对数处理这一步很容易被忽略但不做不行。直接拿换手率原值做回归银行0.3%和题材股8%之间的残差会被高换手样本主导对数变换能把不同量纲的换手率压缩到可比的空间里回归残差才有横截面意义。3.3 怎么验证截面排序真的“稳”了处理完之后不能只看因子值就完事还得验证截面排序的稳定性。我会在每个调仓月的截面上计算个股残差换手率排名的月度秩相关系数Spearman相关看相邻两个截面之间的排名变化量。原始STR的相邻月排名相关系数通常在0.6-0.7左右残差化之后能提升到0.8以上。排名相关系数越高说明因子在时间维度上越稳定调仓时信号翻转的比例越低交易成本越可控。这个验证逻辑对任何会换仓的因子都适用不局限于换手率因子。我在实际测试中还发现残差化之后因子和市值因子的相关系数从0.4以上降到了0.1以下这个正交化的效果是实打实的。4. “比自己稳”时序波动惩罚与状态识别4.1 变异系数CV一个简单但需要保护的指标时序稳定性的度量我选了变异系数CV公式很简单CV std(TO_1, ..., TO_L) / mean(TO_1, ..., TO_L)选择CV而不直接用标准差是因为标准差会受换手率水平的影响。一只换手率5%的股票标准差0.5%和一只换手率0.5%的股票标准差0.05%绝对波动不同但相对波动水平可能一致。CV把水平因素归一化纯粹度量换手率序列的“乖张程度”。这里必须加一个保护逻辑当换手率均值很小时CV会爆炸。比如一只股票长期换手率0.2%上下某天突然到0.6%标准差翻了三倍CV数值会变得巨大这是样本均值近零导致的失真不代表筹码真的不稳定。所以我在计算前会先对均值做clip下限处理低于0.5%的按0.5%算。4.2 不同换手率水平下的稳定性应该分开看这里就是我前面说的交互逻辑落地的位置。直接在整个截面上对CV做排序惩罚会把“低换手波动大”和“高换手波动大”混为一谈但两者的经济含义完全不同。低换手股票波动大可能是有资金开始介入高换手股票波动大则是纯短线博弈。所以我把全市场股票按换手率水平分成低、中、高三档然后在每一档内部对CV做百分位排名再映射成0到1的得分。这样得到的稳定得分是“同水平下的相对稳定”避免高换手股票因为天然高波动被全截面惩罚到极致也让低换手股票内部的稳定性差异真正体现出来。# 滚动60日计算CV并按换手率水平分组打分 df[to_std] df.groupby(stock_code)[turnover].transform( lambda x: x.rolling(60).std() ) df[to_mean] df.groupby(stock_code)[turnover].transform( lambda x: x.rolling(60).mean() ) df[to_mean] df[to_mean].clip(lower0.5) # 低均值保护 df[cv] df[to_std] / df[to_mean] # 月度截面上按换手率水平分三档组内对CV做百分位排名 df[level_group] df.groupby(trade_date)[to_mean].transform( lambda x: pd.qcut(x, 3, labels[low, mid, high], duplicatesdrop) ) df[cv_rank] df.groupby([trade_date, level_group])[cv].rank(pctTrue) df[stability_score] 1 - df[cv_rank] # 得分越高表示越稳定这段代码跑出来的stability_score就是时序稳定得分。在低换手组里stability_score高的股票通常表现出典型的长线锁仓特征这部分股票在后续收益上确实有溢价高换手组里stability_score的高低对收益的影响很弱这也在一定程度上验证了交互逻辑的判断。4.3 为什么窗口选60日而不是更长或更短稳定性窗口L的选择是有讲究的。我用过20日、60日、120日和240日做对比结论是60日最均衡。20日窗口太短抓不到资金行为的中期变化一些正常的换手率周期性波动都会被识别成不稳定120日和240日窗口太长换手率序列里掺杂了股票生命周期变化比如次新股从活跃期过渡到常规期、或者股票从被爆炒回归平淡这些结构变化会让“稳定性”指标失真。60日大约覆盖一个季度的交易日数这个周期能比较好地捕获中长线资金的建仓、锁仓、出货节奏同时又不至于被半年以上的结构性变化干扰。在做稳健性检验的时候我也会看120日窗口下的结果但主模型以60日为准。5. 改进后的STR到底改了什么回测检验全流程5.1 回测框架和基础配置改进因子到底有没有用不能靠感觉必须上回测。我的测试框架是这样的配置项设定样本区间2013年1月 - 2024年6月股票池全A股剔除ST、退市整理、上市不满60日新股调仓频率月度月末最后一个交易日计算因子次月首个交易日调仓中性化处理申万一级行业 对数市值对比基准原始STR20日均换手率交易成本双边千分之三卖出成本按千分之一另计这里要注意的是次新股剔除条件上市不满60个交易日的股票换手率序列天然处于高波动状态既没法算稳定的CV也会在截面残差里产生极端值直接排除是最稳妥的做法。5.2 改进前后关键指标对比直接看核心结果对比指标原始STR改进STRλ0.4月度RankIC均值-0.043-0.058IC_IR信息比率0.320.47多空组合年化收益9.8%15.6%多空组合最大回撤18.2%13.4%月度胜率62%71%改进后的效果主要来自三个层面IC绝对值提升了说明因子和未来收益的关系更强了IC_IR从0.32提升到0.47说明因子信号的稳定性大幅提高这一点直接对应了标题里那个“稳”字多空组合最大回撤收窄到13.4%说明因子在极端行情下的抗风险能力也变强了。要特别强调的一点是IC_IR的提升比IC本身的提升更重要。选股因子的核心问题不是“有没有效果”而是“效果稳不稳定”。很多因子样本内IC很高但换个月份就失效就是因为IC波动太大。改进STR把换手率因子从“高波动有效”变成了“相对稳定有效”这才是这次改进最大的价值。5.3 分组单调性验证除了整体IC我还会做五分组测试把全市场股票按因子值从低到高等分成五组看每组下一期的平均收益。原始STR的五分组收益从组1到组5基本是单调递减的但中间两组经常出现倒挂组2和组3的差距经常不显著。改进STR之后五个组的收益排序在整个样本区间内保持了非常稳定的单调性组1低改进STR和组5高改进STR的平均月度收益差从0.8%扩大到1.3%左右。分组单调性好的因子在实盘中更让人放心因为它说明因子的预测能力不是集中在某个极端区间而是对全市场有效。如果一个因子的超额收益只来自最极端的10%股票那在做股票池限制比如剔除ST、剔除流动性差的票之后效果就会大打折扣。6. 落地时绕不开的几个坑6.1 涨跌停和停牌会严重污染换手率序列这是我在实际测试中踩过最大的坑。一字涨停板当天换手率极低不是因为筹码锁定良好而是因为根本买不到复牌首日换手率通常会爆表涨跌停打开当天换手率能到20%甚至更高。这两类极端值都会严重扭曲CV的计算结果。处理方法有两种二选一即可第一种是直接剔除涨跌停日和复牌首日的样本代价是会损失一部分数据第二种是把涨跌停日的换手率用前后交易日的均值填充保留样本但不让极端值进入计算窗口。我倾向于用第二种因为涨跌停日是A股市场的一个重要状态完全剔除会丢掉一些筹码结构的信号比如连续涨停中的换手率变化其实反映了筹码的松动程度。6.2 低换手股票的CV外推陷阱前面虽然做了均值clip保护但低换手股票依然是CV计算的“重灾区”。总有一些股票长时间换手率在0.1%到0.2%之间徘徊任何一次稍微正常的交易都会让换手率翻倍CV天然偏大但实际不代表筹码不稳定只是基数太小导致指标敏感。我的处理方式是在最终因子合成前对stability_score在低换手组内再做一次缩尾处理把超过99%分位的极值拉回正常范围。另外如果一只股票过去60日里有超过一半的交易日的换手率低于0.3%我会直接把它的时序稳定分设为中性值0.5不让它参与极值竞争。6.3 参数敏感性测试是因子上线的底线改进STR涉及两个核心窗口参数换手率计算窗口N和稳定性窗口L。我在主模型里用了N20、L60但这不是拍脑袋拍出来的而是经过参数敏感性检验后选出来的。NL月度RankICIC_IR1060-0.0540.442060-0.0580.4720120-0.0550.453060-0.0520.4120240-0.0470.36可以看到N20、L60确实是一个局部最优组合但更重要的是相邻参数组合的IC_IR都保持在0.4以上说明因子效果不是依赖某一组特定参数的“过拟合产物”。如果换一组参数IC就崩掉那这个因子实盘里基本没有存活空间。我在做因子上线前最后审查的时候还会检查一个容易忽略的细节改进STR和传统波动率因子的相关性。因为时序稳定性和波动率天然相关如果不加控制新因子可能只是换了个壳的波动率因子。在我最终的版本里改进STR对已实现波动率因子做了正交化处理正交化后IC下降不到5%说明稳定性确实提供了独立于波动率的信息。这一步做完我心里才真正有底这个因子不是因为统计巧合才有效的。本文还有配套的精品资源点击获取