【AI+量化实战 #07】回测收益曲线很漂亮,上线就亏钱?让AI帮你揪出未来函数与过拟合

风险提示:本文仅用于技术演示,不构成投资建议。历史回测结果不代表未来收益。

你有过这种经历吗?

本地回测曲线一路向上,年化收益 80%、最大回撤不到 5%,兴冲冲拿到模拟盘,三个月亏掉 15%。打开代码一看:信号里悄悄用了 shift(-1)——用明天的收盘价决定今天买不买。

这不是段子,而是回测里最常见的 P0 级错误:前视偏差(lookahead bias)。今天的文章教你用一套「AI 回测审计员」工作流,在策略上线前自动扫描两类致命问题:

  1. 前视偏差 / 未来函数:用了当时还不知道的信息;
  2. 过拟合:参数在样本内被优化得太好,样本外直接崩塌。

本文你将得到什么

  • 一个故意带未来函数的"假策略",以及它的修正版;
  • 一段可直接运行的 Python 审计脚本,既能规则审计,也能接大模型审计;
  • 一个 holding-days 网格搜索示例,直观展示 in-sample 与 out-of-sample 的差距;
  • 上线前的自查清单,帮你避开 90% 的回测坑。

一、先构造一个"看起来很美"的假策略

下面的策略逻辑非常简单:

未来函数版(错误示例,请勿实盘)

df["future_close"] = df["close"].shift(-5)
df["signal"] = df["close"].shift(-1) > df["close"] # 明天涨,今天买
df.loc[df["signal"], "return_h"] = (
df.loc[df["signal"], "future_close"]
/ df.loc[df["signal"], "close"] - 1
) * 100

问题在哪里?

  • shift(-1) 把明天的收盘价拿到了今天做决定;
  • 实盘里,今天收盘前你不可能知道明天涨不涨。

这个错误隐蔽性极强,因为代码能跑通、曲线也常常很好看。我们用 5 只蓝筹股(600519、000001、000333、002594、601318)拉取近 180 天日线回测,得到的结果如下(示例数据,非实时):

代码 未来函数版平均收益 修正版平均收益
600519 1.04% 0.83%
000001 1.30% 1.54%
000333 1.17% 1.29%
002594 1.94% 1.92%
601318 0.21% 0.41%

可以看到,未来函数版并不总是更高——但它在逻辑上就是错的。一旦信号和收益计算混用未来信息,整份回测的可信度归零。

二、修正版:用 lag-1 信号

正确的写法是用昨日收盘相对前日收盘的涨跌作为今天开盘买入的信号:

修正版(lag-1 信号)

df["prev_return"] = df["close"].pct_change() # 昨日涨跌
df["signal"] = df["prev_return"] > 0 # 昨日上涨,今日开盘买
df.loc[df["signal"], "entry"] = df.loc[df["signal"], "open"]
df.loc[df["signal"], "exit"] = df.loc[df["signal"], "close"].shift(-5)
trades["return_h"] = (trades["exit"] / trades["entry"] - 1) * 100

这样信号生成只依赖已经收盘的 t-1 日数据,不会再提前看到未来。

三、让 AI 当审计员:规则 + LLM 双保险

有了两份代码后,可以把它们丢给一个审计 Prompt:

你是一名量化回测审计员。请审查以下两个 Python 回测函数,
找出前视偏差(lookahead bias)、未来函数、过拟合风险。

股票代码:{code}
未来函数版平均收益:{buggy_return:.2f}%
修正版平均收益:{fixed_return:.2f}%

【未来函数版代码】

【修正版代码】

请按以下格式输出:
1. 发现的致命问题(P0)
2. 发现的中等风险(P1)
3. 修复建议
4. 是否建议直接用于实盘:是/否,理由

如果你的环境没有配置大模型 key,脚本会自动 fallback 到规则审计,至少能帮你标出:

  • 是否出现 shift(-N) 这类未来函数;
  • 未来函数版收益是否显著高于修正版;
  • 是否缺少手续费、滑点、印花税。

四、过拟合:参数优化得越好,实盘越可能崩

前视偏差是"用错数据",过拟合是"用对数据但自欺欺人"。

下面我们对 holding-days 做 1~20 天的网格搜索,用前 50% 数据找最优参数,再用后 50% 数据验证:

代码 最优持有期 in-sample 收益 out-of-sample 收益
600519 1 天 0.68% 1.09%
000001 20 天 2.07% 2.88%
000333 20 天 3.77% 2.72%
002594 19 天 3.13% 0.66%
601318 1 天 0.22% 1.03%

以 002594 为例,in-sample 选出的 19 天持有期收益 3.13%,但 out-of-sample 只剩 0.66%。这就是过拟合的典型症状:你在样本内找到了一条刚好拟合噪声的曲线。

五、可运行的审计脚本

完整代码已整理到本系列 Demo 目录,核心依赖:

pip install mairui pandas numpy requests

import os
import pandas as pd
import mairui

licence = "LICENCE-66D8-9F96-0C7F0FBCD073" # 演示证书,演示证书(读者复制即用,无需替换)
client = mairui.Client(licence)

拉取日线(示例:不复权)

rows = client.stock_history(code="600519", period="d", st="20260101", et="20260814", dividend="n")
df = pd.DataFrame(rows)
# 字段映射:t=日期 o=开 c=收 h=高 l=低 v=量
df = df.rename(columns={"t": "date", "o": "open", "c": "close", "h": "high", "l": "low", "v": "volume"})

脚本包含三个模块:buggy_backtest、fixed_backtest、overfit_demo,以及一个 call_llm 入口。未配置 LLM 时自动 fallback 到规则审计,保证本地可运行。

六、上线前自查清单

把下面这张表打印出来,策略上线前逐项打勾:

  • 所有信号都只使用买入时刻之前已收盘的数据;
  • 不存在 shift(-N)、df.loc[t, "future_col"] 等未来函数;
  • 留出 out-of-sample 数据集,且结果与 in-sample 差距不超过 30%;
  • 已加入手续费(万 1~万 3)、滑点(0.01%~0.05%)、印花税(卖出千 1);
  • 参数扫描范围有投资逻辑支撑,不是纯数据挖掘;
  • 回测周期覆盖牛、熊、震荡三种市场状态;
  • 有第三方或大模型审计过代码,并留下审计记录。

小结

回测不是算命,而是"在已知历史里验证规则是否自洽"。一旦规则里混进未来信息,或者参数被优化到刚好拟合历史噪声,结果就不再是自洽验证,而是自我欺骗。

AI 在这里最好的角色不是替你生成圣杯策略,而是做一名不知疲倦的审计员:帮你逐行检查 shift(-1)、提醒你做 out-of-sample、追问手续费去哪了。

下篇预告:【AI+量化实战 #08】多因子打分会算不会说?让 AI 把得分翻译成选股理由。


代码与接口文档参考:https://github.com/MaiRuiApi

posted @ 2026-09-12 09:37  量化分析码农  阅读(21)  评论(0)    收藏  举报