Files
fidc-backtest-engine/docs/open-source-trading-engine-review-20260906.md
T
2026-09-06 05:39:14 +08:00

134 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FIDC 开源回测与交易引擎设计审查
## 审查范围
本次审查直接读取以下只读参考源码。源码位于
`/Volumes/T7-Data/WorkSpace/reference-trading`;原计划使用的
`/Volumes/SystemSSD` 在审查时未挂载,因此没有向本机系统盘写入参考仓库。
| 项目 | 审查提交 | 重点 |
|---|---|---|
| NautilusTrader | `ac22d5cf4a7e` | Rust 事件内核、统一回测/实盘组件、时间事件堆 |
| QuantConnect LEAN | `23b735d99a35` | 订阅同步、TimeSlice、Universe 生命周期、惰性集合 |
| Microsoft Qlib | `79633dd9506e` | 表达式缓存、日历切片、内存/磁盘分层 |
| vectorbt | `34b6d5935e3e` | NumPy/Numba 密集数组仿真、紧凑状态数组 |
| Zipline Reloaded | `943010b9da84` | Pipeline DAG、窗口预取、分段执行、复权读取 |
| Backtrader | `b853d7c90b67` | preload/runonce 与逐 bar/live 模式分离 |
## 可借鉴设计
### 1. 时间轴和执行状态必须统一,但热路径不必经过通用消息总线
NautilusTrader 的 `BacktestEngine` 复用数据、执行、风险和缓存组件,并用带稳定
序号的最小时间事件堆推进多个时钟。LEAN 用 `SubscriptionSynchronizer` 将不同订阅
合并到同一个 frontier,再生成唯一 `TimeSlice`。两者共同证明:回测和实盘应共享
订单、风控和时间语义,而不是共享一段策略特例代码。
FIDC 已经以 `decision_date``execution_date`、调度时间和撮合时间构成统一执行合同,
并由同一交易核心服务于回测、模拟盘和实盘。日线全市场选股不应改为逐字段消息
广播;这会给每个股票状态增加分配和动态分派。通用事件总线只保留在订单、成交、
配置审计及外部集成边界。
### 2. 批量研究计算和事件撮合必须使用不同执行形态
vectorbt 把纯数值组合压入连续数组和编译循环;Backtrader 在历史批量模式使用
`preload + runonce`,进入 live/replay 或受限内存模式后关闭该路径。Zipline 的
Pipeline 则先生成 DAG 执行计划,按依赖顺序计算,并允许按日期 chunk 控制内存。
FIDC 应继续保持:
- 日线基础特征、rolling、横截面 rank 和因子挖掘使用 Arrow/NumPy/DuckDB/紧凑数组;
- 订单、成交、T+1、涨跌停、停牌、公司行动和现金流使用确定性事件撮合;
- 不把向量化收益外推到存在订单状态和路径依赖的撮合过程;
- 不让逐 bar 实盘语义退化成预先知道整个未来数组的批量回测语义。
### 3. 不可变数据按内容身份共享,策略结果和可变账户状态严格隔离
Qlib 的缓存层、Zipline 的预取窗口和 Backtrader 的优化数据预载都说明:相同历史
数据不应由每个策略重复解码。FIDC 当前 Source Lake 的 Parquet/Arrow、冻结 query
scope、内容寻址 bundle、进程内 `DataSet` 和共享 result-store block 已符合这个方向。
共享键必须包含完整数据代际、字段投影、PIT 截止时间、复权口径和窗口。禁止共享:
- 选股结果、订单、仓位、账户、风控决策和策略局部变量;
- 缺少 manifest/SHA/PIT 身份的 DataFrame 或 dict
- 盘中 provisional 数据与正式收盘数据混用的缓存项。
### 4. 字段和因子要在计划阶段冻结,运行时只物化真正需要的数据
LEAN 的 `TimeSliceFactory` 复用空集合,并只在收到对应数据时创建集合;Zipline 的
Pipeline 使用执行计划和 refcount 释放中间值。FIDC 已有字段投影、
`DailySnapshotView`、rolling requirement、Factor DAG 和 numeric bytecode VM。
后续优化必须扩展这些类型化计划,而不是恢复宽 Python 行或每次构建完整 map。
## 当前性能事实
2026-09-06 在 177 使用同一冻结五年策略得到:
- restart/cold`17.985s`;数据准备 `14.659s`;引擎 `2.627s`
- process-hot:总耗时中位 `3.245s`;数据准备 `0.006s`;引擎 `2.629s`
- 26,088 笔成交、收益 `0.9219861819172002`、canonical SHA
`b42fea66237d06eadb24f6b8c9e2760e7319fe3699f315b99e01f433ef2aa234`
在所有重复运行中一致;
- 当前剖面热点为 numeric VM `12.39%`、临时股票状态构建 `10.17%`、复权均线
`6.82%`
- Source Lake/进程共享缓存已经把同 bundle 数据准备从 `14.659s` 降到 `0.006s`
所以再把 DataFrame 改成 dict 或扩大 DuckDB 连接数不是当前热路径优化。
两项受开源类型化执行启发的候选已真实验证并删除:
1. 编译期枚举化全部 VM 标识符仅减少约 `1.44%` instructions,五年引擎中位只改善
`0.19%`,却净增约 562 行;
2. VM 槽位代际复用仅减少约 `0.48%` instructions,五年引擎中位回退约 `0.27%`
完整证据见 `docs/evidence/numeric-vm-binding-generation-rejection-20260906.json`
## FIDC 后续优化顺序
### P0:冷数据路径一次构建、分段发布、跨策略共享
以冻结 bundle 的完整身份构建 canonical base panel,并按年份或有界日期段发布
只读 mmap/Arrow 段;父任务只扫描一次 Source Lake,worker 只映射所需段。每日增量
只生成变化尾段,历史段保持同 SHA。必须以 process-cold `14.659s` 为基线,证明
冷启动下降且 RSS、PIT、复权和结果 SHA 不变。
### P1numeric VM 使用类型化 helper opcode 或经证据支持的 super-instruction
当前字符串标识符绑定不是主要成本。下一候选应在编译期把常见 rolling helper、
比较和短路组合编译为类型化 opcode,减少解释器指令数,同时保留 helper 懒求值和
错误信息。必须对主策略、第二种持仓数策略、通用自定义排序策略和分钟策略分别 A/B。
### P1:结果事件按消费者需求分层
撮合事实保持完整不可变;页面摘要、曲线、持仓和交易视图从 typed result store
按需投影。禁止每次回测向 PostgreSQL 写入大矩阵,也禁止为了列表或概览解码全部
事件。优化目标是 `resultSeconds + finalizationSeconds`,不能删减审计事实换速度。
### P2:分钟线有界流式窗口
参考 Zipline 的窗口预取和 Nautilus 的有序事件迭代器,按时间段加载分钟
RecordBatch,保持持仓、订单和指标 ring buffer 有界;不能一次展开全市场全区间
分钟 Python 对象,也不能在 chunk 边界丢失 corporate action、T+1 或订单队列状态。
## 明确不采用
- 不为单次策略把日循环拆成多线程;路径依赖会增加同步开销并破坏确定性。
- 不通过增加 worker、DuckDB 槽位或扩大 HTTP 窗口掩盖单任务热点。
- 不把全量历史数据复制成每个 worker 独占的 dict/DataFrame 缓存。
- 不直接引入另一个框架的事件总线、账户或撮合实现;只借鉴机制并用中国市场合同验收。
- 不缓存策略结果,也不以 request hash 返回旧结果代替真实回测。
## 验收矩阵
任何性能候选至少覆盖:
| 合同 | 必须保持 |
|---|---|
| 五年主策略 | 收益、成交数、canonical/result-store SHA、终态审计 |
| 五年第二策略 | 不同持仓数下的同一组证据 |
| 通用 rank 策略 | 非 market-cap 特例排序仍正确 |
| 分钟策略 | 调度点、分钟成交价、滑点、成交量限制和 SHA |
| 冷/热运行 | data/engine/result/finalization 分段、RSS、instructions/cycles |
任一合同漂移、只有微基准改善、或真实 wall/RSS 变差时,候选必须删除并保留拒绝证据。