打包成开源库:第一个成品的交付
16 个测试通过 → 构建 wheel → 装进全新环境 → 冒烟测试跑通
P4 章走到这里,qlab 已经有了事件、撮合、账户、回测四个模块,七条撮合规则、六条账户行为、双路对账、与 backtrader 对拍。
但它现在还只是你目录里的一堆 .py 文件。
这一节把它变成一个真正的库:
【一】跑测试套件 16 passed
【二】构建 wheel qlab-0.4.0-py3-none-any.whl (10686 字节)
【三】装进全新的干净环境 import qlab 成功,版本 0.4.0
冒烟测试 回测跑通:期末权益 908,055
第三步是关键:在一个不含源码目录的全新虚拟环境里 import qlab,回测跑通——这才叫"能给别人用"。
你在自己厨房做出一道菜,和把它做成能寄给别人的预制菜,是两回事。
后者要考虑:别人家没有你的调料架(依赖)、别人的锅不一样(Python 版本)、别人不知道你放了多少盐(默认参数)、你改了配方要通知他(版本与变更日志)。
"在我机器上能跑"和"是一个库",中间隔着这一节。
一、测试套件:把 P4 各节的验证汇总
16 个测试,分四组:
| 组 | 数量 | 覆盖 |
|---|---|---|
test_matching_* | 8 | 涨跌停、停牌、T+1、整手、资金约束、限价单、印花税仅卖出 |
test_account_* | 3 | T+1 锁定与解锁、均价含费、清仓归零与已实现盈亏 |
test_engine_* | 4 | 无未来函数、双路对账、成本单调、可复现 |
test_regression_* | 1 | 锁住已修复的 bug |
全部不依赖行情数据——用合成价格序列,毫秒级跑完,可以挂在每次提交上。
写这套测试时,测试自己抓了我一次。
test_engine_strategy_cannot_see_today 我第一版写的断言是:
assert seen == list(range(len(df))) # [0, 1, 2, ..., 59]
跑出来失败了:实际是 [1, 2, ..., 59]。
原因:第 0 天没有任何历史,引擎压根不调用策略函数。是我的断言错了,不是引擎错了。
写测试时对被测代码的假设,同样需要被验证。
这次是测试抓住了我;如果我当时"顺手"把断言改成宽松一点的写法(比如只检查递增),这个认知偏差就会一直留在我脑子里。
回归测试:锁住已修复的 bug
def test_regression_records_unaffordable_lot():
"""p4.1 修复:想建仓但买不起一手时,必须记为被拒,而不是静默跳过。
这个 bug 的危害是把「你没钱建仓」伪装成「策略选择空仓」——
净值曲线一模一样,结论完全不同。
"""
df = make_bars(200, start=500.0) # 高价股
r = run_backtest(df, always_in, cash=3000) # 一手 5 万,只有 3000 元
assert any("一手" in k for k in r.log.summary()["被拒原因分布"])
每修一个 bug,配一个回归测试。否则三个月后的重构会把它悄悄放回来——而且这类 bug 不会报错。
🔧 动手做:亲手改坏引擎,看测试网兜住它(6 分钟)
16 个测试全绿,是不是就是走个仪式?你来验证它到底有没有用——改坏引擎,看测试抓不抓得住。
打开 qlab/engine/matching.py,把 limit_pct: float = 0.10(涨跌停 10%)改成 1.0(等于关掉涨跌停),跑 python -m pytest tests/ -q。
你会看到(实测):立刻两条变红——test_matching_limit_up_blocks_buy 和 test_matching_limit_down_blocks_sell FAILED。
想明白:16 个测试不是给你看"全绿"的摆设,是在你(或半年后的你、或帮你改代码的 AI)把引擎改坏时,立刻报警的那张网。一道永远绿的测试等于没测(p0.3);正因为你一改坏它就红,这张网才是真的。这就是为什么成品要打包成带测试的库——不是为了好看,是为了以后动它时不心虚。
二、pyproject.toml:把假设写进配置
[project]
name = "qlab"
version = "0.4.0"
requires-python = ">=3.11"
dependencies = ["pandas>=2.2", "numpy>=1.26"]
[project.optional-dependencies]
dev = ["pytest>=8.0"]
plot = ["matplotlib>=3.8"] # 画图才需要
data = ["baostock", "akshare"] # 取数才需要
可选依赖分组是个容易被忽略的细节:核心库只依赖 pandas + numpy,别人不画图就不用装 matplotlib。
一个只想跑回测的人,不该被迫装上 akshare 和它的一长串依赖。
三、CHANGELOG:量化库的一条额外规矩
语义化版本大家都知道:主版本改 API、次版本加功能、修订号修 bug。
但量化库有一条额外的规矩:
任何会改变回测结果的修改,即使 API 完全不变,也必须升次版本并写进 CHANGELOG。
因为使用者会拿新版本重跑旧策略。结果变了却没提示,比 API 不兼容更危险——API 不兼容会立刻报错,结果变化不会。
本次 0.4.0 就有这么一条:
### 变更(**会改变回测结果**)
- `backtest`:新增「资金不足一手,无法建仓」的拒单记录。此前该情形被静默跳过,
回测显示为「策略选择空仓」——净值不变,但结论完全不同(p4.1)
CHANGELOG 里还必须有已知限制一节:
- 单标的回测;多标的组合需自行聚合(P5 处理)
enforce_t1在日频单次调仓下不绑定(结构性原因,p4.1)- 分红口径固定为"后复权 + 不单独入账"(p4.5)
把限制写在明处,比假装没有限制更专业。
四、构建与验证:三步全流程
【一】跑测试套件 16 passed
【二】构建 wheel Successfully built dist/qlab-0.4.0-py3-none-any.whl (10686 字节)
【三】装进全新环境 + qlab==0.4.0 (from file:///.../qlab-0.4.0-py3-none-any.whl)
冒烟测试 import qlab 成功,版本 0.4.0
回测跑通:期末权益 908,055,{'成交笔数': 21, '被拒笔数': 0}
第三步必须在"不含源码目录"的地方跑。
如果你在项目根目录下 import qlab,Python 会直接找到源码文件夹——即使你的包配置完全是错的,它也能 import 成功。
正确做法:
uv venv /tmp/qlab_fresh建一个全新环境- 从 wheel 文件安装(不是
pip install -e .) cd /tmp切到别处,再 import
只有这样,你验证的才是别人拿到 wheel 之后的真实体验。
这和 p4.9 对拍是同一个思路:引入外部视角,而不是自己验自己。
五、README:先说它不做什么
大多数 README 只讲功能。本库的 README 有一节叫“它没做什么”:
## 它没做什么
- 不支持多标的组合(P5 章处理)
- 不接实盘下单通道(**这是刻意的**)
- 不保证任何收益
一个量化库的可信度,一半来自它承认自己不做什么。
六、这就是第一个成品
回到 p0.1 承诺的三个成品:
| 成品 | 状态 |
|---|---|
| ① 自研回测引擎 | ✅ 本节交付 |
| ② 因子评价体系 | P2.7 已给出方法,P5 整合进库 |
| ③ 模拟交易系统 | P8 章 |
qlab 0.4.0 现在具备:
- 架构级防未来函数(p4.3)
- 七条 A 股撮合规则 + 单元测试(p4.4)
- 账户与盈亏分解(p4.5)
- 双路对账(p4.6)
- 与 backtrader 对拍(p4.9)
- 被拒委托记录——向量化回测里不存在的信息(p4.1)
📌 免责:本课为技术教学,
qlab为教学用途的开源实现,不构成投资建议,不保证任何收益。
P4 章完结,这门课的第一个成品交付。
这一节做的四件事:
- 测试套件 16 个——撮合 8 + 账户 3 + 引擎级 4 + 回归 1,全部不依赖行情数据、毫秒级跑完
- pyproject 的可选依赖分组——只想跑回测的人,不该被迫装 akshare
- CHANGELOG 的额外规矩——任何改变回测结果的修改都必须升版本,因为"结果变了却没提示"比 API 不兼容更危险;外加必须写"已知限制"
- 三步验证:测试 → 构建 wheel → 在不含源码的全新环境里从 wheel 安装并冒烟测试
外加一次"测试抓住了我":test_engine_strategy_cannot_see_today 的断言我写错了(以为策略从第 0 天开始被调用)。写测试时对被测代码的假设,同样需要被验证。
P4 章十节回顾:选型 → 参数 → 信号层 → 撮合层 → 账户层 → 双路对账 → 绩效 → 可视化 → 交叉对拍 → 打包。每一节都留下了一段可运行的代码,而不只是一个知识点。
下一章 P5 开始用这个引擎做策略:规格书、多因子、参数优化与参数平原、滚动前推验证、蒙特卡洛置信区间、成本与容量、衰减监控、风险平价、风险预算。
那一章会反复用到 P3 的统计纪律——因为有了可信的引擎之后,下一个瓶颈就是"你怎么知道这个策略不是搜出来的"。
🔎 来源与核验· 3 条,点开核对
