⚠ 交易存在重大亏损风险。本网站的所有内容仅供教育用途,不构成财务建议。
第 13 课,共 13 课 · 免费

从数据到报告的完整研究笔记本项目,附针对前视偏差和过度拟合的代码审查清单

把一项完整的策略研究组织成可复现的项目,从原始数据一直运行到书面结论,并用一份能发现“美化回测”错误的清单审查代码。

作者: Moustafa Hijazi内容审核日期: 2026-10-30阅读约 8 分钟自学课程

你将学到什么

  • 一种文件夹和笔记本的布局,让六个月后的你或任何其他人都能复现这项研究。
  • 工作顺序:数据、清洗、特征、信号、回测、绩效指标、样本外检验、报告。
  • 一份逐行检查前视偏差、幸存者偏差和过度拟合的代码审查清单。
  • 如何诚实地撰写结果,包括尝试过但没有奏效的内容。

讲解

样本外测试、参数平台以及大量试验的代价已在课程《系统化交易与回测》(第 6 课和第 7 课)中讲过。本课关注的是让这些理念真正落地的工程实践:项目结构、固定的工作流程,以及一次他人可以重复的审查。

项目布局

  • project/
  • data/raw/ # 下载的数据,从不手动编辑
  • data/clean/ # 由 01_data.ipynb 生成的 parquet 文件
  • notebooks/ # 01_data, 02_features, 03_backtest, 04_report
  • src/ # indicators.py, engine.py, metrics.py(第 6、8、9 课)
  • tests/ # test_metrics.py, test_engine.py
  • config.yaml # 代码、日期、参数、成本假设
  • requirements.txt # 锁定的版本:pandas==2.x, numpy==…

复用的代码放在 src/ 中;笔记本只负责调用函数和展示结果。锁定软件包版本,因为一次 pandas 更新就可能改变某个默认设置(频率字符串、重采样标签),从而改变你的数字。为所有随机过程设定种子,并在报告中记录数据的下载日期。

按顺序进行的工作流程

  1. 先提出假设。在接触数据之前,先写下一句经济学逻辑和确切的规则(课程《系统化交易与回测》,第 2 课)。现在就确定样本内 / 样本外的划分,并在第 7 步之前不要打开样本外文件。
  2. 数据。下载、保存原始数据,记录数据来源和复权方法(第 4 课)。用一份“今天的指数成分股”名单回测过去十年,就是幸存者偏差。
  3. 清洗与验证。缺口、重复、零价格、时区;保存到 data/clean/。
  4. 特征与信号使用 src/ 中经过测试的函数;第 t 根 K 线上的每个特征只使用截至 t 的数据。
  5. 回测使用第 8 课的引擎,并包含成本模型(课程《系统化交易与回测》,第 8 课)。
  6. 绩效指标与图表(第 9 课和第 10 课),在样本内期间上计算,使用一个小型参数网格,而不是去搜索最好的那一格。
  7. 样本外只运行一次,参数保持冻结。无论结果如何都要记录下来。
  8. 报告:假设、数据、规则、成本、两张结果表、图表、尝试过的变体数量、决策。

代码审查清单

把你的笔记本当作别人的来审查;每一行都是一个“是或否”的问题。

领域 检查项 典型修复
时序 每个信号在设定仓位之前是否都位移了一根 K 线? pos = signal.shift(1)
时序 滚动窗口是否使用 center=False(默认值)且没有 bfill()? 去掉居中设置和向后填充
时序 标准化处理(z 分数、排名、缩放器)是否只在过去的数据上拟合? 使用扩展窗口或滚动窗口拟合
数据 品种范围是否按每个日期当时所知的情况来定义? 使用时点成分股名单,或给出诚实的说明
数据 是否在相关场景中用复权价格计算收益率、用未复权价格计算止损距离? 同时保留两列
成交 跳空是否按开盘价成交,而不是按止损价或信号价格成交? min(open, stop)
成本 佣金、点差和滑点是否按每笔交易、每个方向计入? 在配置中设置成本模型
过度拟合 尝试过多少组参数、多少条规则和多少个品种范围?报告中是否写明了这个数量? 记录每一次运行
过度拟合 相邻的参数是否给出相似的结果(平台)? 绘制参数网格热力图
过度拟合 样本外数据是否被动用过不止一次? 如果是,它现在已属于样本内;寻找新的数据
可复现性 “Restart kernel and run all”(重启内核并运行全部)能否复现报告中的数字? 修复隐藏状态和单元格顺序

关于显著性的经验法则:年化夏普比率的 t 统计量大约等于 夏普比率 × √年数。四年期间 0.8 的夏普比率得出 1.6,低于通常约为 2 的门槛,而这还没有考虑尝试过的变体数量;在二十次试验中,最好的那一次预计仅凭运气就会显得不错。

实例演算

假设示例,所有数字均为编造。一位交易者在 30 只大盘股上测试“在 100 日突破时买入,以 3 × ATR 移动止损离场”,期间为 2014–2025 年,每个方向成本 0.1%。

  1. 划分。样本内为 2014–2021 年(8 年),样本外为 2022–2025 年(4 年),在第一次运行之前就已确定。
  2. 网格。突破长度 50、100、150 × ATR 倍数 2、3、4 = 9 个变体,均有记录。样本内夏普比率介于 0.6 到 1.1 之间,最好的一格为 (100, 3) = 1.1;其相邻格为 0.9 和 1.0,形成一个平台,因此保留这一选择。
  3. 审查。清单检查发现,ATR 列使用了 rolling(14).mean(),并基于当天的真实波幅来设定当天的止损。引擎随之改为使用前一根 K 线的 ATR(第 8 课)。样本内夏普比率从 1.1 降至 1.0;最大回撤保持 −22% 不变。
  4. 样本外。运行一次:夏普比率 0.6,CAGR 7.2%,最大回撤 −19%,118 笔交易。显著性:0.6 × √4 = 1.2;样本内数值为 1.0 × √8 = 2.8。样本外结果为正,但单独来看在统计上较弱。
  5. 报告中的决策。“该规则在样本外保留了约 60% 的样本内夏普比率(0.6 / 1.0),处于一个平台上,共尝试 9 个变体,修复了一个前视偏差错误。证据有限;下一步是使用第 12 课的脚本进行六个月的模拟交易,而不是投入真实资金。”已对手续费建模;不计税费和融资成本。

常见错误

“只是看一眼”样本外期间每看一次保留的数据、每在之后做一次调整,都会把它变成样本内数据。如果你在看到 2022–2025 年的结果后改变了 ATR 倍数,诚实的选择只有两个:报告该测试已不再是样本外测试,或者寻找新的数据,例如其他市场或此后已经过去的月份。请在报告中写明划分日期以及样本外运行的执行日期。

练习活动

在纸上完成:审查下面这段笔记本代码以及随后的结果。

  • df['z'] = (df.close - df.close.mean()) / df.close.std()
  • df['signal'] = df.z < -1.5
  • df['pos'] = df.signal.astype(int)
  • df['ret'] = df.close.pct_change() * df.pos

6 年样本内夏普比率为 2.4;2 年样本外夏普比率为 −0.3。(a) 找出代码中的两个前视偏差问题。(b) 计算样本内和样本外夏普比率的近似 t 统计量。(c) 改写这四行代码,使其不存在前视偏差(如果你更喜欢用文字,也可以描述改动)。

显示答案

(a) 第一,df.close.mean() 和 df.close.std() 使用了包括未来价格在内的整个样本,因此任何日期的 z 分数都“知道”未来的平均值。第二,pos 没有位移:今天的收益是依据今天收盘价计算出的信号赚取的。

(b) 样本内:2.4 × √6 = 5.9;样本外:−0.3 × √2 = −0.4。样本内的高数值是由这些错误造成的,这就是它崩溃的原因。

(c) 使用滚动窗口或扩展窗口,并进行位移:

  • m = df.close.rolling(250).mean(); s = df.close.rolling(250).std()
  • df['z'] = (df.close - m) / s
  • df['signal'] = df.z < -1.5
  • df['pos'] = df.signal.shift(1).fillna(False).astype(int)
  • df['ret'] = df.close.pct_change() * df.pos

局限性:消除前视偏差只会让测试变得诚实,而不会让它盈利;修正后的规则仍然需要计入成本、使用带止损的引擎,以及一个全新的样本外期间。

要点回顾

  • 把可复用的代码连同测试放在 src/ 中,锁定版本,并让“全部运行”能够复现报告。
  • 在第一次回测之前确定假设、规则以及样本内 / 样本外的划分;样本外只运行一次。
  • 用清单审查时序、数据、成交、成本和试验次数;记录你尝试过的每一个变体。
  • 以 夏普比率 × √年数 粗略报告显著性,同时报告变体数量和决策,包括“证据不足”。

下一步

至此,课程《面向交易者与投资者的 Python》全部结束;下一门课程《自营交易公司、资助账户与作为一门生意的交易》将探讨考核规则、回撤类型,以及把交易作为一门生意时的实务问题和德国税务问题,而像你在这里构建的这样一套经过测试、脚本化的流程,正是其起点。

自测

回答问题后核对答案。你可以随意重试多次。

1. 某策略在 5 年样本外期间的年化夏普比率为 0.9。使用粗略规则 t ≈ 夏普比率 × √年数,t 统计量是多少,应如何解读?

2. 在一项研究中,研究者运行了样本外期间,看到结果较弱,于是修改了离场规则,重新运行后得到了更好的结果。第二个结果属于什么性质?

3. 笔记本中的哪一行最明显地引入了前视偏差?

供您自行复习的自动自测。您的答案在浏览器中核对,我们不会查看或评估。这不是考试,也不是资格证书。

您的进度

“已完成”仅表示您将本课标记为已学完,并不是对您理解程度的测试。 学习进度和笔记仅保存在此设备的此浏览器中,而非账户中。