为什么单独写一篇技术路线

上一篇更像比赛复盘,这一篇就专门把 THU-BDC2026 的技术路线理一遍。

这个项目从最早的深度模型,到后面的 LightGBM LambdaRank,中间不是简单的“换模型”,而是我对这个题目的理解发生了变化:股票排序任务不只是预测收益率,更像是在同一天的 300 只股票里做横截面比较。

所以后面我越来越关注三件事:

  • 每天的横截面排序;
  • 训练和预测的时间隔离;
  • 特征是否真的能穿过不同年份。

第一阶段:Transformer + 横截面建模

最早的设计是 StockTransformer。

输入用过去 30 个交易日的数据,每只股票有 72 个技术指标,整体组织成:

1
[batch, 300只股票, 30天, 72特征]

模型里有几个关键模块:

  • TransformerEncoder:处理单只股票的时间序列;
  • FeatureAttention:聚合不同尺度的时序信息;
  • CrossStockAttention:让同一天的股票互相“看见”;
  • MoE Ranking:用多个专家头做排序打分;
  • Score Head:输出每只股票的最终分数。

这个设计的优点是表达能力强,也比较符合“股票之间有相对关系”的直觉。缺点也很明显:训练慢、调参成本高、回测迭代不方便,而且验证方式稍微有问题,复杂模型会把问题放大。

第二阶段:转向 LambdaRank

后面我把主要迭代切到了 LightGBM 的 lambdarank

原因很实际:

第一,比赛目标本质上是 Top 5 排序,不一定非要预测一个绝对收益率。每天 300 只股票作为一个 group,用排序目标直接优化 NDCG@3 / NDCG@5,会更贴近任务。

第二,LightGBM 训练快很多。一次滚动回测虽然还是要跑很久,但比深度模型更适合反复试错。

第三,树模型的特征重要性更容易检查。它不一定代表因果,但至少能让我发现模型是不是被某一类奇怪特征带偏。

标签和分组

标签定义是:

1
(T+5 开盘价 - T+1 开盘价) / T+1 开盘价

也就是说,模型预测的是从 T+1 到 T+5 的阶段收益。为了让不同交易日之间可比,我会按日期做横截面处理,再把同一天的股票作为一个排序组。

这个地方最容易出问题。

如果回测日是 bt_date,训练集不能只写成:

1
processed['日期'] < bt_date

因为靠近 bt_date 的样本,它的 label 可能已经用到了未来开盘价。后面我改成至少隔开一段时间,避免训练集偷看到预测附近的信息。

这一步比换任何模型都重要。

特征工程

项目里用过的特征大概分几类:

类型 例子 作用
趋势/位置 ma_dev_60dprice_pos_20d 描述价格相对均线和区间的位置
K 线形态 lower_shadow_ratioupper_shadow_ratio 描述日内买卖力量
量价关系 corr_pv_20obv_ratio 描述成交量和价格变化关系
截面排名 *_cs_rank 在同一天 300 只股票里做相对比较
市值/行业 cap_binsector_id 提供结构性信息
资金流向 主力净流入率超大单占比 捕捉资金行为信号

我后来对“截面排名”越来越有好感。因为股票比赛里,很多时候绝对值没有那么稳定,但同一天谁更强、谁更弱,反而更接近任务本身。

几个关键版本

版本 主要改动 夏普 累计收益
修复泄露后基线 排除含未来信息的训练样本 -0.49 -17.11%
CutVol 砍掉波动率相关特征 0.40 4.39%
Tushare 增强 加资金流向和行业信息 0.70 12.33%
Top-80 特征筛选 只保留历史重要性前 80 -0.07 -7.47%
三分类标签 前 30% / 中间 / 后 30% -1.08 -25.77%
行业覆盖修复 行业覆盖率 4.7% 到 100% 0.18 -0.48%

这些数值都是阶段性回测结果,不是官方排名,也不是投资建议。

最反直觉的地方

这个项目里有几个结论挺反直觉。

第一,砍特征会变好。波动率特征在有数据泄露时很显眼,修复以后反而拖累模型。它提醒我:特征重要性不是永恒真理,它依赖验证方式。

第二,特征筛选会变差。Top-80 版本训练速度快了很多,但跨年份效果不行。金融数据里,过去重要的东西不一定在未来继续重要。

第三,修行业覆盖率也会变差。行业数据从 4.7% 修到 100%,按理说 metadata 更干净了,但模型把 sector_id 看得太重,结果反而下降。

这三个结果都说明一件事:量化模型不能靠直觉验收,必须靠严格回测验收。

我现在更认可的路线

如果重新做一遍,我不会一上来就堆很复杂的深度结构。

我会先用 LightGBM LambdaRank 做一个干净、快、可解释的基线,把下面这些东西做扎实:

  • 日期分组和标签隔离;
  • 滚动回测;
  • 去最优月后的稳健性检查;
  • 特征重要性漂移;
  • 单次改动的 ablation;
  • 预测结果的权重约束。

等这些都稳定以后,再考虑把深度模型拿回来做融合。

这个项目最后让我更相信:比赛里真正高级的地方,不是模型名字听起来多厉害,而是你能不能证明自己的结果不是碰巧、不是偷看未来、不是被某个奇怪特征骗了。