THU-BDC2026 实验日志:那些把结果越改越差的优化
先说结论
THU-BDC2026 最值得记录的,不是某一次看起来很漂亮的收益率,而是那些把模型从“看起来有效”拉回“真的可验证”的实验。
这篇就是我的实验日志。它不追求把项目包装成一路顺风,反而专门把几次把结果越改越差的优化写下来,因为比赛里真正能留下来的经验,往往就藏在这些失败里。
| 时间 | 实验 | 结果 | 我的判断 |
|---|---|---|---|
| 2026-05-31 | 单周期回测 | 最新数据集模型单周 +5.9533% | 很亮,但周期太短,只能当线索 |
| 2026-06-04 | 修复数据泄露 | 夏普从 2.30 掉到 -0.49 | 难看,但终于诚实 |
| 2026-06-05 | 删除波动率特征 | 特征 289 -> 227,夏普回到 0.40 | 做减法有效 |
| 2026-06-06 | 加资金流向因子 | 夏普 0.70,累计收益 12.33% | 目前最像样的一版 |
| 2026-06-06 | Top-80 特征筛选 | 训练快 57%,收益掉到 -7.47% | 快不等于好 |
| 2026-06-07 | 行业覆盖修复 | 覆盖率 4.7% -> 100%,夏普降到 0.18 | 数据完整不等于模型会用 |
为什么要写成日志
这个项目一开始很容易让人上头:模型复杂、指标丰富、还有一些看起来很强的回测结果。
但量化项目最怕的就是“漂亮得不正常”。只要训练集切分、标签构造、特征窗口里有一点点未来信息,模型就会像突然开窍一样变强。问题是这种强不是真的强,它只是提前看了答案。
所以这篇文章的重点不是“我做出了一个多厉害的模型”,而是记录我怎么一步步把水分挤掉:
- 先承认单周期结果不能当结论;
- 再修复训练和预测之间的时间隔离;
- 接着删掉可能依赖泄露的特征;
- 最后再看哪些因子还能留下真实增益。
早期单周期回测:很亮,但不能信太多
archive 里有一份早期回测报告,只测了 2026-03-09 到 2026-03-13 这 5 个交易日。
当时结果看起来很好:
| 模型 | 单周期收益 |
|---|---|
| 原始数据集模型 | -0.7367% |
| 最新数据集模型 | 5.9533% |
| 等权基准 | 1.3114% |
如果只看这张表,很容易觉得方向已经对了。但 5 个交易日太短,运气成分很大。它更像一个提示:“这里可能有东西,值得继续查”,但不能证明模型稳定有效。
后来回头看,这个阶段最大的价值不是收益,而是提醒我必须把验证周期拉长,把回测流程做严。
修复数据泄露:最痛,但最值
真正改变项目走向的是数据泄露修复。
原来的回测只保证训练样本日期早于回测日,但标签使用了未来 5 天开盘价。也就是说,靠近回测日的训练样本虽然日期在过去,标签却可能已经跨进未来窗口。
这类泄露非常隐蔽。代码看起来没问题,结果也很好看,但本质上模型已经偷看了答案。
修复后结果马上变难看:
1 | 夏普: -0.49 |
这一步很打击人,但它是整条实验线里最重要的一步。因为从这里开始,后面的结果才有讨论价值。
做减法:砍掉波动率特征
修复泄露之后,我开始怀疑一批波动率相关特征。
例如:
boll_widthatr_pctvol_20d- 由这些指标衍生出来的聚合特征和横截面排名特征
它们在旧版本里重要性很高,但旧版本本身有泄露。所以我不确定它们是真的有效,还是只是帮模型记住了错误验证里的捷径。
于是我做了一版删除实验:
1 | 特征维度: 289 -> 227 |
这次结果反而变好。它给我的提醒很直接:特征不是越多越安全。尤其在金融数据里,有些特征看似信息量很大,其实只是把噪声和错误验证一起放大了。
资金流向:少数真正带来增益的东西
后面我加入了 Tushare 资金流向相关因子,主要包括三类:
- 主力净流入率;
- 小单净流入率;
- 超大单占比。
这一版是目前实验日志里比较像样的一版:
1 | 夏普: 0.70 |
更关键的是,主力净流入率_ma20_cs_rank 进入了特征重要性 Top 20。
这让我觉得,资金行为信号可能比单纯堆价格形态更有价值。价格形态很容易过拟合,资金流向至少在这个窗口里提供了另一类信息。
三个失败优化
Top-80 特征筛选
我用 2023 年前的数据筛出重要性最高的 80 个特征,想减少噪声、加快训练。
训练速度确实变快了:
1 | 训练时间: 116 分钟 -> 50 分钟 |
但结果变差了:
1 | 累计收益: +12.33% -> -7.47% |
这说明历史窗口里的重要性不一定能迁移到未来。金融数据不是静态数据集,市场状态一变,过去最重要的特征可能反而拖后腿。
三分类标签
我还试过把股票粗分成三档:
- 前 30%;
- 中间 40%;
- 后 30%。
本意是让模型更关注“买入区”和“避雷区”,但实际效果很差,夏普掉到 -1.08。
原因大概是排序任务不能切得太粗。10 bins 标签虽然复杂一点,但保留了更多相对顺序信息,更适合每天从 300 只股票里选 Top 5。
行业覆盖修复
还有一次很反直觉的失败:行业覆盖修复。
原来行业覆盖率只有 4.7%,这肯定不合理,所以我把行业信息补到了 100%。从数据完整性的角度看,这一步绝对是正确的。
但模型表现却从夏普 0.70 降到 0.18,而且 sector_id 变成了最重要特征。
这说明模型可能不是“学到了行业”,而是“太相信行业”。行业信息需要更细的约束,比如降权、正则、分组验证,不能直接一股脑塞进去。
最后留下的规则
这轮实验之后,我给自己留下了几条规则:
- 单周期结果只能当线索,不能当结论。
- 回测第一步永远是查数据泄露。
- 特征重要性要按时间段看,不能拿一个历史窗口当真理。
- 做减法不丢人,能删掉噪声也是能力。
- 更完整的数据也需要被模型正确使用。
- 所有“看起来合理”的改动,最后都要让回测说话。
下一步怎么做
如果继续推进这个项目,我会优先做三件事:
- 把滚动回测周期拉长,不再被单周收益带节奏;
- 对行业、市值、流动性这类强结构特征做分组验证;
- 保留 LightGBM LambdaRank 主线,把深度模型作为辅助实验,而不是主战场。
这篇记录不构成任何投资建议。它更像一份比赛实验备忘录:把好看的结果拆开,把难看的结果留下,最后剩下的才可能是真的经验。

