FactorZen · 贡献指南
FactorZen 是以因子库准入为核心的量化研究平台。优先接受能提升研究可信度、可复现性、测试判别力与文档准确性的改动。
在动手之前,先读三条设计铁律——PIT 无未来函数、护栏咬合、可复现。冲突时它们是裁决依据,评审也按它们来。
本项目没有全局 Python。 所有命令都经 pixi 执行,不要用系统 python / pip,也不要装全局包。
pixi install
cp .env.example .env # 填入 TUSHARE_TOKEN(仅真实数据拉取需要)
pixi run smoke任务定义在 pixi.toml 的 [tasks],常用的几个:
| 任务 | 展开 | 用途 |
|---|---|---|
pixi run fz |
python -m factorzen.cli.main |
CLI 入口 |
pixi run lint |
ruff check . |
全仓 lint |
pixi run typecheck |
mypy |
全量类型检查 |
pixi run typecheck-fast |
dmypy run -- src/factorzen |
增量类型检查(daemon,首跑后秒级) |
pixi run test |
pytest tests/ -n auto |
全量测试 |
pixi run coverage |
python tools/run_coverage.py |
全量测试 + 覆盖率门槛 |
任意包内的命令都可以用 pixi run -- <cmd> 直接跑,例如 pixi run -- pytest tests/test_x.py -v。
不同阶段跑不同强度的检查。每层都不能省,但可以降频。
改代码的循环里只跑与改动直接相关的测试,用 -x -q 快速失败:
pixi run -- pytest tests/lift/test_lift_test.py::test_lift_admission_four_branches -v
pixi run -- pytest tests/ -k "pit or neutral or membership" -x -q
⚠️ 绝不在前台跑无界的全量 pytest——套件有 952 个用例、97 个测试文件,历史上出现过挂死。全量只在合并前跑,且必须带超时。
pixi run lint
pixi run typecheck两条都必须扫全仓:lint 包含 tests/,typecheck 覆盖整个 src/factorzen。
⚠️ 只 lint 自己改的模块会漏掉别处,push 之后在 CI 上炸。迭代频繁时可以用pixi run typecheck-fast(mypy daemon)顶替,但合并前必须跑一次冷typecheck兜住 daemon 的 stale 状态。
timeout 900 pixi run testℹ️ 全量测试不能被定向测试替代。本仓库最大的一类缺陷是双路径漂移——同一份语义有两处实现,改一侧忘了另一侧。这类问题定向测试抓不到,只有全量能抓。所以策略是「降频,不是省略」:一批改动合并前跑一次,而不是每次编辑都跑。
pixi.toml 里有 format 任务,但它是 ruff format .,会一次性改动数百个文件、把真实改动淹没在格式 diff 里。
✅ 格式问题按
pixi run lint的报错逐处修。ℹ️ 例外:
.pre-commit-config.yaml里的ruff-formathook 只作用于你已暂存的 Python 文件,与全仓 format 是两回事,启用 pre-commit 是安全的。
如本机装了 pre-commit,可启用本地钩子(版本与 CI 一致,都经 pixi run):
pre-commit install工作区常年存在未跟踪的本地文件——行情数据、缓存、研究产出、草稿。批量 add 会把它们卷进提交。
✅ 精确
git add你改的那几个文件。 提交前先git status --short过一眼。
| 改动性质 | 流程 |
|---|---|
| 新模块 / 新能力 | 分支 + PR + CI 绿后 merge |
| 跨模块重构 | 分支 + PR |
改承重文件(daily/evaluation/backtest.py、cli/main.py、discovery/lift_test.py 等) |
分支 + PR |
| 单文件小 bug 修复 | 可直接 master |
| 文档 / 单测补充 | 可直接 master |
⚠️ 判不准就当「大改」走 PR。 误开 PR 的成本是几分钟,误直推 master 的成本是回滚一条已被别人拉走的历史。
PR 合并后删除 head 分支。栈式 PR 要注意:下层合并并删除分支时,GitHub 会直接关闭依赖它的上层 PR——先把上层 rebase 到 master 再重建。
遵循 Conventional Commits,正文可以用中文:
fix(discovery): lift 裁决 SE 非有限时改为 reject——修静默放行
feat(intraday): 日内特征叶子接入挖掘全链(零回归)
perf(risk): 风格因子一次物化 + numpy OLS 替换逐日回归
docs: CLI 参考对齐 parser 真实参数
test(backtest): 补空权重表 carry 语义的回归断言
常用类型:feat / fix / perf / refactor / test / docs / chore。
一个逻辑一个 commit。 顺手的格式调整、无关的重命名不要塞进功能 commit。
同时开多条线时用 git worktree 隔离,不要在同一个 checkout 里来回切分支——工作区里的未跟踪产物会互相污染。
修 bug 先写一个能复现问题的失败测试,再动实现。这不只是流程洁癖:没有先失败过的测试,无法证明它真的在测那个 bug。
本仓库反复踩过的坑是恒真断言:用 A 和 B 构造出 C,然后断言 C == f(A, B)——这永远成立,零判别力。
✅ 用独立公式、外部 ground truth、或反例来验证。等价性重构要拿旧实现的拷贝当 golden 逐值比对。
⚠️ 「测试全绿」不等于「测试有效」。把契约字段改成Optional之后,原本有判别力的断言可能被静默恒真化——改契约时要 grep 全部直接引用逐个审。
任何触及收益、价格、成交约束、样本切分的改动,都必须在 PR 里说明是否可能引入未来函数,并补相应回归测试。自查清单:
- universe 是逐日快照,不是期末快照。
- 财务数据按公告日对齐,不按报告期。
- 执行定价用
pre_close,不用当日收盘。 - 滚动因子要扩窗预热(
expanded_start)。 - 预处理的统计量只用 ≤t 的样本。
- t 日算出的信号在 t+1 执行。
平台里有若干处「同一语义、两套实现」的配对(如模拟交易与向前执行的信号执行时点、挖掘护栏与 Agent 护栏的 passed 判定、单因子链路与报告链路的回测参数)。**改任一侧必须检查配对侧,两侧都要有测试。**新增第二条路径时,必须同时加一致性测试。
解析外部输入(LLM 输出、因子表达式、配置文件)时只抛 ValueError 一类异常。except ValueError 接不住 IndexError——一条畸形输入不许崩掉整个 session。
单股票、全 NaN、样本数小于分组数、近常数序列都要显式守卫:秩相关在 n=2 时恒为 ±1,分组回测的空腿会变成裸头寸,E[x²]−E[x]² 的微负值开方会让 NaN 穿透下游。
- 框架代码 →
src/factorzen/ - 用户可扩展因子 →
workspace/factor_store/<market>/<name>/(meta.json + factor.py)
模板由 fz factor new <name> --frequency <freq> 生成,写法见因子编写。
行情数据、运行产物、日志、notebook checkpoint、.env、任何 token。数据一律落 data/,研究产出一律落 workspace/,两者都已 gitignore。
CI 在向 main / master 的 push 与 PR 上触发,跑 3 个实质步骤:
| 步骤 | 命令 |
|---|---|
| Lint | pixi run lint |
| Type check | pixi run typecheck |
| Test + Coverage | pixi run coverage |
coverage 任务本身就是全量 pytest(-n auto + pytest-cov),测试失败即红,所以不需要额外的 Test 步骤。覆盖率门槛为 74%,低于门槛直接失败。
任一步骤红即 fail。本地按三层验证跑过,CI 基本不会有意外。
PR 模板 .github/PULL_REQUEST_TEMPLATE.md 里的自查项请如实勾选——特别是「无未来函数」与「无凭据入库」两条。
- 对外文档(
README.md/docs/)按能力组织,不暴露内部里程碑代号与过程术语。 - 写文档或示例前,先跑
pixi run -- fz <cmd> --help对照src/factorzen/cli/的真实 parser,不要凭印象写命令。help 里承诺的行为、报错里引用的旗标,必须真实存在。 - 已知限制就地标注在对应能力旁,不要用模糊措辞掩盖。MVP 阶段的东西如实写成 MVP。
- 每份文档职责单一,互相链接而非互相复述。
普通问题走 GitHub Issues。
安全问题不要公开披露,处理方式见 SECURITY.md。