/~learn/s/c760fbc6
> 最后更新:2026-07-02 > > 本文档记录选股因子的实际回测验证过程和修正结论。它是 01-选股-多因子设计方案 的后续验证文档——设计方案提出了 5 因子框架,本文档用数据证明其中 4 个因子在 A 股上无效并修正为单因子动量。
---
设计方案 V1 提出 5 个等权重因子:
| 因子 | 名称 | 预期作用 |
| ------ | ------ | --------- |
| momentum | 动量(40+80 日,R² + 回撤惩罚) | 选出趋势最强势的股票 |
| stability | 波动率稳定性 | 奖励平稳上涨、惩罚剧烈波动 |
| anti_overbought | 反追高 | 避免买入涨得过高的股票 |
| volume_confirm | 量价确认 | 奖励放量上涨、惩罚缩量下跌 |
| liquidity | 流动性 | 剔除无人问津的股票 |
首次全量跑完后,选出的 TOP 10 如下(2026-07-02):
| 排名 | 代码 | 名称 | 评分 | 20日涨幅 |
| ------ | ------ | ------ | ------ | ---------- |
| 1 | 600705.SH | 中航产融 | 0.807 | — |
| 2 | 600837.SH | 海通证券 | 0.794 | — |
| 3 | 688158.SH | 优刻得 | 0.758 | -5.6% |
| 4 | 300435.SZ | 中泰股份 | 0.766 | — |
| 5 | 300058.SZ | 蓝色光标 | 0.770 | — |
直觉上就不对——一堆跌的、横盘的股票竟然进了 TOP。下面用数据逐因子剖析。
---
得分分布: mean=0.909, p25=1.0, p50=1.0, p75=1.0
得分=1.0 的比例: 937/1134 = 82.6%
得分为 1.0 的股票过去 20 日均收益: -7.65%(正收益仅 9/50)
为什么会这样?
反追高因子的设计是:偏离 60 日均线 < 15% 不惩罚(得满分 1.0),偏离 15%-50% 线性惩罚。
但在 A 股现实中,「偏离均线 15% 以上」是一个极罕见的场景——大部分股票要么在均线附近震荡(得 1.0),要么在均线下方(还是得 1.0,因为偏离度 < 15%)。只有在短时间内暴涨 15%+ 的妖股才会被惩罚。
结果:82.6% 的股票得分完全相同(=1.0),等于 20% 的权重被浪费,而且得满分的股票里 82% 在亏钱。
量价确认 = sign(20日收益) × (20日均量/60日均量 − 1)
这个公式的致命缺陷:
下跌缩量的死股被奖励 +0.5,这完全违背了「量价确认」的本意。回测中「动量 + 量价确认」组合每期收益仅 +0.15%(其他组合 +2% 以上),接近腰斩。
stability = 1 − (40日波动率 / 120日波动率)
这个公式奖励「近期波动远小于历史波动」的股票。但问题是——在 A 股,一只从 100 跌到 50 再横盘 40 天的股,40 日波动极低、120 日波动很高,stability = 1 − 0.2/0.5 = 0.6,比正常温和上涨的股得分还高。
结论:稳定性因子奖励的是「跌完以后死掉的股票」,而非「稳步上涨的股票」。
liquidity = avg_amount × 0.5 + avg_turnover × 0.5
日均成交额 6 亿(如招商银行)→ score = 3 亿。日均成交额 6000 万(小盘股)→ score = 3000 万。量纲差异 50000 倍,rank 归一化后勉强能用,但和动量因子的 rank 分数放在一起加权,信息比例完全失衡。
---
| 参数 | 值 |
| ------ | ----- |
| 股票池 | 1249-1405 只(缓存中有数据的过滤后股) |
| 时间范围 | 2024-10-11 → 2026-05-29(20 个月) |
| 频率 | 双周(周五),79 期 |
| 持仓 | 每期选 Top 10 等权 |
| 基准 | 每期所有过滤后的股票等权 |
| 评估 | 持有 20 个交易日的平均收益 |
| 配置 | 每期均收益 | 胜率 | 累积收益 | vs 基准 |
| ------ | :--: | :--: | :--: | :--: |
| 动量(40+80日,R²+回撤惩罚) | +5.33% | 61% | 1324% | ✅ 胜出 |
| 原始 5 因子(含反追高) | +2.98% | 59% | 716% | ❌ 落后 |
| 动量(20+40日,无回撤惩罚) | +2.90% | 62% | 375% | ❌ 落后 |
| 动量(60日纯收益) | +2.20% | 57% | 237% | ❌ 落后 |
| 动量+稳定性+量价 | +2.42% | 58% | 396% | ❌ 落后 |
| 基准(等权全市场) | +3.49% | — | 1258% | — |
| 排名 | 代码 | 名称 | 行业 | 20日涨幅 | 40日涨幅 |
| ------ | ------ | ------ | ------ | ---------- | ---------- |
| 1 | 920438.BJ | 戈碧迦 | 元器件 | +129% | +265% |
| 2 | 300868.SZ | 杰美特 | 元器件 | +49% | +119% |
| 3 | 300975.SZ | 商络电子 | 元器件 | +17% | +148% |
| 4 | 688766.SH | 普冉股份 | 半导体 | +134% | +221% |
| 5 | 003004.SZ | 声迅股份 | IT设备 | +11% | +148% |
| 6 | 301188.SZ | 力诺药包 | 玻璃 | +50% | +137% |
| 7 | 605376.SH | 博迁新材 | 小金属 | +44% | +108% |
| 8 | 301200.SZ | 大族数控 | 专用机械 | +35% | +107% |
| 9 | 688545.SH | 兴福电子 | 元器件 | +89% | +158% |
| 10 | 688233.SH | 神工股份 | 半导体 | +201% | +235% |
所有 10 只股过去 20 日全部正收益!
---
raw = 40日收益 × 0.6 + 80日收益 × 0.4
R²趋势质量:
对 40 日归一化价格做线性回归
slope ≤ 0 → R² 为负值(下跌趋势打负分)
slope > 0 → R² 越大越得分(趋势越平滑越高)
clamp(R², -1, 1); min(R², 0.3)
回撤惩罚:
距 80 日高点回撤 0~5% → 不惩罚(×1.0)
距 80 日高点回撤 5~15% → 轻微惩罚(×0.7)
距 80 日高点回撤 >15% → 重度惩罚(×0.3)
final_score = raw × max(R², 0.3) × 回撤惩罚
normalize(method='rank') → 百分位排名(0-1 区间)
weight = 1.0
无其他因子参与加权
---
selection/stock/
├── factor/
│ ├── momentum.py # ★ 唯一生产因子
│ ├── stability.py # 保留但未使用
│ ├── anti_overbought.py # 保留但未使用
│ ├── volume_confirm.py # 保留但未使用(公式有 bug)
│ └── liquidity.py # 保留但已改为 StockDataFetcher 的过滤层
├── backtest_simple.py # 回测脚本(可随时重新验证)
└── engine.py # _default_selector() 已改为单因子动量
保留其他因子代码的目的是方便后续继续实验——任何新因子想法都可以通过 backtest_simple.py 加一行配置快速验证。
---
show_marker.py 的推送流程,与 ETF 选基结果一起推送