20 日反转因子选股 · 检验报告

因子表 my_cn_stock_bar1d · 因子方向越大越好 · 持仓最高 20 只等权 · 月度调仓 · 基准 中证全指 · 测试段 2024-06-01 ~ 2026-10-29 · 策略文件 sha256 b3bca8f4948e…

1 概览与总评

无效
一句话总评。因子在训练段和测试段都有显著的排序信息(RankIC 0.0536、0.0870),但做成「每月买得分最高的 20 只」后没有赚到超额:训练段、验证段年化超额分别是 -20.69%、-17.65%,测试段只有 +1.82%(信息比率 0.23,不显著)。因子的含义只是猜测,所以总评最高本来就只到「存疑」,这次数字连「存疑」也不够,给「无效」(指这套持仓规则,不是说因子完全没有信息)。
因子方向
越大越好
选因子最大的 20 只;由训练段 RankIC 的符号锁定
测试段年化超额
+1.82%
信息比率 0.23,t≈0.36
测试段夏普
0.35
基准 0.30
测试段最大回撤
-27.70%
全程 -80.46%
训练段年化超额
-20.69%
夏普 -0.40
验证段年化超额
-17.65%
夏普 -0.98
测试段 RankIC
0.0870
t = 3.27,课程口径「强」
月换手(单边)
87%
课程口径 20%~50%

因子表 my_cn_stock_bar1d,因子列 factor(表里有 factor、factor_pre、factor_raw 三列数值;docs 的因子名是「20日反转因子(行业市值中性化)」,与 factor(最终因子)一致)。基准 中证全指(000985.CSI)。数据区间 1896 个交易日,2019-01-02 至 2026-10-29。

总评规则(我自拟,你没有给定):因子含义只是猜测,最高「存疑」;测试段年化超额不为正,判「无效」;为正但近似 t 值小于 2,且训练段、验证段超额都为负,也判「无效」;其余判「存疑」。

2 方向判定

只用训练段的月末信号日判定,验证段和测试段在这一步没有碰。

项目训练段结果
训练段样本月末信号日 46 个(2019-01-31 ~ 2022-10-31),平均每期 2369 只股票
RankIC 均值 / 标准差0.0536 / 0.0950(课程口径「强」)
t 值 = 均值 ÷ 标准差 × √期数3.83(月度取样,收益区间不重叠,不需要 Newey-West)
IC 大于 0 的月份占比65.2%
前半段 / 后半段 IC 均值0.0506 / 0.0567
5 分组下期平均收益(G1 最小 → G5 最大)0.56% / 1.51% / 1.53% / 1.73% / 1.61%
G5 − G1+1.06%
方向 = sign(IC 均值)+1,越大越好(选因子最大的 20 只)

三项可信度检查

可信度检查结果说明
|t| ≥ 2通过t = 3.83
前后半段 IC 符号一致,且与方向一致通过0.0506 与 0.0567 同为正
G5 − G1 的符号与方向一致通过+1.06%
是否 U 形(G1、G5 同时高于或低于 G3)通过G1 0.56%、G3 1.53%、G5 1.61%:G1 低于 G3,G5 高于 G3,不是 U 形

与文档核对:一致。因子表的说明写着「值越大表示近期跌得越多、越看涨」,方向为 +1,与训练段 IC 的符号相同。

方向在验证段、测试段是否翻转

之后的段RankIC 均值t 值IC>0 占比信号日数G5 − G1符号是否与方向一致
验证段0.03991.7066.7%15+0.74%一致
测试段0.08703.2777.8%27+1.79%一致

验证段只有 15 个信号日,t 值 1.70 没有到 2,只能说符号没变;方向在验证段、测试段都没有被改动。另外训练段 G5(1.61%)略低于 G4(1.73%):好处主要来自避开 G1(最近涨得最多的一档),不是越跌越好。

3 规则卡、假设卡与切分

规则卡内容
因子表与列my_cn_stock_bar1d · factor。表里有 factor、factor_pre、factor_raw 三列数值;docs 的因子名是「20日反转因子(行业市值中性化)」,与 factor(最终因子)一致
方向及依据越大越好(+1)。训练段 RankIC 0.0536,t 3.83,前后半段 0.0506/0.0567,G5−G1 +1.06%
候选池全 A,剔除:ST/*ST、停牌、上市不足 250 个交易日(因子表已剔除)、近 20 日日均成交额低于 5000 万元。信号日涨停的股票没有剔除(过滤条件上限 4 个已用完);改为回测后检查被拒单与成交价
持仓与权重得分 = 方向 × 因子值,取最高 20 只,等权,单票不超过 8%,投入比例 95%,初始资金 1,000,000 元
调仓与成交月度;月末交易日收盘后生成信号,下一个交易日开盘成交(引擎本身 D 日下单 D+1 开盘成交,不额外加滞后)
风控无止损;停牌或无行情的股票当天不下单
成本佣金买卖各 0.03%(最低 5 元),卖出印花税 0.1%,滑点单边 0.1%;用 param() 声明
基准中证全指(000985.CSI)。与全A、不限市值的股票池口径最接近;但它是市值加权,本策略等权持有 20 只,口径仍不一致
三集切分训练 2019-01-01 ~ 2022-11-30;验证 2023-01-01 ~ 2024-04-30;测试 2024-06-01 ~ 2026-10-29(因子表与行情的最后一天一致)。段间各留约 20 个交易日(训练段最后一个信号日 2022-10-31 的标签,卖出价是 2022-12-01 的开盘价,落在训练段之后的隔离期内,没有进入验证段 2023-01-01)
假设卡 猜测
近期超跌的股票被过度抛售,随后价格回归,故买入跌得多的
因子表的说明只写了「过去 20 个交易日涨跌幅取反」,没有说明为什么赚钱;这句话是我按字段名和说明拟的,没有被数据验证过。

三集切分时间轴

训练 950 天2019-01-01 ~ 2022-11-30隔离 22验证 320 天2023-01-01 ~ 2024-04-30隔离 20测试 584 天2024-06-01 ~ 2026-10-29
候选池诊断(训练段月末信号日,汇总)数值
因子覆盖率(满足流动性与状态条件的股票里有因子值的比例)92.0%
每期候选池大小:最小 / 中位数1412 / 2392(低于 100 只的期数:0)
剔除条数(各条件有重叠,不能相加)ST 7,751;停牌 985;无因子值 24,618;成交额不足 72,137(总计 192,161 行)
信号日涨停(没有剔除,只做事后检查)3,323 行
单行业最大持仓数(前 20 只)训练 10 · 验证 12 · 测试 17(阈值 30 只,未超)

4 三集对照

指标训练验证测试全程
交易日数9503205841,896
策略累计收益-34.89%-29.87%+22.94%-48.99%
基准累计收益+42.90%-8.47%+18.24%+49.16%
策略年化-10.76%-24.38%+9.32%-8.56%
基准年化+9.93%-6.73%+7.50%+5.46%
年化超额-20.69%-17.65%+1.82%-14.02%
夏普(扣无风险利率)-0.40-0.980.35-0.27
基准夏普0.42-0.530.300.22
最大回撤-62.72%-44.96%-27.70%-80.46%
超额净值最大回撤-63.30%-28.50%-25.23%-77.39%
信息比率-1.19-1.040.23-0.72
月换手(单边,月均)86%87%87%87%

衰减判定。规则是验证段夏普不低于训练段的 60% 算正常衰减,符号翻转或夏普腰斩算过拟合。这里训练段夏普 -0.40 本身就是负的,验证段 -0.98 更低,没有「衰减」可言:训练段夏普就为负,谈不上衰减;验证段夏普更低,仍为负。验证段 RankIC 符号与锁定的方向一致(方向稳定),所以不按「方向不稳定」处理。测试段夏普回到 0.35,略高于基准的 0.30,但年化超额的近似 t 值只有 0.36,说不上有效。

测试段取自一次从 2019-01-02 开始的连续回测,期初持仓来自验证段末尾发出的信号;该连续回测的前 1292 个交易日与此前的训练+验证段回测逐日一致(日收益最大差 0)。夏普口径与引擎一致(日无风险利率 0.0001173):本报告按日收益算出的整段夏普 -0.275,与引擎报告的夏普相差不到 0.003。月换手 =(买入额 + 卖出额)÷ 2 ÷ 组合市值,按月累加,100% 表示一个月内整个组合换掉一遍。

5 图表

图 1 训练段 5 组下期收益(方向依据)

0.00%0.50%1.00%1.50%2.00%G1(最小)0.56%G21.51%G31.53%G41.73%G5(最大)1.61%

每月把股票按因子从小到大分 5 组,看下一期平均收益。G1 最低(0.56%),G2 到 G5 在 1.51% 到 1.73% 之间。G5 − G1 为 +1.06%,方向取 +1。

图 2 策略与基准净值(训练 / 验证 / 测试拼接,底色区分)

策略中证全指
训练验证测试0.0000.5001.0001.5002.00020192020202120222023202420252026中证全指 1.492策略 0.510

一条从 2019-01-02 开始的连续净值,三段之间留有隔离期(不单独计算)。测试段的净值从训练、验证段亏损后的位置接着走,所以全程累计仍为负(-48.99%)。

图 3 超额净值(策略 ÷ 基准)

训练验证测试0.2000.4000.6000.8001.0001.20020192020202120222023202420252026超额净值 0.342

超额净值在训练段持续下行,验证段继续下行,测试段小幅回升但没有回到起点;期末 0.342。

图 3b 回撤

策略回撤超额净值回撤
训练验证测试-100.0%-80.0%-60.0%-40.0%-20.0%-0.0%20192020202120222023202420252026策略回撤 -67.8%超额净值回撤 -71.5%

策略最大回撤 -80.46%,超额净值最大回撤 -77.39%;线尾是 2026-10-29 当天仍未修复的回撤。

图 4 参数高原:训练段夏普随持仓数的变化

调仓每 1 个月调仓每 2 个月调仓每 3 个月
-0.50-0.40-0.30-0.20-0.10-0.00持仓 16 只持仓 18 只持仓 20 只持仓 22 只持仓 24 只

持仓数 16~24(默认 20 的 ±20%)和三种调仓周期共 15 个组合,训练段夏普全部为负(-0.41 到 -0.26)。验证了 6 个组合,验证段夏普也全部为负(-1.13 到 -0.92)。这是一片平的低地,不是高原:参数不敏感,但整片都不赚钱。

图 5 成本 ×1 与 ×2 的净值(训练 + 验证段)

成本 ×1成本 ×2中证全指
0.0000.5001.0001.5002.000201920202021202220232024中证全指 1.281成本 ×1 0.441成本 ×2 0.366

成本 ×2 指佣金、最低佣金、印花税、滑点全部翻倍。×1 累计 -55.94%,×2 累计 -63.40%;年化超额从 -19.72% 降到 -22.75%。超额本来就是负的,所以「成本翻倍后超额是否仍为正」这项不成立。

图 6 分年度收益与超额

策略基准超额(策略 − 基准)
-60.00%-40.00%-20.00%-0.00%20.00%40.00%2019(训练)2020(训练)2021(训练)2022(训练)2023(验证)2024(验证/测试)2025(测试)2026(测试)

八个年份里超额为正的只有 1 个(2026 年,这一年基准下跌 14.34%)。2024 年横跨验证段和测试段,是按整个自然年算的。

图 7 月换手(单边)

0.0%20.0%40.0%60.0%80.0%100.0%2019-012020-012021-012022-012023-012024-012025-012026-01

全程月均 87%,中位数 89%。每个月基本把组合换掉一遍,课程口径是 20%~50%,这个策略远高于上限。

6 五个数评价

课程口径本策略判定
年化超额 > 8%测试段 +1.82%(训练 -20.69%、验证 -17.65%)不达标
信息比率 > 1.0测试段 0.23(训练 -1.19、验证 -1.04)不达标
最大回撤 > -20%测试段 -27.70%(验证段 -44.96%,全程 -80.46%)不达标
月换手 20%~50%87%(测试段 87%)不达标
容量 > 5 亿粗估 1.52 亿(测试段信号日的中位数);训练 + 验证段 1.33 亿。规则:单票买入额不超过其 20 日日均成交额的 10%,逐期取 20 只里最紧的一只不达标(粗估)

五项都不达标,所以「三项都特别漂亮时先怀疑数据」的提示不适用;相反,数据一致性已经核对过(见第 8 节)。容量是粗估,没有考虑冲击成本曲线,也没有考虑卖出同时受限。

7 结论(大白话)

8 风险与备注

自检与记账

项目结果说明
试验次数与 DSR如实记录回测共 26 次:训练 + 验证段 4 次(首跑、重跑存曲线、成本 ×2、零成本)、调参 15 次、调参验证 6 次、测试 1 次;调参搜了 2 个参数(上限 3),试验 15 次(上限 40)。调参最优者的 DSR 概率 0.25(< 0.95,出现 SELECTION_LUCK 警告):最优夏普 -0.26,15 次随机尝试期望能到 0.10。最终参数保持默认,没有采用调参结果。
验证集、测试集各看过几次如实记录验证段被看了 5 次(首跑、分段评估、调参验证、成本回测、体检);测试集开出 1 次,另有 1 次中断的尝试(没有产出结果)。开测试集之前有三处读到过测试段数据但没有看过数字(见 trials.json)。方向没有被改动。
中断的测试集尝试已记录第一次开测试集时脚本被自己的保护逻辑挡回,没有任何结果文件,AIStudio 无残留进程。原因我推断是命令先在本地执行一遍、再被转到 AIStudio 重跑一遍,第二次开测试集时留下的记录支持这一推断(两行记录相隔 1 秒、主机不同);第一次当时没有记进程号,无法直接证实。
成本与容量假设粗估佣金万三(最低 5 元)、卖出印花税 0.1%、滑点单边 0.1%;成本 ×2 全段累计 -63.40%。每月买卖额按组合市值平均计 87%,成本拖累年化约 3.77%。容量粗估见第 6 节,是成交额占比,没有冲击成本曲线。
涨跌停与被拒单通过(有说明)引擎对涨停开盘的买单:训练 + 验证段 0 笔、测试段 4 笔开盘在涨停价,其中成交 4 笔(测试段)。跌停开盘的卖单:训练 + 验证段 25 笔里成交 6 笔,其余被取消(原因是「'open' price is lower limit」19 笔);测试段 7 笔里成交 3 笔。引擎在跌停开盘时取消了部分卖出,涨停开盘的买入没有被拒(只有测试段 4 笔);这是观察到的结果,不是文档规定。买单被拒:训练 + 验证段 16 笔([-114]Insufficient cash 13 笔, [-107]Market data not found 3 笔),测试段 2 笔([-107]Market data not found 2 笔)。成交价与当日开盘价的最大偏差 0.20%(训练 + 验证)和 0.20%(测试),买入中位数 +0.10%、卖出 -0.10%,与 0.1% 滑点一致。信号日涨停的股票没有剔除(过滤条件已满 4 个)。事后统计:买单在次日开盘处于涨停价的,训练 + 验证段 0 笔,测试段 4 笔,样本很小,不足以下结论。
未建模的交易细节未建模没有冲击成本曲线、没有融券、没有停牌期间的资金占用、没有手数以外的碎股处理、没有分红税;策略只在信号日下单,跌停开盘被取消的卖单要等下一个信号日才会重新下。
数据自检:被跳过的标的已记录因子加工时有 274 只标的缺上市天数、行业或市值被跳过,其中北交所 245 只(89%),集中在 920 开头的新代码;只有 3 只出现在本策略的成交里。
数据自检:退市标的在样本内策略交易过的 1006 只股票里有 31 只在 2024-04-30 前已无行情;引擎对退市股按最后行情结算(全程有 3 笔成交类型为 E 的记录,成交价 0.18~0.78 元,其中 2 笔在测试段;我推断它们是退市清算,没有核实)。退市整理期的真实成交没有建模。
因子体检:信号寿命通过RankIC 滞后 0/1/2/3 期:0.0538 / 0.0190 / -0.0050 / 0.0084,逐步衰减,没有出现滞后后反而更高的怪象。
因子体检:随机因子对照通过300 次随机选 20 只,月均超额均值 -0.01%,范围(5%~95%)-0.58% 到 +0.55%;真实因子前 20 只 -1.22%,低于全部随机结果。随机对照应当无超额:通过。
超额拆解部分做了全程 β = 1.09、α 年化 -19.42%(t = -2.68);成本拖累年化约 3.77%。风格暴露未做(没有取风格因子)。
回测与独立计算对账通过回测的月度收益与 SQL 独立算出的「前 20 只等权」月度收益相关系数 0.956;回测每月低 0.58%,与成本方向一致。引擎整段夏普、年化、信息比率与本报告独立计算的差不超过 0.003。
引擎 SQL 的一个未解释现象有未解释项取标签的 SQL 在查询范围边缘偶尔让某一期候选池整期为空(训练段的 2022-10-31、验证段的 2023-01-31、2024-03-29),每次日期不同,原因没有查清。做法是多取一个月、在 Python 里按信号日精确裁剪并断言每期都在;候选池大小已与 pandas 独立统计对账(相对差最大 0.79%)。若还有我没发现的整期丢失,会让断言报错而不是静默。
单日异常已核对分红入账被记为 trade_type 'D',我曾把它误当作成交价偏离;重算时只用真实成交,最大偏离 0.2%。