causal-memory

2026-09-29

系列·肆。让 LLM 写因子表达式是当下最时髦的玩法,也是最容易翻车的:AI 生成的因子里混进未来函数、常量列、幸存者偏差,回测曲线漂亮得不像话——因为确实是假的。这篇讲 factor-miner-mcp 怎么用工程纪律把这道关卡住。

因子的一生

LLM 生成因子代码
      ↓
沙箱执行(factor_execute)        ← 隔离环境跑代码,防 os.system/网络/文件
      ↓
评估(factor_evaluate)           ← IC / IR / 分位单调性 / 半衰期 / t 值
      ↓
OOS 准入(样本外验证)            ← 训练窗调参,验证窗定生死
      ↓
入库 + 日频计算                   ← 进因子库,每日增量更新
      ↓
衰减巡检                          ← IC 掉到阈值以下报警,因子退役

这套流程对应三个 MCP 工具:factor_execute(沙箱跑代码,只回契约检查)、factor_evaluate(把因子值变成专业指标)、factor_tearsheet(完整报告)。三个工具拼起来解决一个真实断链:沙箱不回因子值、tearsheet 要自带数据,中间必须有一个"代码 → 指标"的翻译层。

真实演示:同一套评估,有预测力和纯噪声差多少

不需要 qlib 数据也能看懂评估层在干什么。构造两个合成面板(250 交易日 × 50 标的):一个有真实预测力(次日收益 = 0.002 × 当日因子 + 噪声),一个是纯噪声,都过 analytics.factor_tearsheet:

== 有预测力因子 ==
ic.mean                    =  0.9968        ← 截面秩相关,接近 1
ic.ir                      =  748.46        ← IC 均值/IC 标准差,稳定性
分位收益 1d(q1→q5)       =  -0.0027 → -0.001 → 0.000 → 0.0011 → 0.0027
                              (严格单调递增——因子方向清晰)
多空 1d 年化                =  1.3723        ← q5-q1 价差年化
单调性 monotonic            =  True (increasing)

== 纯噪声因子 ==
ic.mean                    = -0.0030        ← 零附近晃
ic.ir                      = -0.02          ← 没有稳定性可言
多空 1d 年化                = -0.0008       ← 无价差

真实世界的因子 IC 能稳定在 0.03–0.05 就够吃肉了,这里的 0.99 是合成数据的理想值,拿它是为了让你看清评估指标的形状:单调的分位收益、正的 IC、够高的 IR。实盘里见到"IC 0.2+ 且分位严格单调"的因子,第一反应应该是查未来函数,而不是庆祝。

单调性判定有个细节值得学:rho > 0.9 不等于单调,服务里 quantile_monotonicity 要求严格递增/递减才给 monotonic: True——0.9 的"几乎单调"在契约测试里明确必须为 False。

三条工程纪律(这才是这个项目的核心价值)

1. 沙箱不只是防恶意,更是防"无意的越界"。 LLM 生成的因子代码里出现 import os、读环境变量、发网络请求,大概率不是攻击而是模型瞎写。test_sandbox_idioms.py(6 项测试)钉住了哪些 API 在沙箱里可用、哪些必须拒绝。沙箱执行只回 {eval_ok, eval_detail} 契约检查结果,不回因子值——取数和评估分离,杜绝"评估代码和被评估代码串通"。

2. 退化输出必须显式,不许装成正常结果。 test_degenerate_outputs.py(11 项测试)钉了两个真实事故:vol_forecast 纯 EWMA 数学上天然平坦(无均值回复),永远拿不到期限结构,后来补了 AR(1) 均值回复路径;predict 缺输入时曾恒返回 signal=0.0 / confidence=0.5,会被上层误当成真实模型输出——现在返回 insufficient_input + null。宁可报错,不返回看起来像答案的数字。

3. OOS 准入是唯一信任来源。 因子在训练窗里调参,在样本外窗口验证——假设缺失只是"标记"(不影响拿指标),但必填检查是入库闸门,不是评估闸门。因子入库后还有衰减巡检:IC 序列滚动下滑到阈值以下就报警退役。逻辑和上一篇新闻管线的"宁报错不返回脏数据"一脉相承。

跑全量回测需要什么

本文的合成演示不需要真实数据,但完整链路需要 qlib 的 cn_data(instruments/all.txt 也是 astock-data-mcp 全市场截面的本地代码表来源):

pip install pyqlib
qrun configs/data_collector  # 或用现成的社区镜像数据
python3 server.py --port 50055   # factor-miner-mcp 起服务

Docker 镜像里已带好 qlib 环境和依赖(wheels/ 目录锁了版本),服务器上一键起。

下一篇换个体裁:不挖因子了,用 Kronos 基础模型直接对 K 线做零样本预测。

项目地址:github.com/JingxuanC/factor-miner-mcp

Comments (0)

Sign in with GitHubto leave a comment.