← 返回模块
4.2.1.4beta 可读 · 未来付费校验通过内容版本 2026-05-24

研究工具链与可复现性

4.2.1 · 研究工作流程与纪律 · 量化全流程

一位 私募 量化 团队 的 资深 研究员 在 原 研究 PR 上 线 半 年 之后 把 报告 递 给 一 位 初级 队友。"重新 跑 一 遍。基金 经理 在 问 这 个 信号 在 2024 年 数据 上 是否 还 work。" 初级 从 共享 盘 拉 出 notebook 打 开,第一 个 错误 立 刻 撞 上 来:ImportError: cannot import name 'X' from 'pandas'。本 机 上 pandas 版 本 是 2.4;notebook 是 1.5 时 写 的。初级 pip install pandas 1.5 —— 另 一 个 单元格 又 挂 了,因为 numpy 版本 现在 不 兼容。两 个 小时 依赖 战争 之后,notebook 跑 通 了,但 算 出 的 数 与 报告 里 不 一致。种子 没 记 录;共享 盘 上 的 数据 文件 自 原始 跑 之后 被 触 动 过;原始 代码 状态 的 git commit SHA 找 不 到。"结果" 无法 复现。资深 研究员 走 回 基金 经理 那 里。"我们 没 有 一 个 可 复现 的 信号。我们 有 一 个 信号 的 记忆。" 本 课 是 模块 的 工程 capstone —— 让 L1 实验 日志 可 审计、让 L2 测试集 上锁 可 执行、让 L3 DSR 半 年 后 可 被 另 一 研究员 验证 的 那 一 层。本 节 结束 后,你 应当 能 产出 一 个 队友 可以 从 单 一 命令 重 跑 的 研究 PR。

六 层 经典 研究 栈

1. notebook vs script                       - Jupyter for exploration; .py for production; transition within two weeks
2. version-pinned dependencies              - pyproject.toml + uv.lock / poetry.lock / requirements.txt with hashes
3. seeded RNGs                              - every randomness source seeded; seed logged per run
4. git + feature-branch + pull-request workflow - every project a feature branch; result is a PR
5. experiment tracking                      - mlflow / wandb / SQLite log of hyperparameters / seed / data window / metrics / artefact path / git_commit_sha
6. code-review checklist                    - five binary checks enforced at PR review

规则:每 一 个 研究 结果 都 必须 能 从 单 条 命令 复现,给 定 锁 定 的 依赖、锁 定 的 数据 快照、记 录 的 种子 与 git commit SHA。六 层 是 让 这 条 规则 可 执行 的 工程 管 道。第 (1) 层 把 想法 生成(notebook)与 结果 主 张(脚本)分 开;第 (2) 层 冻结 运行 时;第 (3) 层 冻结 随机 性;第 (4) 层 把 一 个 结果 打 包 为 带 版本 的 diff;第 (5) 层 记 录 谱 系;第 (6) 层 在 合 并 时 落实 纪律。

研究 PR 的 八 项 artefact

每 一 个 进 入 仿真 交易 的 研究 结果 都 以 pull request 形 式 发 货。PR 按 此 顺序 打 包 八 项 artefact:

(a) notebook                           - frozen at result-claim moment
(b) production script                  - regeneratable from CLI
(c) experiment log                     - CSV / SQLite / mlflow run-ids
(d) pre-registration document          - the L1 six-field template, committed at project start
(e) in-sample result
(f) single out-of-sample evaluation result
(g) multiple-testing correction        - Bonferroni / BH-FDR / DSR with N counter from the experiment log
(h) write-up                           - the curated narrative for human review

规则:PR 不 merge 即 研究 结果 不 成 立;代码 评审 清单 不 过 即 PR 不 merge。八 项 artefact 闭 合 前 几 课 的 全 链:(d) 接 L1 预登记;(e) 与 (f) 接 L2 测试集 单 次 触碰 纪律;(g) 接 L3 修正;(c) 是 (g) 的 试验 计数器 源;(a) 与 (b) 是 L4 notebook-vs-脚本 纪律;(h) 是 结果 给 人 看 的 那 一 面。

实验 日志 schema

实验 日志 的 最 小 schema 是 SQLite 里 一 张 runs 表。九 列,按 此 顺序,SQL 类型 固 定:

CREATE TABLE runs (
    run_id              TEXT    PRIMARY KEY,
    timestamp           TEXT    NOT NULL,
    hyperparameters_json TEXT   NOT NULL,
    seed                INTEGER NOT NULL,
    data_window         TEXT    NOT NULL,
    metric_name         TEXT    NOT NULL,
    metric_value        REAL    NOT NULL,
    artefact_path       TEXT,
    git_commit_sha      TEXT    NOT NULL
);

表 名 runs、九 列 名 按 此 顺序(run_idtimestamphyperparameters_jsonseeddata_windowmetric_namemetric_valueartefact_pathgit_commit_sha)以及 SQL 类型 跨 区域 字节 一致。git_commit_sha 列 让 日志 不可 伪造 —— 每 一 个 指标 都 与 一 个 具体 的 代码 状态 绑 定。声 称 在 某 个 git_commit_sha 处 Sharpe 等 于 2.0 的 那 一 行 可以 重 复 验证:git checkout <sha>、恢复 数据 快照、设 种子、重 跑、比 对。指标 对 不 上,那 行 就 是 谎言。这 一 列 的 不可 伪造 性 给 了 下 文 代码 评审 清单 真正 的 牙 齿。

五 项 代码 评审 清单

1. test set evaluated exactly once                    - search the experiment log for runs that touched the test set; expect exactly one
2. seed logged for every run                          - search the log for null seeds; expect zero
3. data window justified in pre-registration          - the window in the pre-registration document matches the window in the result
4. universe is survivorship-bias-free                 - the universe definition references `universe(date, symbol)` from 4.1.1 L4
5. multiple-testing correction applied with the experiment-log N - DSR / Bonferroni / BH-FDR reported alongside the headline metric using the actual count of trials from the log

规则:任 一 项 不 过 即 阻 塞 PR 合 并 至 修复;这 是 L1 + L2 + L3 的 工程 落地。检查 (1) 是 L2 测试集 上锁 的 工程 牙 齿。检查 (2) 是 复现 性 地 板 —— 没 有 记 录 种子 的 run 无法 字节 一致 地 重 现。检查 (3) 是 预登记 一致 性 检查 —— 窗口 与 预登记 不 一致 的 结果 要么 是 未 标 注 的 偏离(即 +1 进 多重 检验 修正 的 试验 计数器),要么 是 文档 bug;任 一 情况 PR 都 不能 合 并 直至 差异 解 决。检查 (4) 是 4.1.1 L4 的 survivorship-free universe;检查 (5) 是 用 实验 日志 N 让 L3 修正 可 审计。

代码:seed 一切

def seed_all(seed: int) -> None:
    """Seed every randomness source the project might touch.

    Two runs with the same seed must produce byte-identical metrics on the same
    hardware and the same lock-file environment.
    """
    import os
    import random
    import numpy as np
    random.seed(seed)
    np.random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    try:
        import torch
        torch.manual_seed(seed)
        if torch.cuda.is_available():
            torch.cuda.manual_seed_all(seed)
    except ImportError:
        pass
    try:
        import tensorflow as tf
        tf.random.set_seed(seed)
    except ImportError:
        pass

函数 名 seed_all、参数 seed: int、随机 源(randomnumpyPYTHONHASHSEEDtorchtorch.cudatensorflow)以及 规则 "two runs with the same seed must produce byte-identical metrics on the same hardware and the same lock-file environment" 跨 区域 字节 一致。可 选 依赖(torchtensorflow)的 try / except ImportError 让 函数 在 缺 库 环境 里 仍 然 安全。

代码:log 一 次 run

def log_run(run_id, hyperparameters, seed, data_window,
            metric_name, metric_value, artefact_path):
    """Insert one row into the experiments.db `runs` table.

    Retrieves the current git commit SHA via subprocess; binds the metric to the
    specific code state so the row cannot be faked retroactively.
    """
    import json
    import sqlite3
    import subprocess
    import time
    sha = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip()
    conn = sqlite3.connect('experiments.db')
    conn.execute(
        "INSERT INTO runs VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
        (run_id, time.strftime('%Y-%m-%dT%H:%M:%S%z'),
         json.dumps(hyperparameters), seed, data_window,
         metric_name, metric_value, artefact_path, sha),
    )
    conn.commit()
    conn.close()

函数 名 log_run、参数 名、SQLite 文件 名 experiments.dbsubprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip() 取 git commit SHA 与 规则 "每 一 个 写 入 任何 报告 的 指标 都 必须 能 追 溯 回 这 张 表 里 的 一 行" 跨 区域 字节 一致。

代码:pyproject.toml 骨架

[project]
name = "alpha-research-momentum"
version = "0.1.0"
description = "5-day momentum signal research for 510300"
requires-python = ">=3.11"

[project.dependencies]
numpy = ">=1.26"
pandas = ">=2.2"
scikit-learn = ">=1.4"
matplotlib = ">=3.8"
mlflow = ">=2.10"
jupyter = ">=1.0"

[tool.uv]
lock-file = "uv.lock"

YAML 键([project][project.dependencies][tool.uv])、依赖 名(numpy、pandas、scikit-learn、matplotlib、mlflow、jupyter)以及 Python 版本 要求(>=3.11)跨 区域 字节 一致。规则:lock 文件(uv.lock)和 pyproject.toml 一 起 提交 到 git;部署 用 uv pip sync 对 lock 文件。没 有 lock 文件,"version-pinned dependencies" 只 是 口 号 —— numpy>=1.26 允许 任 何 未 来 numpy 版本 包 括 破坏 性 更新;lock 文件 把 完整 传 递 闭 包 冻 死。

复现 一 个 结果 的 四 项 经典 输入

1. the locked dependency set   - uv.lock or poetry.lock or requirements.txt with hashes, committed to git
2. the locked data snapshot    - S3 / OSS partitioned parquet with a fixed version tag, referenced in the config
3. the logged seed             - from the experiment log
4. the git commit SHA          - from the experiment log; reproduce by `git checkout <sha>`

规则:每 一 个 研究 结果 都 必须 能 从 单 条 命令 复现,给 定 这 四 项 输入;另 一 研究员 给 定 这 四 项 输入 仍 不能 复现,那 这 个 结果 就 不可 复现,也 就 不 是 研究 结果。单 条 命令 通常 是 python run_experiment.py --config=<config-id>make repro-<run-id>;四 项 输入 是 让 这 条 命令 在 其中 跑 的 那 个 确定 性 宇宙 的 配置。

经典 capstone 视角:本 课 是 capstone —— 它 把 L1 实验 日志、L2 测试集 上锁、L3 试验 计数器 与 DSR 串 起 来,打 包 进 让 整 个 栈 可 复现 的 工程 管 道 里。下 文 的 capstone 练习 围 绕 这 一 综合 设计。

Formula Explorer

\text{repro\_score} = w_1 \cdot \text{lock} + w_2 \cdot \text{snapshot} + w_3 \cdot \text{seed} + w_4 \cdot \text{sha}

纪律 总结

四 项 经典 输入 都 能 从 单 条 命令 恢复 的 研究 项目 是 可 复现 的;输入 散 落 在 个人 笔记本、未 打 tag 的 共享 盘 文件夹、未 锁 版本 的 conda 环境 里 的 项目 不 是。策略 的 夏普比率、相对 基准 的 信息比率、净 值 曲线 的 最大回撤、部署 之后 的 Alpha 衰减、对 价值 / 质量 / 动量 因子 的 因子模型 归因、对 2007-2008 / 2015 股灾 / 2018 trade-war / 2020 疫情 / 2022 房地产 drawdown 的 压力测试、下游 4.4 的 均值方差优化、组合优化 —— 只 有 当 它们 都 绑 定 到 实验 日志 里 的 git_commit_sha 时,每 一 项 指标 才 可 信。没 有 绑 定 时,指标 是 记忆;有 绑 定 时,指标 是 研究 结果。

买 方 在 2015-2018 年 之间 完成 了 这 套 栈 的 采 用 —— 量化 私募 龙头(明汯、幻方 量化、中诚、灵均、九坤 投资)、公募 量化 部门(天弘 / 富国 / 华夏 / 嘉实 / 工银瑞信)以及 卖 方 系统化 桌 子(中信 系统化、华泰 QIS)。2018 年 López de Prado 《Advances in Financial Machine Learning》中文 版 与 2016 年 Harvey-Liu-Zhu 论文 设 定 了 学术 地板;AMAC 中国 证券 投资 基金业 协会 关于 私募 研究 日志 与 信息 披露 的 要求 设 定 了 监管 地板;量化 龙头 的 工程 严格 度 设 定 了 实战 地板。

练习

Exercise

你 正在 给 一 项 关于 510300 沪深 300 ETF 的 5 日 动量 信号 研究 创建 研究 PR。按 顺序 产出 四 项 工程 artefact 并 报告 答案。

(i) 写 一 份 完整 的 pyproject.toml 骨架,列 出 六 项 高 阶 依赖(numpy>=1.26, pandas>=2.2, scikit-learn>=1.4, matplotlib>=3.8, mlflow>=2.10, jupyter>=1.0)与 Python 版本 要求(>=3.11),用 一 句话 说 明 uv.lock 文件 做 了 pyproject.toml 不 做 的 什么 事。

(ii) 写 出 实验 日志 最 小 schema 的 SQL CREATE TABLE runs (...),含 九 列(run_id, timestamp, hyperparameters_json, seed, data_window, metric_name, metric_value, artefact_path, git_commit_sha)与 类型;用 一 句话 说 明 为 什么 git_commit_sha 列 是 不可 伪造 的 那 一 列。

(iii) 写 一 个 Python seed_all(seed: int) 函数,seed randomnumpyPYTHONHASHSEED 以及 可 用 时 的 torch;演示 用 seed=42 跑 两 次 后 再 算 np.random.rand(3) 返回 同 样 的 三 个 数。

(iv) 把 五 项 代码 评审 清单 应 用 到 一 个 假想 研究 PR 上:该 PR 声 称 在 510300 2022-2023 窗口 上 样本外 Sharpe 1.8、N=10 次 试验;对 五 项 检查 每 一 项 说 明 该 主 张 是 PASS 还 是 FAIL(假定 实验 日志 显示:测试集 触碰 1 次、10 次 run 全 部 记 录 种子、数据 窗口 与 预登记 一致、universe 来 自 4.1.1 L4、报告 DSR = 0.92 配 N=10);每 一 项 用 一 句话 论证 裁定。

把 全部 四 个 答案 作 为 一 套 打 包 artefact 报告。

提示
pyproject.toml 声 明 依赖 区间;uv.lock 冻 死 版本 + 哈希 闭 包,保 证 字节 一致 安 装。git_commit_sha 把 指标 与 代码 状态 绑 定,可 由 git checkout <sha> 重新 验证。
提示
N=10 + DSR=0.92:修正 应 用 正确,DSR 在 suggestive 带。五 项 检查 全 PASS。PR 可 合 并 —— suggestive(非 strong)DSR 下 应 进 入 影子 交易。

研究 栈 的 一 天 —— 各 层 如何 组 合

一 个 具体 的 日 常 研究 闭 环 走 法,把 经典 研究 栈 的 各 层 按 序 调 用。研究员 周一 开 一 条 新 特性 分支 —— research/momentum-510300-2024-q2。先 提交 预登记 文档:L1 的 六 字段 模板,填 入 阈值 规则 Sharpe > 1.0 and DSR > 0.95 → paper-trade for one quarter; otherwise abandon,试验 计数器 初 始化 为 1。pyproject.tomluv.lock 同 一 个 commit 提交;下 一 个 人 uv pip sync 即 得 与 原 研究员 完全 相 同 的 numpy、pandas、scikit-learn、matplotlib、mlflow、jupyter 版本。

周二 是 2015-2021 窗口 上 的 样本内 探索。研究员 打 开 EDA notebook;data/test/ 分区 在 文件 系统 层 上锁,测试集 无法 被 意 外 打 开。每 试 一 个 变体,notebook 调 log_run(...)experiments.db 里 插 一 行,含 种子、数据 窗口、指标 与 当前 git commit SHA。到 周五 下午,实验 日志 有 五十 行;预登记 时 计数器 为 1,现在 是 50;增量 是 L3 DSR 公式 看 到 的 N。

下 周 一 研究员 冻结 样本内 调参、破封 测试集 恰好 一 次、用 实验 日志 N=50 计算 样本外 Sharpe + DSR。结果:Sharpe 1.4、DSR 0.88 —— suggestive 不 strong。项目 启动 时 预登记 的 决策 规则 自动 触发:Sharpe > 1.0 → 影子 交易 一 季 度;DSR suggestive → 保守 仓 位。研究员 开 一 个 研究 PR 打 包 八 项 artefact:notebook、生产 脚本、实验 日志 转 储、预登记 文档、样本内 结果、单 次 样本外 结果、配 N=50 的 多重 检验 修正、写 入 报告。PR 模板 要求 五 项 评审 检查 全 部 勾 上;评审 一 确认 单 次 测试集 触碰;评审 二 确认 50 个 种子 全 记 录;评审 三 确认 窗口 与 预登记 一致。PR 周五 合 并;影子 交易 簿 周一 开 仓。

六 个 月 后 基金 经理 问 信号 在 2024 数据 上 是否 还 work。新 研究员 拉 出 合 并 PR,git checkout <sha> 到 合 并 commit,uv pip sync uv.lock,从 实验 日志 取 种子,把 脚本 指 向 新 的 2024 数据 快照,重 跑 生产 脚本。2024 Sharpe 落 在 0.6;信号 已 衰减。决策 规则 再 触 —— abandon。两 次 跑 都 可 从 单 条 命令 复现;两 次 跑 全 链 可 审计。这 是 六 层、八 artefact、SQLite schema、五 项 评审 检查 与 四 项 输入 组 合 起 来 的 产 出。少 任 一 件,六 个 月 后 的 跟 进 都 做 不 到。

参考 卡

本 课 装 配 的 组件,按 序:

  • Inline-code listing —— 六 层 经典 研究 工具 栈。
  • Inline-code listing —— 研究 PR 的 八 项 artefact。
  • Fenced ```sql code block —— 实验 日志 最 小 schema(含 九 列 的 runs 表)。
  • Inline-code listing —— 五 项 代码 评审 清单。
  • Fenced ```python code block —— seed_all(seed: int)
  • Fenced ```python code block —— log_run(run_id, hyperparameters, seed, data_window, metric_name, metric_value, artefact_path)
  • Fenced ```yaml code block —— pyproject.toml 骨架 与 六 项 依赖。
  • Inline-code listing —— 复现 一 个 结果 的 四 项 经典 输入。
  • Exercise —— 四 项 研究 PR artefact 综合 练习,配 两 条 渐进 Hint。
  • FormulaExplorer —— 复现 评分 组 合 公式。

模块 结尾

模块 完结。四 节 课 合 成 一 套 持 久 纪律。L1 把 工作流 写 进 书面 合同 —— 七 阶段、六 字段 预登记、四 项 artefact、四 个 诊断 问题。L2 把 数据 纪律 机制 讲 精确 —— 三 分区、四 切 法、四 泄漏 模式、五 项 泄漏 检测 清单。L3 把 试验 计数器 转 成 通缩 概率 —— Bailey-Lopez de Prado 公式、三 项 修正、DSR 阈值 分 层、五 种 p-hacking 形 式。L4 把 上 述 三 课 包 进 工程 —— 六 层 栈、八 项 PR artefact、含 git_commit_sha 的 SQLite schema、五 项 代码 评审 清单、四 项 复现 输入。同 时 尊重 四 课 的 研究 项目 产 出 队友 可 从 单 条 命令 重 跑 的 结果,配 试验 计数器、配 DSR、配 书面 假设。少 任 一 课 的 项目 产 出 一 个 记忆。下 一 个 模块 4.2.2(信号 构 建)讲 的 是 建 什么 信号;本 模块 讲 的 是 怎么 诚 实 地 测试 信号,让 买 方 信任 你 发 货 的 东西。