当前位置:首页>python>我的量化模型骗了我四次:一个 Python 量化复盘血泪史

我的量化模型骗了我四次:一个 Python 量化复盘血泪史

  • 2026-09-23 17:20:36
我的量化模型骗了我四次:一个 Python 量化复盘血泪史

用 Python 揭秘均值回归策略:你的收益从何而来?

2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!

引言

很多刚学 Python 做量化的朋友,都会经历这样一个高光时刻:写完回测脚本,一运行,年化收益 50%,瞬间感觉自己离财富自由只差一个云服务器。

但真相往往很扎心——一个跑出超高收益的回测,通常不是发现了金矿,而是代码里埋了 Bug。

今天这篇文章,复盘的就是一个「跑不动」的周频动量模型的完整调试过程。作者本想做一个能给股票排序的模型,结果排序能力几乎等于抛硬币。在寻找原因的路上,他被自己的数字骗了整整四次,每一次「惊喜」最后都被证明是陷阱。最妙的是,真正的 Alpha(超额收益)其实从第一天起就存在,只是一直藏在市值阶梯的下一层。

对于学 Python 做数据分析、机器学习、量化的同学来说,这是一份关于「如何不被自己的代码欺骗」的绝佳教材。


先搞懂一个核心指标:Rank IC

判断一个排序模型好不好,关键指标叫 Rank IC(秩信息系数),它衡量的是「你预测的打分排名」和「实际发生的收益排名」之间的相关性。

  • • Rank IC = 0:你的排序就是抛硬币,纯随机
  • • Rank IC = 1:你是预言家
  • • 真实世界里,能稳定做到 0.02 ~ 0.05 已经很不错了,只要交易得当就能赚钱

而作者最初的模型只有 0.015,有些周甚至和 0 没区别。模型几乎分不清未来的赢家和输家。

接下来,就是四次「数字撒谎」的全过程。


谎言 1:印钞机般的回测(因为它偷看了明天的报纸)

早期版本的回测跑出了年化 50% 以上的收益。作者高兴了 30 秒,然后开始反胃——因为一个周频动量模型不可能在美股大盘股上做到 50%,连顶尖的量化机构都做不到。如果你的笔记本做到了,那一定是它在作弊。

Bug 出在选股票池的方式上。

作者的做法是:先筛选出「今天市值超过 100 亿美元」的公司,再把它们的历史价格拉回到 2021 年做回测。

听起来很合理,实则是剧毒操作。比如某只股票,今天是市值几十亿的大公司,所以进了名单;但在回测的那段历史窗口里,它其实只是一只从 0.11 美元涨到 1.54 美元的「仙股」。模型根本没预测到这个暴涨,它只是被人喂了一份「已经胜出」的名单。

这就是两个经典错误:

  • • 前视偏差(Look-ahead bias):用了当时根本不可能知道的信息
  • • 幸存者偏差(Survivorship bias):只研究赢家,因为输家(破产、退市的公司)从数据里消失了

验证方法非常朴素:保持模型完全不变,只把那些在窗口期是仙股的约 9% 的名字剔除掉,看会发生什么。

# 同一个模型,唯一的区别只是股票池不同

# 剔除掉在回测窗口期内属于仙股的股票

clean = [s for s in universe if not was_penny_during_window(s)]

dirty_return = backtest(universe).annual_return   # 污染版:+51%
clean_return = backtest(clean).annual_return      # 干净版:+24%

# 仅仅去掉一小撮仙股,一半的「业绩」就走人了

去掉一小撮被污染的票,收益直接腰斩,这就是铁证。真正的 Alpha 是分散在几百只股票上的,绝不会因为 9% 的名单消失就生死攸关。

正确做法是构建「时点正确(point-in-time)」的股票池:每一周只用当周已知的信息来决定哪些股票合格,并且把后来破产、退市的公司也包含进去。改完之后,虚高的 51% 收益坍缩到了 19%,连一段看起来惊心动魄的 27% 回撤也凭空消失了——因为它从来就不真实。

经验:一个好到离谱的回测,是一份 Bug 报告,不是一个发现。看到漂亮数字时,第一个要审查的不是模型,而是「你的数据是否让模型偷看了答案」。


中场补课:Sharpe 比率到底意味着什么

从这里开始,作者不再看「裸收益」,转而看 Sharpe 比率(夏普比率),因为脱离风险谈收益毫无意义。靠扛住 40% 回撤换来的 19%,和平平稳稳赚来的 19%,完全是两回事。

Sharpe 的本质就是:收益 ÷ 波动率,即每单位风险换来多少回报。这里有一份「速查表」:

  • • Sharpe ≈ 0.5:差不多就是长期买入并持有标普 500 的水平。还行,但你得吃下每一次崩盘(2008、2020)
  • • Sharpe = 1.0:好。一个真正可交易的量化策略
  • • Sharpe = 1.5:优秀。资金曲线平滑,亏损月份很少
  • • Sharpe ≥ 2.0:圣杯。但如果你在回测里看到,那几乎一定是 Bug 或前视泄漏(参见谎言 1)
  • • Sharpe < 0:你在花钱买「亏钱」这项服务

为什么要把及格线设在 0.5?因为 0.5 是「免费」的——买个指数基金然后去海边躺着就能拿到。一个策略要值得你搭数据管道、反复训练、承担隐藏 Bug 的风险,至少得跑赢「躺平」的收益。

记住「2.0 意味着 Bug」这条警告,后面还会再出现。


谎言 2:信号是真的,但我测量的时机完全错了

数据干净之后,Rank IC 诚实地弱到了 0.0014,基本就是噪声。作者差点在这里放弃。

然后他检查了一个一直「假设」却从未「验证」的东西:预测周期(horizon)。

他原本预测的是未来 5 天的收益,而 5 天恰恰是最糟糕的选择。这里涉及一个著名效应——短期反转(short-term reversal):在几天的尺度上,刚刚暴涨的股票往往会回吐一部分。而他想捕捉的「动量」,要到两周左右才真正发力。

于是他把预测周期从几天扫描到几周,发现信号在 5 天时几乎死透,在 12 ~ 16 天的区间里强了大约 10 倍。他一直把相机对准了一个主角根本不在的位置。换到 13 天周期后,Rank IC 爬升到了 0.015。

经验:「信号很弱」和「我测量错了信号」会产生一模一样的结果。在断定「没有 Alpha」之前,先确保你看的地方,是教科书说它应该出现的地方。


诚实的高墙:一个赚不到钱的真信号

接下来这段很扎心。即使数据干净、周期也对了,这个策略在大盘股上依然变不了现。作者把它拆开找原因:

第一步,做多空美元中性(dollar-neutral):做多最强的、做空最弱的,两边等额,抵消掉大盘的整体上涨。原本多头策略的 Sharpe 看起来有 0.55,去掉市场贡献后,真正的选股 Sharpe 只剩 0.28。一半多的「能力」,其实只是「大盘在涨,而我恰好满仓做多」。

第二步,做行业中性(sector-neutral):只允许模型在同行业内下注(最好的科技股 vs 最差的科技股),不许跨行业押注。波动率从 17% 暴跌到 5.5%。听着很棒,但原因很残酷——账户约 90% 的波动来自「行业押注」,而不是选股。模型的「本事」主要是发现「科技股热、能源股冷」。剥掉这部分,纯粹的同行业内选股 Alpha 很小,扣掉真实交易成本后基本是盈亏平衡。

结论很难写进自己的复盘里:大盘股上的 Alpha 确实存在,但它只是 Beta 和行业暴露,披着一件「选股能力」的外衣。


谎言 3:一个其实是「运气好掷了个骰子」的惊艳结果

有一个杠杆确实有用:EMA 平滑(指数移动平均)。与其按当天的原始打分排序,不如按过去 5 天打分的指数移动平均来排序。逻辑是:单日打分又跳又吵,平滑几天能让真信号显现,还能避免你因为一天的波动就疯狂调仓。

它真的有效。某一次运行中,平滑后的行业中性多空策略,净 Sharpe 达到了 0.57。在连续一周的盈亏平衡之后,作者承认自己很想相信这个数字。

而这恰恰是你最不该相信一个数字的时刻。

这里有个很多人不知道的真相:模型训练往往是非确定性的。像 LightGBM 这类梯度提升库,会为每棵树随机抽样行和特征,还会跨多个 CPU 线程按完成顺序累加梯度。而浮点数运算并不严格满足结合律,a + b + c 可能和 c + b + a 在最后一位小数上不同。对于这么微弱的信号,最后几位小数足以把结果推来推去。同样的代码跑两遍,会得到两个不同的 Sharpe。

所以在相信 0.57 之前,作者固定了随机性(设置随机种子、开启确定性模式),并跨多个种子重复实验,每个种子都是一个独立可复现的模型,来观察结果的分布。

# 这个结果是真的,还是我掷了个好骰子?

results = {col: [] for col in ["raw", "ema5", "ema8"]}

for
 seed in (0, 1, 2):
    # 每个种子都可复现的滚动训练

    oos = walk_forward(instruments, seed=seed)
    # 对信号做 5 日 EMA 平滑

    oos["ema5"] = ema(oos["pred"], span=5)
    for
 col in results:
        results[col].append(net_sharpe(oos, signal=col))

# 打印每种信号的均值、标准差和原始数据

for
 col, vals in results.items():
    v = pd.Series(vals)
    print
(f"{col}: mean={v.mean():+.2f} std={v.std():.2f} ({vals})")

输出结果:

raw    mean=-0.25  std=0.14  (-0.29, -0.36, -0.10)
ema5   mean=+0.14  std=0.13  ( 0.05,  0.09,  0.29)
ema8   mean=+0.19  std=0.30  (-0.09,  0.15,  0.51)

两个结论浮出水面:

  1. 1. EMA 平滑确实有用:每一个种子都比不平滑要好,而原始信号其实是负的。这部分很稳健。
  2. 2. 诚实的期望 Sharpe 大约是 0.15,而不是 0.57。那个让人兴奋的数字只是分布的上沿,相当于「反复跑直到掷出一个好结果再停手」。它的误差棒跨过了 0,根本没法说这个策略能赚钱。

这次实验还顺手揪出了一个生产环境的真 Bug:线上模型是非确定性重训的,意味着「是否发布信号」的开关,可能在完全相同的数据上无缘无故地翻转。固定随机种子修复了它。

经验:单次回测数字是一个样本,不是一个事实。如果你不能复现它、给它套上误差棒,那你拥有的不是结果,而是一则轶事。而且你越希望某个数字是真的,就越应该卖力地去弄死它。


谎言 4:「聪明钱」陷阱——流行不等于有预测力

当模型不给力时,人总想去找「魔法特征」。作者去了网上最火的地方:Smart Money Concepts(SMC,聪明钱概念)。

在短视频平台上,无数交易员对「订单块」「流动性扫单」「市场结构转变」深信不疑,说得像是击败市场的作弊码。

于是他做了测试:写了一个严格因果(无任何前视泄漏)的 SMC 检测库,识别波段高低点、标记订单块、测量到流动性池的距离。然后做了一次干净的 A/B 测试——同样的 5 折交叉验证,一次用基础特征,一次额外加上 SMC 特征。

结果是一次干净利落的统计学拒绝:

特征集
Rank IC
ICIR
Decile Spread
基础(161 个特征)
+0.0079
+0.078
+0.0140
+ SMC(165 个特征)
+0.0053
+0.052
+0.0177

加上 SMC 特征后,核心排序指标 Rank IC 下降了超过 30%,信息比率也被拖低。它给了模型更多过拟合的空间,而不是更多信号。

经验:在让任何流行指标接近你的实盘组合之前,先用对抗的方式去检验它。如果数据说它更差,就删掉,不管有多少「大师」告诉你相反的话。社交媒体上的百万播放量,不会给一个指标赋予数学上的优势。


真相揭晓:Alpha 一直藏在市值阶梯的下一层

到这一步,作者已经用诚实的测量,排除了在大盘股上变现的每一种方法。瓶颈不在策略构建,而在原始信号强度——大盘股就是没多少 Alpha。它们是地球上被分析得最透彻、定价最有效的股票。动量在那里几乎失效再正常不过:所有人都已经跑过你这个模型了。

于是他换了个问题:在更小、更少人关注的股票里,Alpha 是不是更强?他不需要新数据,直接把已有的预测按市值分桶,测量每个桶内的 Rank IC。

# Alpha 是否依赖市值?

# 为每一行附上其时点正确的市值数据

merged = pd.merge_asof(
    oos.sort_values("datetime"), caps.sort_values("date"),
    left_on="datetime", right_on="date",
    by="instrument", direction="backward")

# 按不同市值区间分桶统计

for
 name, lo, hi in [("small $0.5-2B", 5e8, 2e9),
                     ("mid $2-10B", 2e9, 1e10),
                     ("large $10-50B", 1e10, 5e10),
                     ("mega >$50B", 5e10, 1e18)]:
    b = merged[(merged.market_cap >= lo) & (merged.market_cap < hi)]
    ts = tear_sheet(b)
    print
(f"{name}: rank_IC={ts['rank_ic_mean']:+.4f}")

输出结果:

small $0.5-2B : rank_IC=+0.0573   decile_spread=+0.0406
mid   $2-10B  : rank_IC=+0.0203   decile_spread=+0.0157
large $10-50B : rank_IC=+0.0157   decile_spread=+0.0047
mega  >$50B   : rank_IC=+0.0241   decile_spread=+0.0003

梯度完美符合有效市场假说:公司越小,模型预测的赢家和输家之间的差距就越大。

最小的那一桶看起来无比惊艳。但作者已经吃过太多次「惊艳」的亏——那些微盘股,又一次是「后来长成大盘股」的幸存者,幸存者偏差正在虚高它们。没有一份幸存者干净的小盘股数据集,这个数字不可信。

但中盘股那一桶,既有潜力又站得住脚。于是他做了真正的检验:单独导入一个 2 ~ 10 亿美元的中盘股票池,专门训练一个中盘模型(而不是让一个全局模型摊薄在所有市值上),并跑完整套机器——行业中性、EMA 平滑、多种子误差棒、以及更真实的中盘交易成本(因为流动性更差)。

中盘专用模型,行业中性多空,EMA-5,真实 30bps 成本,5 个种子:
净超额 Sharpe = +0.81 +/- 0.23  (每个种子都为正,最差也有 +0.61)

Sharpe 在 0.8 量级,市场中性,每个种子都为正。然后是真正说服他的那一步:他只在「今天仍然是中盘股」的票上重跑,扔掉所有「长成了大盘股」的赢家。如果这是幸存者偏差,它会崩塌。结果它稳稳守在 +0.77。这东西是真的。

当然还有最后一道诚实的门槛:他仍然缺少那些退市或归零的中盘股,补上这个洞需要更好的数据源。但这是整个项目里,第一次出现一个「经受住了他所有破坏尝试」的结果。


总结

这整个故事,给学 Python 做数据与量化的我们留下了几条价值千金的纪律:

  1. 1. 太好看的回测是 Bug 报告,不是发现。看到惊艳数字,先查数据是否泄漏,再查模型。
  2. 2. 构建股票池一定要「时点正确」,把破产退市的公司也算进去,否则前视偏差和幸存者偏差会联手骗你。
  3. 3. 「信号弱」和「测错了」长得一模一样。在放弃前,确认自己测量的周期、口径是否正确。
  4. 4. 拆解你的收益来源:把 Beta(大盘)、行业暴露剥离干净,剩下的才是你真正的选股能力。
  5. 5. 单次结果是样本,不是事实。固定随机种子,跨多个种子跑出误差棒——你越想相信一个数字,就越要卖力地去弄死它。
  6. 6. 流行 ≠ 有效。对任何网红指标都要做对抗式 A/B 测试,数据说差就删。
  7. 7. 设一条「免费」的及格线(比如 Sharpe 0.5,躺平的水平),对 2.0 以上的数字本能地保持怀疑。

那个动量 Alpha 从第一天起就存在,作者只是不断地测错它,又不断地被自己的测错结果欺骗。四次,屏幕上的数字撒了谎,每次谎言都是同一个形状:它看起来比现实更好,而它看起来越好,你就越想相信它。

最后他唯一信任的数字,是那个在他反复、刻意、不怀好意地想让它消失之后,依然为正的数字:+0.77。

在这个游戏里,「真 Alpha」和「自我幻觉」之间的差别,往往就是一个你忘了跑五遍的数字。


参考文章

加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。

财经数据与量化投研知识社区

2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:

  1. 1. 双典系统赋能:获赠《财经数据宝典》与《量化投研宝典》完整文档,凝练多年实战经验,构建系统化知识框架;
  2. 2. 量化因子日更教程(2026重磅新增):每日更新「量化因子专题教程」,配套完整可运行代码与实战案例,深度拆解因子构建、回测与优化全流程;
  3. 3. 量化文章专题教程库:500+篇星球独有高质量教程式文章,系统覆盖策略开发、因子研究、风险管理等核心领域,内容基本每日更新,并配套精选学习资料与实战参考;
  4. 4. 量化投研实战课程:赠送《AKQuant-入门及实战》《PyBroker-入门及实战》视频课程,手把手教学,快速掌握量化策略开发技能;
  5. 5. 财经数据支持:定期更新国内外财经数据,为策略研发提供精准、可靠的数据基础;
  6. 6. 顶尖学者与行业专家分享:年度邀请学术界博士与业界资深专家开展前沿论文精讲与实战案例分享,不少于4场,直击研究前沿与产业实践;
    专家直连答疑:与核心开发者及领域专家实时互动,高效解决投研实战难题;
  7. 7. 专业社群与专属福利:加入高质量交流社群,获取课程折扣及更多独家资源。

星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!

好文推荐

1. 用 Python 打造股票预测系统:Transformer 模型教程(一)

2. 用 Python 打造股票预测系统:Transformer 模型教程(二)

3. 用 Python 打造股票预测系统:Transformer 模型教程(三)

4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)

5. 揭秘隐马尔可夫模型:因子投资的制胜武器

6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用

7. 金融 AI 助手:FinGPT 让你轻松掌握市场分析

8. 量化交易秘籍:为什么专业交易员都在用对数收益率?

9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解

10. 掌握金融波动率模型:完整 Python 实现指南

好书推荐

最新文章

随机文章