顶级量化机构愿意开源的,通常不是交易策略,而是它们认为可以公开的工程工具链。读这些项目不能只看机构名和 stars,更要看它们放在真实工作流里的位置:需要什么数据、输入输出是什么、能带来什么效果、维护成本有多高。结论很直接:这里面有些项目现在就能用,有些只适合研究思路,还有几个不建议新项目再引入。
一、先给结论:哪些真的值得用?
截至 2026-06-07,这 22 个项目可以分成四类。
在分类之前,先说明两个容易被标题误导的口径。第一,这份清单里的 ArcticDB 是 source-available(Business Source License 1.1),不是 OSI 认证的开源许可证:个人研究和非商业原型可以放心用,但生产和 Database Service 使用前必须先确认付费授权,不能当成普通开源数据库直接上生产。第二,WorldQuant Alpha101 不是 WorldQuant 官方开源仓库,而是社区对 101 Formulaic Alphas 公式的 Python 实现,这里只作为「学习材料」列入,不应算作顶级机构开源项目。其余项目才是常规意义上、可按开源许可证使用的工具。
第一类是现在就值得试用的项目:
| 项目 | 适合谁 | 采用建议 |
|---|---|---|
| man-group/ArcticDB | 金融时间序列数据存储 | 技术上很可用,但 BSL 许可证对生产和数据库服务有限制,商业使用前必须确认授权。 |
| man-group/dtale | pandas 数据探索 | 最容易上手,一行 dtale.show(df) 就能把 DataFrame 变成浏览器里的交互界面。 |
| deshaw/pyflyby | Python / notebook 高频研究 | 自动 import、整理 import 很实用,适合研究员日常环境。 |
| deshaw/versioned-hdf5 | HDF5 科学数据版本管理 | 场景窄但价值明确,适合大文件数据版本化。 |
| deshaw/nbstripout-fast | notebook 仓库协作 | 很实用,用 Rust 加速清理 notebook 输出,适合直接放到 git filter。 |
| janestreet/magic-trace | Linux 低延迟性能分析 | 工具价值很高,但要求 Linux + Intel Processor Trace,不适合普通 macOS 环境。 |
| hudson-trading/corral | C++20 协程系统 | header-only,设计清楚,适合已有 C++20 异步系统渐进接入。 |
| hudson-trading/slang-server | SystemVerilog / FPGA 开发 | 对硬件开发者很有用,有 VS Code、OpenVSX、Neovim 路径。 |
第二类是可用,但要先看约束的项目:
| 项目 | 主要约束 |
|---|---|
| man-group/notebooker | 是一个 Web 服务,需要 MongoDB 和 notebook 执行环境;许可证是 AGPL-3.0。 |
| man-group/PyBloqs | 适合生成静态 HTML/PDF 报告,但不是现代 BI 或 dashboard 工具;许可证是 LGPL-2.1。 |
| twosigma/marbles | 还能用,但现在很多团队已经用 pytest;它更适合 unittest 代码库。 |
| deshaw/pjrmi | 很专业,只有当你有 Python 调 Java 的真实需求才值得引入。 |
| hudson-trading/heracles-ql | README 明确说项目仍在建设中,适合研究和内部小范围试用。 |
| optiver/timestamp9 | Postgres 扩展,最后提交在 2023 年,适合需要纳秒时间戳的窄场景。 |
第三类是有学习价值,但不建议新项目直接采用的项目:
| 项目 | 原因 |
|---|---|
| twosigma/flint | 时间序列 join 思路很有价值,但依赖 Spark 2.3/2.4,默认分支最后提交停在 2019 年。 |
| twosigma/beakerx | 多语言 notebook 思路好,但 README 只支持 JupyterLab 1.2/2.x,默认分支最后提交在 2021 年。 |
| twosigma/Cook | 已归档,README 明确说开发已停止。 |
| optiver/optiver-asyncpg | 是 MagicStack/asyncpg 的 fork,最后提交在 2022 年;看 fork 思路可以,真用应优先看官方 asyncpg。 |
| yli188/WorldQuant_alpha101_code | 适合学习 Alpha101 公式,但仓库没有 README、依赖、测试;其中 101Alpha_code_2.py 直接 py_compile 会报 IndentationError。 |
第四类是只有在你接受 OCaml 技术栈时才值得用的项目:
| 项目 | 判断 |
|---|---|
| janestreet/core | OCaml 标准库替代方案,Jane Street 生态核心。 |
| janestreet/async | OCaml 协作式并发库,适合 OCaml 服务端系统。 |
| janestreet/hardcaml | 用 OCaml 做硬件设计和仿真的 DSL,适合 FPGA / ASIC 方向。 |
二、先从人的工作流看:这些项目到底怎么落地?
如果只看源码目录,很容易把这些项目读成“某个公司写了一个库”。但真实使用时,更重要的不是这个库有几个模块,而是它能不能放进真实工作流里:它吃什么数据,输出什么结果,能省掉哪类重复劳动,或者能看见以前看不见的问题。
可以先把它们放进一条典型量化工程链路里:
graph TB
subgraph "数据和代码入口"
A1["市场数据
tick / trade / quote / bar"]
A2["研究资产
notebook / 因子代码 / 报告模板"]
A3["生产系统
Java 服务 / Postgres / async IO"]
A4["低延迟与硬件
Linux 进程 / C++ 协程 / HDL"]
A5["监控规则
MetricsQL / alert config"]
end
subgraph "研究数据底座"
B1["ArcticDB
DataFrame 时间序列库"]
B2["versioned-hdf5
HDF5 文件版本化"]
B3["timestamp9
Postgres 纳秒时间戳"]
end
subgraph "研究与分析工作台"
C1["D-Tale
DataFrame 浏览器"]
C2["pyflyby
import 自动化"]
C3["WorldQuant Alpha101
公式学习材料"]
C4["BeakerX
多语言 notebook"]
end
subgraph "批处理和报告生产化"
D1["Flint
Spark 时间序列 join"]
D2["Cook
批处理调度器"]
D3["Notebooker
notebook 定时报表"]
D4["PyBloqs
静态 HTML/PDF 报告"]
D5["nbstripout-fast
notebook Git 清理"]
end
subgraph "生产桥接和运行时"
E1["PJRMI
Python 调 Java"]
E2["optiver-asyncpg
Postgres 协议客户端 fork"]
E3["HeraclesQL
可测试的 MetricsQL DSL"]
end
subgraph "低延迟、语言和硬件工程"
F1["magic-trace
Intel PT 控制流追踪"]
F2["Corral
C++20 结构化并发"]
F3["Core / Async
OCaml 标准库和并发"]
F4["Hardcaml
OCaml 硬件 DSL"]
F5["slang-server
SystemVerilog LSP"]
end
A1 --> B1
A1 --> B2
A1 --> B3
B1 --> C1
B1 --> D1
B2 --> C1
A2 --> C2
A2 --> C3
A2 --> C4
A2 --> D3
D3 --> D4
A2 --> D5
D1 --> D3
D2 --> D3
A3 --> E1
A3 --> E2
E2 --> B3
A5 --> E3
A4 --> F1
A4 --> F2
A4 --> F3
A4 --> F4
A4 --> F5
下面是逐个项目的落地判断。
Flint
- 能用在哪:大规模 tick、trade、quote、bar 数据对齐,尤其是“成交发生时,最近一条报价是什么”这类 ASOF join。
- 需要的数据源:按交易日、symbol、时间排序的行情表,通常来自 Parquet、CSV、Spark DataFrame 或内部行情仓库。
- 输入:带
time列的 Spark DataFrame / RDD,以及 join 容忍时间、方向、symbol key。 - 输出:对齐后的 DataFrame,可以继续做特征、事件窗口、回测样本。
- 带来的效果:把本来要在 pandas 单机或 SQL 里很别扭的时间近邻匹配,变成 Spark 分布式算子。
- 解决的问题:大数据量下,普通等值 join 不理解“过去最近一条”和“未来最近一条”的时间语义。
BeakerX
- 能用在哪:混合语言研究环境,比如 notebook 里既要跑 Python,又要调用 Scala/Spark、Java 内部库、SQL 查询。
- 需要的数据源:不是某一种金融数据,而是 JVM 依赖、Spark 数据集、SQL 数据库和 notebook 里的交互式对象。
- 输入:Jupyter cell 里的 Python、Scala、Groovy、Java、Kotlin、Clojure、SQL 代码。
- 输出:执行结果、表格、图表、widget、跨语言对象显示。
- 带来的效果:减少研究员在不同 IDE、脚本和服务之间切换的成本。
- 解决的问题:量化研究经常是多语言系统,但普通 notebook 默认只擅长 Python。
Marbles
- 能用在哪:已有大量
unittest的 Python 研究代码、数据处理代码、内部工具测试。 - 需要的数据源:测试用例、测试失败时的局部变量、断言表达式和调用栈。
- 输入:普通 unittest 测试,或者继承
marbles.core.TestCase的测试类。 - 输出:更可读的失败报告,包括失败表达式、变量值、作者注释和上下文。
- 带来的效果:减少测试失败后的猜测和反复 print 调试。
- 解决的问题:研究代码里测试失败常常不是“看不出错在哪”,而是“看不出这个断言本来想证明什么”。
Cook
- 能用在哪:大规模回测、仿真、参数搜索、数据补算、批处理任务队列。
- 需要的数据源:任务定义、Docker 镜像、资源需求、用户/池子/quota、Mesos 或 Kubernetes 集群资源。
- 输入:job spec,包括 CPU、内存、镜像、命令、重试策略、pool、优先级。
- 输出:被调度的 task instance、运行状态、失败重试、日志、资源使用历史。
- 带来的效果:让很多研究员和系统共享同一个集群时不至于互相抢死。
- 解决的问题:批处理集群里最难的不是“能不能跑任务”,而是公平性、抢占、重试和资源利用率。
ArcticDB
- 能用在哪:行情、bar、tick、因子、特征、回测中间结果、研究数据湖。
- 需要的数据源:pandas DataFrame、NumPy 数据、S3/MinIO/Ceph/LMDB/Azure Blob 等存储后端。
- 输入:symbol 名称、DataFrame、metadata、append/update/snapshot 操作。
- 输出:带版本号的
VersionedItem,里面是读回的 DataFrame、metadata、version。 - 带来的效果:研究员仍然用 pandas 思维,但数据开始有版本、snapshot、append、update 和对象存储能力。
- 解决的问题:CSV/Parquet 文件堆越用越乱,数据修正和历史复现没有统一语义。
D-Tale
- 能用在哪:notebook 或脚本里快速探索 pandas DataFrame,尤其是字段多、缺失多、想快速筛选和画图时。
- 需要的数据源:pandas DataFrame、Series、Index,也可以从 CSV、Parquet、Excel、ArcticDB 等加载。
- 输入:
dtale.show(df)里的 DataFrame。 - 输出:浏览器交互界面、筛选结果、图表、相关性分析、导出的代码或文件。
- 带来的效果:把
head()、describe()、corr()、手写筛选变成可点击探索。 - 解决的问题:研究员看数据时反复写临时代码,效率低且容易丢失探索过程。
Notebooker
- 能用在哪:日报、风险报告、组合监控、策略表现报告、数据质量报表。
- 需要的数据源:notebook 模板、参数、MongoDB、业务数据源、执行 kernel。
- 输入:notebook template、运行参数、调度配置。
- 输出:执行后的 notebook、HTML 报告、运行元数据、历史结果索引。
- 带来的效果:把一次性 notebook 变成可定时、可回看、可参数化的报告系统。
- 解决的问题:研究员写出来的 notebook 很有用,但业务方需要的是稳定、定时、可追溯的结果。
PyBloqs
- 能用在哪:把 Python 分析结果生成静态 HTML/PDF 报告,适合邮件、归档、内部汇报。
- 需要的数据源:pandas 表格、matplotlib/plotly 图、文本、图片、HTML 片段。
- 输入:Block、Table、Plot、HStack/VStack 组成的报告树。
- 输出:HTML、PDF、PNG、SVG 等静态产物。
- 带来的效果:把“分析代码”和“给别人看的报告”连接起来。
- 解决的问题:很多研究结果不是要做成 dashboard,而是要稳定地产出一份可读报告。
Core
- 能用在哪:OCaml 服务端、交易基础设施、命令行工具、长期维护的大型 OCaml 工程。
- 需要的数据源:这里的数据源不是行情,而是 OCaml 代码、类型、序列化格式、命令行参数。
- 输入:
open! Core后使用 Core 模块写的 OCaml 程序。 - 输出:更一致的容器、错误处理、时间、序列化、命令行和基础库行为。
- 带来的效果:团队代码风格和基础类型统一,减少标准库历史包袱。
- 解决的问题:大型 OCaml 工程不能每个人按自己的方式处理时间、错误、Map、序列化。
magic-trace
- 能用在哪:Linux 低延迟服务、交易系统、撮合/行情处理、偶发延迟尖刺分析。
- 需要的数据源:目标进程、Linux perf、Intel Processor Trace、ELF 符号、perf map。
- 输入:pid 或启动命令,以及触发条件,比如 Ctrl+C、函数 breakpoint、采样窗口。
- 输出:
trace.fxt.gz,可在 Perfetto / magic-trace UI 里查看时间线。 - 带来的效果:看到普通 profiler 看不到的极短时间窗口里的真实控制流。
- 解决的问题:低延迟系统里很多问题不是平均慢,而是某几毫秒里突然发生了奇怪路径。
Async
- 能用在哪:OCaml 网络服务、交易服务、异步 RPC、文件/定时器/信号驱动程序。
- 需要的数据源:socket 事件、timer、文件描述符、RPC 请求、进程信号。
- 输入:
Deferred.t、I/O 操作、异步 callback、调度器任务。 - 输出:非阻塞执行结果、RPC 响应、连接状态、异常传播。
- 带来的效果:把等待点放进类型和调度器里,避免随意阻塞主线程。
- 解决的问题:交易服务需要可控并发,而不是到处开线程、靠锁和 callback 硬拼。
Hardcaml
- 能用在哪:FPGA/ASIC 设计、硬件加速模块、低延迟逻辑原型和仿真。
- 需要的数据源:不是传统数据源,而是硬件规格、端口定义、时序逻辑、测试向量。
- 输入:OCaml 写的 Signal DSL、interface、register spec、module/functor 参数。
- 输出:cycle simulator、波形、Verilog/VHDL RTL。
- 带来的效果:用通用语言的类型系统和抽象能力生成硬件结构。
- 解决的问题:复杂硬件设计里,裸 Verilog 很难管理参数化、接口一致性和可测试性。
pyflyby
- 能用在哪:IPython、Jupyter、研究脚本、内部 Python 工具库密集的团队。
- 需要的数据源:Python 源码、
.pyflybyimport database、内置标准库/NumPy/import 规则。 - 输入:未知符号、Python 文件、import block、项目级 import 规则。
- 输出:自动补齐 import、整理后的 import block、删除 unused import。
- 带来的效果:减少研究过程里“停下来补 import、整理 import”的小摩擦。
- 解决的问题:notebook 研究的成本往往不是一个大阻塞,而是每天反复发生的小打断。
PJRMI
- 能用在哪:Python 研究环境需要调用 Java 服务、Java 风控库、Java 数据访问层的团队。
- 需要的数据源:Java classpath、JVM 对象、Python 对象、socket/pipe/JNI 传输。
- 输入:Python 侧方法调用、Java 类名、参数、callback。
- 输出:Java 方法返回值、Python proxy、异常、反向 callback 结果。
- 带来的效果:让研究员不必把 Java 资产重写成 Python,也不用每个功能都包 HTTP API。
- 解决的问题:成熟金融机构里 Python 和 Java 技术栈经常长期共存,边界调用很痛。
versioned-hdf5
- 能用在哪:已有 HDF5 工作流的科学数据、历史行情、实验结果、金融大文件。
- 需要的数据源:HDF5 文件、h5py group/dataset、NumPy array、版本写入记录。
- 输入:一次
stage_version里的 dataset 创建、修改、resize、delete。 - 输出:同一个 HDF5 文件里的多个只读版本视图。
- 带来的效果:文件不必复制很多份,也能追踪数据修正历史。
- 解决的问题:数据一旦回补或清洗逻辑变化,旧实验还能不能复现。
nbstripout-fast
- 能用在哪:有 Jupyter notebook 的 git 仓库,尤其是团队协作和频繁提交场景。
- 需要的数据源:
.ipynbJSON、.git-nbconfig.yaml、git clean filter。 - 输入:提交时进入 git 的 notebook 文件。
- 输出:去掉 output、execution_count 和指定 metadata 的 notebook。
- 带来的效果:diff 变小,review 看代码本身,仓库历史不被输出污染。
- 解决的问题:notebook 输出进 git 后,协作、审查、仓库体积都会变差。
Corral
- 能用在哪:C++20 异步 I/O、低延迟服务、已有 callback/asio 系统迁移 coroutine。
- 需要的数据源:Asio 事件、callback API、timer、channel/event/semaphore 等同步信号。
- 输入:
Task<T>、co_await、nursery、外部事件循环。 - 输出:结构化任务结果、取消传播、异常传播、同步原语状态。
- 带来的效果:异步任务不再到处 detach,生命周期能跟作用域绑定。
- 解决的问题:C++ coroutine 好写,但任务树、取消、异常和 callback 桥接如果没人管,很容易失控。
slang-server
- 能用在哪:SystemVerilog / Verilog 工程、FPGA/ASIC 开发、硬件团队编辑器体验。
- 需要的数据源:
.sv/.svh文件、filelist、include path、macro、top module、.slang配置。 - 输入:编辑器 LSP 请求和 HDL 工程上下文。
- 输出:diagnostics、hover、completion、go to definition、references、inlay hints、hierarchy。
- 带来的效果:硬件代码开发体验接近现代软件 IDE。
- 解决的问题:HDL 工程结构复杂,没有 compiler-grade 语义分析时,编辑器只能做很浅的文本辅助。
HeraclesQL
- 能用在哪:VictoriaMetrics / vmalert 告警规则、大规模服务监控、延迟敏感系统告警库。
- 需要的数据源:Prometheus/VictoriaMetrics metrics、labels、service context、阈值配置。
- 输入:Python DSL 表达式、context、rule config。
- 输出:MetricsQL 字符串、Prometheus/VictoriaMetrics rule YAML、测试用例。
- 带来的效果:告警规则从复制粘贴的字符串变成可组合、可测试、可类型检查的代码。
- 解决的问题:监控规则一多,YAML 字符串复制会造成维护灾难和隐性错误。
timestamp9
- 能用在哪:Postgres 里存纳秒级事件时间,比如 tick、订单事件、低延迟日志。
- 需要的数据源:时间戳字符串、bigint 纳秒值、Postgres 表和索引。
- 输入:
timestamp9literal、bigint、timestamp/timestamptzcast。 - 输出:Postgres 原生类型形式的纳秒时间戳,可比较、排序、建索引。
- 带来的效果:纳秒时间进入 SQL 类型系统,而不是靠约定说某个 bigint 是时间。
- 解决的问题:微秒精度不够时,事件先后顺序和查询表达都会变得不可靠。
optiver-asyncpg
- 能用在哪:Python asyncio 服务访问 PostgreSQL,尤其是连接池、prepared statement、COPY、LISTEN/NOTIFY。
- 需要的数据源:PostgreSQL 数据库、SQL 查询、连接参数、类型 codec。
- 输入:async query、参数、transaction、pool acquire/release。
- 输出:Record、cursor、prepared statement 结果、事务状态。
- 带来的效果:高性能 async Postgres 客户端路径,减少 DB-API 包装成本。
- 解决的问题:生产系统里数据库客户端的性能、取消、超时、连接池状态都可能成为瓶颈。
WorldQuant Alpha101
- 能用在哪:学习因子公式、重写自己的 alpha primitive、理解横截面 rank 和时间序列 rolling。
- 需要的数据源:OHLCV、成交额、收益率等行情字段,且要明确频率、复权、股票池和交易日历。
- 输入:包含
S_DQ_OPEN、S_DQ_HIGH、S_DQ_LOW、S_DQ_CLOSE、S_DQ_VOLUME等字段的 DataFrame。 - 输出:单个 alpha 的因子值 Series/DataFrame。
- 带来的效果:帮助初学者把论文公式翻译成 pandas 表达式。
- 解决的问题:它解决的是学习和公式理解问题,不解决生产因子库里的数据质量、对齐、中性化和回测验证。
三、判断一个项目是否真的可用,要看什么?
这类清单最容易写成“某某机构开源了什么”。但真正有价值的判断,不是这个项目来自哪家公司,而是它能不能进入真实工作流。一个项目是否值得采用,至少要看六件事。
- 工作流位置:它属于数据存储、研究探索、批量调度、报告生成、生产桥接、低延迟分析,还是硬件开发工具?位置不清楚,项目再有名也很难落地。
- 数据前提:它需要 tick、bar、OHLCV、DataFrame、notebook、Postgres、HDF5、HDL 文件,还是 JVM classpath?数据前提决定了接入成本。
- 输入和输出:一个工具真正的边界,是它接收什么、吐出什么。比如 ArcticDB 输入 DataFrame 和 symbol,输出版本化 DataFrame;magic-trace 输入进程和触发条件,输出 trace 文件。
- 工程效果:它到底是节省人工操作、提高复现性、减少延迟尖刺定位成本,还是把不可维护的字符串规则变成可测试代码?如果说不出效果,就不该引入。
- 维护和授权成本:最后提交时间、归档状态、依赖版本、许可证、部署复杂度,都会决定它是“能直接用”还是“只能学习思路”。
- 源码可信度:真正的核心逻辑是否清楚?有没有测试?是否只是示例仓库?像 WorldQuant Alpha101 这类项目,就不能因为名字响亮而忽略代码质量。
Mermaid 图也应该按这个标准画。太粗的图只能告诉读者“这里有几个模块”,但不能说明项目为什么能工作;太细的图会把博客变成 API 手册。比较合适的粒度,是让读者看完一张图就知道入口、源码边界、核心抽象、运行时依赖和输出结果。
flowchart TB
subgraph "1. 使用入口"
A1["CLI / Python API / REST / 编辑器插件"]
A2["输入数据
DataFrame / notebook / SQL / trace / HDL"]
end
subgraph "2. 源码边界"
B1["顶层包或入口文件"]
B2["核心目录"]
B3["测试和示例"]
B4["构建脚本 / 配置文件"]
end
subgraph "3. 核心实现"
C1["核心抽象"]
C2["解析 / 编码 / 调度"]
C3["状态管理"]
C4["错误处理"]
end
subgraph "4. 运行时和外部依赖"
D1["数据库 / 对象存储"]
D2["JVM / Spark / Postgres / kernel"]
D3["OS / CPU / 编译器"]
end
subgraph "5. 输出和采用判断"
E1["DataFrame / HTML / trace / SQL / RTL"]
E2["能直接用"]
E3["需要 PoC"]
E4["只适合学习架构"]
end
A1 --> B1
A2 --> B1
B1 --> B2
B2 --> C1
B3 --> C1
B4 --> D2
C1 --> C2
C2 --> C3
C3 --> C4
C4 --> D1
C4 --> D2
C4 --> D3
D1 --> E1
D2 --> E1
D3 --> E1
E1 --> E2
E1 --> E3
E1 --> E4
后文每个项目都会按这个思路展开:先讲它解决什么问题,再讲它的架构和源码结构,最后给出采用建议。
四、Two Sigma:研究平台和大规模时间序列处理
Two Sigma 这组项目展示的是量化研究基础设施:Spark 时间序列 join、多语言 notebook、测试可读性、大规模批处理调度。但现在回头看,它们的状态差异很大。
1. twosigma/flint:Spark 上的大规模时间序列 join
GitHub:twosigma/flint
默认分支最后提交:2019-08-06
许可证:Apache-2.0
Flint 解决的是 Spark DataFrame 不擅长的时间序列对齐问题。量化数据里最典型的场景是:成交数据里有一笔 trade,你要找它之前最近的一条 quote,或者找指定容忍时间窗口内的行情快照。普通 join 只处理 key 相等,Flint 提供的是带时间方向和容忍度的 temporal join。
完整架构与实现原理:
graph TB
subgraph "源码和构建边界"
S1["build.sbt
Spark / Scala 依赖"]
S2["src/main/scala
JVM 核心实现"]
S3["python/ts/flint
PySpark 包装层"]
S4["src/test / python tests
join 和 window 行为验证"]
end
subgraph "输入契约"
A1["CSV / Parquet / Hive / Spark DataFrame"]
A2["必须有 time 列
通常是 Long 纳秒时间戳"]
A3["可选分组键
symbol / exchange / venue"]
A4["Spark partition 和排序前提"]
end
subgraph "Python 使用入口"
B1["FlintContext"]
B2["read.dataframe / read.csv / read.parquet"]
B3["TimeSeriesDataFrame
Python facade"]
B4["用户调用
leftJoin / futureLeftJoin / summarizeWindows"]
end
subgraph "JVM 核心抽象"
C1["TimeSeriesRDD"]
C2["OrderedRDD
按 key 和 time 组织"]
C3["TimeSeriesRow"]
C4["Window / Cycle / Summarizer"]
C5["Temporal join algorithm"]
end
subgraph "Spark 运行时"
D1["Driver 构建 logical 操作"]
D2["Executors 执行 partition 内扫描"]
D3["Shuffle / repartition
按 key 和 time 对齐"]
D4["JVM jar 必须在 classpath"]
end
subgraph "输出和边界"
E1["对齐后的 Spark DataFrame"]
E2["事件窗口特征"]
E3["tick / quote / trade ASOF join"]
E4["限制:Python 包不是全部
还需要匹配 Spark/Scala/JAR"]
end
S1 --> S2
S2 --> C1
S3 --> B1
S4 --> C5
A1 --> A2 --> A3 --> A4 --> B1
B1 --> B2 --> B3 --> B4
B4 --> C1
C1 --> C2
C1 --> C3
C1 --> C4
C4 --> C5
C5 --> D1
D1 --> D2
D2 --> D3
D4 --> D2
D3 --> E1
E1 --> E2
E1 --> E3
D4 --> E4
Flint 的技术核心不是“多一个 join API”,而是把 Spark 里普通无序的分布式行数据提升成“按时间有序的分布式序列”。底层 OrderedRDD 保留排序属性,TimeSeriesRDD 在这个基础上实现时间窗口、近邻匹配和聚合,Python 侧的 TimeSeriesDataFrame 再把它包装成 PySpark 用户能接受的接口。
它为什么好:
- 把时间排序变成一等公民:普通 Spark join 只知道 key 相等,不知道“找过去最近一条 quote”这种时间语义。Flint 把时间容忍度、过去/未来方向、窗口都做成算子。
- 避免把 tick 数据拉回单机:trade/quote 对齐如果用 pandas 做,只适合小数据;Flint 的设计仍然跑在 Spark 分区上。
- 贴合金融数据的物理形态:历史行情通常按交易日、symbol、时间排序落盘。Flint 复用这种“at-rest 的天然排序”——
OrderedRDD给每个分区带上Range/RangeSplit/RangeDependency元数据记录它覆盖的时间区间,join 时用这些区间边界判断右表哪些分区可能落进 tolerance 窗口,只对相关分区做有限重分发、再在两侧有序迭代器上做 merge-scan(归并),而不是先对整表做 hash shuffle。这才是它“减少不必要 shuffle”的真正机制。 - 研究表达更接近业务问题:
leftJoin(tolerance="100ms")比手写窗口 join 更不容易犯方向和边界错误。
局限也很明显:它绑定的是老 Spark 生态。今天看它,更重要的是学会“时间序列 join 框架应该怎么抽象”,而不是直接把它搬进新系统。
源码级目录结构和模块分工:
1flint/
2 src/main/scala/com/twosigma/flint/rdd/ 分布式有序 RDD 核心
3 src/main/scala/com/twosigma/flint/rdd/function/ join、summarize、group 等算子实现
4 src/main/scala/com/twosigma/flint/timeseries/ TimeSeriesRDD 和金融时间序列 API
5 src/main/scala/com/twosigma/flint/arrow/ Arrow 读写转换
6 src/main/scala/org/apache/spark/sql/ Spark SQL/DataFrame 兼容层
7 python/ts/flint/ PySpark 包装层
8 src/test/scala/ 与 python/tests/ Scala 和 Python 回归测试
读源码时建议先从 src/main/scala/com/twosigma/flint/rdd/OrderedRDD.scala 看起。它定义了 Flint 最底层的“分区内有序、分区间有 range 边界”的数据结构,配套的 Range.scala、RangeSplit.scala、RangeDependency.scala 负责描述分区范围和依赖关系。MergeIterator.scala、PartitionsIterator.scala 这类文件体现了它为什么能做时间序列近邻匹配:它不是把所有数据攒到 driver,而是在有序迭代器上流式推进。
时间序列 API 在 timeseries/TimeSeriesRDD.scala。这个文件把 OrderedRDD 包装成用户理解的时间序列对象,Clocks.scala、Windows.scala、Summarizers.scala 分别处理采样时钟、窗口边界和聚合入口。真正的 join 实现在 rdd/function/join/,比如 LeftJoin.scala、FutureLeftJoin.scala、RangeMergeJoin.scala,这些文件把“过去最近一条”“未来最近一条”“容忍区间”编码成 merge 扫描逻辑。聚合在 rdd/function/summarize/,Summarizer.scala 是协议,CompositeSummarizer.scala 支持多个统计量一次遍历计算。
这里补一个容易踩的默认值:leftJoin / futureLeftJoin 的 tolerance 默认是 "0ns"——不显式传 tolerance 时会退化成“时间戳精确匹配”,ASOF 那种“取过去/未来最近一条”的语义必须显式给出 tolerance(如 "100ms")才生效。另外输入侧的时间单位由 fromDF(...)(timeUnit = ...) 配置,可以是毫秒/纳秒等,Flint 内部会统一换算成纳秒——不用自己先把列转成纳秒。
Python 层没有重写算法,python/ts/flint/context.py、readwriter.py、summarizers.py、windows.py 主要负责把 PySpark 用户的调用转换成 JVM 侧对象。这个分层很典型:Scala 侧掌握分布式执行和排序语义,Python 侧只做 API 适配。
它的核心抽象是:
- Scala 侧:
TimeSeriesRDD - Python 侧:
TimeSeriesDataFrame - 关键能力:
leftJoin、futureLeftJoin、窗口聚合、周期聚合、interval 聚合
安装和运行大概是这样:
1# Scala jar
2sbt assemblyNoTest
3
4# Python binding
5cd python
6pip install .
7
8# PySpark 里同时带上 jar 和 py-files
9pyspark \
10 --jars /path/to/flint-assembly-{VERSION}-SNAPSHOT.jar \
11 --py-files /path/to/flint-assembly-{VERSION}-SNAPSHOT.jar
Scala 示例:
1import com.twosigma.flint.timeseries.TimeSeriesRDD
2import scala.concurrent.duration._
3
4val trades = TimeSeriesRDD.fromDF(tradesDf)(isSorted = true, timeUnit = MILLISECONDS)
5val quotes = TimeSeriesRDD.fromDF(quotesDf)(isSorted = true, timeUnit = MILLISECONDS)
6
7val joined = trades.leftJoin(quotes, tolerance = "100ms")
Python 侧从代码和测试看,常见用法是用 FlintContext 读入 pandas 或 Spark DataFrame,再做时间序列 join:
1from ts.flint import FlintContext
2
3flint = FlintContext(sqlContext)
4trades = flint.read.dataframe(trades_df)
5quotes = flint.read.dataframe(quotes_df)
6
7joined = trades.leftJoin(quotes, tolerance="100ms", key="symbol")
采用建议:思路很值得学,但不建议新项目直接采用。README 明确写的是 Spark 2.3/2.4、Scala 2.12、Python 3.5+。默认分支最后提交停在 2019 年。如果现在做大规模 tick 数据对齐,更应该先看 Spark 自带窗口、DuckDB/Polars、ClickHouse ASOF JOIN,或者基于 Parquet 分区和 Arrow/Polars 做更轻量的 join。Flint 更适合作为理解“金融时间序列 join 应该怎么设计”的参考。
2. twosigma/beakerx:多语言 Jupyter 环境
GitHub:twosigma/beakerx
默认分支最后提交:2021-01-21
许可证:Apache-2.0
BeakerX 是 Jupyter 的多语言扩展,支持 JVM 语言内核和交互式 widget。它的价值在于让研究环境不只停留在 Python:Scala、Groovy、Java、Kotlin、Clojure、SQL 可以放到同一个 notebook 体系里。
完整架构与实现原理:
graph TB
subgraph "浏览器和 Notebook 前端"
A["Jupyter Notebook / JupyterLab"]
B["BeakerX JS extensions"]
C["Interactive widgets
table / plot / forms"]
end
subgraph "Kernel 层"
D["Jupyter messaging protocol"]
E["Groovy kernel"]
F["Scala kernel"]
G["Java kernel"]
H["Kotlin / Clojure kernel"]
I["SQL kernel"]
end
subgraph "JVM 执行层"
J["JVM process"]
K["Classpath / Maven dependency"]
L["Spark integration"]
M["对象显示协议"]
end
subgraph "跨语言能力"
N["Autotranslation"]
O["JSON / table display model"]
P["前端 D3 / plotting 渲染"]
end
A --> B --> C
A <--> D
D --> E
D --> F
D --> G
D --> H
D --> I
E --> J
F --> J
G --> J
H --> J
J --> K
J --> L
J --> M
M --> O --> P --> C
N --> O
BeakerX 的设计目标是把 Jupyter 从“Python notebook”扩成“多语言研究工作台”。Jupyter 自身通过消息协议连接 kernel,BeakerX 做的是给 JVM 语言提供 kernel,并补上交互式表格、绘图、自动显示和跨语言对象转换。
它为什么好:
- 语言边界变薄:研究员可以在 notebook 中同时用 Scala/Spark、Java 库、SQL 查询和 Python 可视化,不需要频繁切进不同 IDE。
- JVM 生态直接进入研究环境:很多金融机构内部服务和大数据工具在 JVM 上,BeakerX 让这部分能力能被 notebook 调用。
- 显示层统一:表格、图和 widget 不只是 stdout,而是结构化对象,前端可以做排序、筛选和交互。
- 依赖管理贴近 JVM 工作流:Classpath/Maven 能让 notebook 临时加载 Java/Scala 依赖,这对内部工具验证很实用。
它的问题也来自同一个方向:Jupyter 前端生态变化很快,BeakerX 绑定的 Lab 版本较老。今天它更像一个“多语言 notebook 平台设计案例”,而不是新项目默认选择。
源码级目录结构和模块分工:
1beakerx/
2 beakerx-dist/ conda/pip 分发包、配置文件和聚合安装入口
3 doc/python/ Python API 和 widget 示例 notebook
4 doc/groovy/ Groovy kernel、表格、图表、polyglot 示例
5 doc/scala/ Scala、Spark、JVM 依赖示例
6 doc/java|kotlin|... 其他 JVM 语言 notebook 示例
7 test/ipynb/ 多语言 notebook 回归测试
8 docker/ 与 binder/ 可复现演示环境
这个仓库当前更像 BeakerX 发行和文档仓库,而不是一个容易从单个源码入口读下去的内核实现仓库。beakerx-dist/configuration.yml、requirements.txt、setup.py 负责把多个 kernel、前端扩展和依赖组合成 beakerx_all。doc/ 下面的大量 .ipynb 实际上就是可执行规格文档:doc/groovy/Charting.ipynb 展示绘图协议,doc/groovy/TableAPI.ipynb 展示表格模型,doc/groovy/PolyglotMagic.ipynb 展示跨语言交互,doc/resources/jar/ 放 JVM 示例依赖。
测试目录也很能说明架构边界:test/ipynb/python/polyglotKernel.ipynb、AutoTranslationPythonTest.ipynb、TableAPIPythonTest.ipynb 不是普通单元测试,而是直接驱动 notebook 执行。也就是说,BeakerX 的核心质量保证在“Jupyter 消息协议 + kernel 执行 + 前端显示对象”这一整条链路上,而不是单个 Python 函数。今天如果要复用它,最值得借鉴的是这些 notebook 作为集成测试的组织方式。
更细一点看,doc/groovy/ClasspathMagicCommands.ipynb 和 doc/scala/Spark.ipynb 对应 JVM 依赖和 Spark 这类金融研究常见场景;doc/python/TableAPI.ipynb、doc/python/ChartingAPI.ipynb、doc/python/PlotJSAPI.ipynb 体现 Python 侧显示对象如何进入 BeakerX 前端;doc/sql/Sql.ipynb 表示它还把数据库查询纳入同一个 notebook 工作流。StartHere.ipynb 是用户入口,docker/ 和 binder/ 是可复现实验环境,beakerx-dist/beakerx_all 则是把这些能力打包成发行组件的聚合层。所以 BeakerX 的源码导读不能只找一个“kernel.py”,而要按发行包、notebook 行为规格、语言示例和集成测试四条线读。
README 里的安装方式是:
1# 安装全部内核和组件
2conda install -c beakerx beakerx_all
3
4# 或者只装部分内核
5conda install -c beakerx beakerx_kernel_groovy
6conda install -c beakerx beakerx_kernel_java
7conda install -c beakerx beakerx_kernel_scala
8conda install -c beakerx beakerx_kernel_sql
9conda install -c beakerx beakerx_kernel_clojure
10conda install -c beakerx beakerx_kernel_kotlin
11
12# JupyterLab 2.x
13conda install -c conda-forge jupyterlab=2
14conda install -c beakerx beakerx_all
采用建议:不建议作为新 notebook 平台采用。原因不是理念过时,而是生态版本过旧。README 写的是 Jupyter Notebook 和 JupyterLab 1.2/2.x,现在 JupyterLab 已经走到更后面的架构。默认分支最后提交在 2021 年。如果你只是想在 notebook 里跑多语言,现在可以优先看 Jupyter kernels、Apache Toree、xeus 系列、Quarto,或者直接把研究逻辑拆成 Python + 服务接口。
BeakerX 最值得保留的启发是:量化研究环境经常是混合语言的,工具链应该承认这一点,而不是强行把所有东西都塞进 Python。
3. twosigma/marbles:更可读的 unittest 失败信息
GitHub:twosigma/marbles
默认分支最后提交:2024-01-11
PyPI:marbles==0.12.3
许可证:MIT
Marbles 是一个 unittest 扩展,目标是让测试失败信息更像文档,而不是只给你一段 traceback。它能展示失败语句、局部变量、作者写的说明、语义更明确的 assertion。
完整架构与实现原理:
graph LR
subgraph "测试入口"
A["unittest TestCase"]
B["marbles.core.TestCase"]
C["marbles mixins"]
D["python -m marbles runner"]
end
subgraph "断言执行"
E["语义化 assertion"]
F["note / annotation"]
G["局部变量捕获"]
H["traceback 控制"]
end
subgraph "失败报告"
I["失败表达式"]
J["变量值"]
K["作者注释"]
L["可读错误信息"]
end
A --> D
B --> D
C --> B
D --> E
E --> F
E --> G
E --> H
E --> I
G --> J
F --> K
I --> L
J --> L
K --> L
Marbles 的实现不是重写测试框架,而是站在 unittest 上增强失败表达。它提供自己的 TestCase 和 mixins,测试执行仍然沿用 unittest 体系;真正增加价值的是 assertion 失败时,它能把失败表达式、局部变量、注释和上下文组织成更可读的报告。
它为什么好:
- 兼容遗留 unittest:大型机构里测试代码往往很多,完全迁移 pytest 成本高;Marbles 可以渐进替换 runner 或 TestCase。
- 把失败信息当文档:研究代码失败时,最贵的是理解“这个断言本来想证明什么”。
note和语义化 assertion 能降低阅读成本。 - 减少调试 printf:失败时自动带变量值,不需要来回加 print。
- 适合实验性代码:量化研究里测试经常覆盖数据边界、统计结果和临时实验,可读失败信息比测试框架花哨更重要。
它不如 pytest 流行,但设计理念是对的:测试失败输出应该服务后续排查,而不是只服务机器执行。
源码级目录结构和模块分工:
1marbles/
2 marbles/core/marbles/ runner、TestCase、失败信息生成核心
3 marbles/mixins/marbles/ 可混入的断言增强
4 docs/examples/ 失败输出和自定义断言示例
5 requirements/ 开发、测试、文档依赖
6 tests/ core、setuptools、日志和示例回归测试
源码里 marbles.core 包实际在 marbles/core/marbles/core/ 下(注意多一层 core/),核心文件是 marbles/core/marbles/core/marbles.py,它提供 marbles.core.TestCase 以及 note、断言包装、失败报告拼装。它增强失败信息的机制很取巧:TestCase 重写了 __getattribute__,凡是被查找到的、名字以 assert 开头的可调用方法(以及 fail)都会被动态包成 wrapper——这样无需逐个重写,就能自动覆盖 unittest 现有及未来新增的所有断言;用户写的 note= 注释则借原生断言的 msg 通道“夹带”下去,并在失败那一刻才用测试函数的局部变量做惰性 format 展开(未失败零开销)。_stack.py 用 traceback.walk_stack 找到 test_*/setUp/tearDown 帧、快照它的局部变量(这就是报告里“Locals”的来源),log.py 负责把失败报告按统一格式输出,setuptools.py 则让老项目可以通过 setup.py marbles 接入。
marbles/mixins/marbles/mixins.py 是另一层扩展点。它不强迫用户完全替换测试框架,而是把更语义化的断言能力作为 mixin 注入已有 unittest.TestCase。这里有个容易被忽略的依赖差异:marbles.core 只用标准库(没有第三方依赖),但 marbles.mixins 是另一个独立包、强依赖 pandas(install_requires 写了 pandas<3,>=1,因为 DateTimeMixins 等用 pd.Series 实现),pip install marbles.mixins 一定会拉 pandas;只有 hdfs3 才是真正可选(仅测远程 HDFS 文件时需要)。docs/examples/custom_assertions.py、getting_started.py 能看到它的设计目标:断言失败时不仅告诉你“错了”,还要告诉你“这个测试本来想表达什么”。这也是它比普通 runner 更有价值的地方。
测试文件把它的模块边界说得很清楚:marbles/core/tests/test_main.py 验证命令行 runner,test_marbles.py 验证 TestCase 和失败报告,test_setuptools.py 验证 setup.py marbles 这条遗留接入路径,test_log.py 验证日志输出。marbles/core/tests/examples/example_unittest.py、example_marbles.py、example_error.py 是“同一类测试在 unittest 和 marbles 下失败输出有什么区别”的对照材料。也就是说,Marbles 的架构重点不是算法复杂度,而是把 runner、TestCase、stack inspection、日志和 setuptools 插件串成一个低迁移成本的测试体验。
安装:
1pip install marbles
直接跑现有 unittest:
1python -m marbles test_module.py
如果你的项目还在用 python setup.py test,可以改成:
1python setup.py marbles
也可以在 setup.cfg 里加 alias:
1[aliases]
2test = marbles
采用建议:可用,但优先级不高。如果你有大量 unittest 遗留代码,它能改善失败信息。如果你现在新建 Python 项目,pytest 的 assertion rewriting 已经足够好,社区生态也更强。Marbles 的价值更多在“测试失败信息应该服务读者”这个理念。
4. twosigma/Cook:已归档的大规模批处理调度器
GitHub:twosigma/Cook
默认分支最后提交:2023-04-24
GitHub 状态:archived
许可证:Apache-2.0
Cook 是面向 Mesos 和 Kubernetes 的批处理调度器。它解决的是资源紧张时如何排队、抢占、重试、提高集群利用率的问题。量化机构每天跑回测、仿真、参数搜索、数据任务,这类 scheduler 在当年很有实际价值。
完整架构与实现原理:
graph TB
subgraph "提交入口"
A["CLI cs"]
B["REST API"]
C["Java / Python client"]
end
subgraph "Cook Scheduler 核心"
D["Job admission"]
E["Queue / pool / user quota"]
F["Fairness policy"]
G["Preemption policy"]
H["Retry / failure recovery"]
I["Instance state machine"]
end
subgraph "持久化和状态"
J["Datomic"]
K["Job metadata"]
L["Instance history"]
end
subgraph "匹配与执行后端"
FZ["Fenzo 装箱匹配"]
M["Mesos framework backend"]
N["Kubernetes backend"]
end
subgraph "接入方"
O["Spark on Cook
Cook 当 Spark 的调度后端"]
end
subgraph "实际负载"
P["Backtest"]
Q["Simulation"]
R["Data job"]
end
A --> D
B --> D
C --> D
O -->|executor 作为 Cook 作业提交| D
D --> E --> F --> G --> I
H --> I
I <--> J
J --> K
J --> L
I --> FZ
FZ --> M
FZ --> N
M --> P
M --> Q
M --> R
N --> P
N --> Q
N --> R
Cook 的架构重点是“资源紧张时的批处理治理”。用户通过 CLI/API 提交 job,scheduler 把 job 放进 pool 和 queue,根据用户配额、公平性、优先级、抢占策略决定哪些 instance 可以运行。所有 job 和 instance 状态写入 Datomic(不可变、单一真相源),匹配/装箱实际委托给 Netflix 的 Fenzo 库,最终把任务交给 Mesos 或 Kubernetes 执行。这里有个常见误读要纠正:Spark 不是第三种执行后端,而是方向相反——Cook 可以充当 Spark 的资源调度后端,让 Spark executor 作为 Cook 作业被调度,最终仍落到 Mesos/K8s 上跑。
它为什么好:
- 解决的是真实资源冲突:量化回测和模拟任务很容易把集群打满。Cook 的核心价值是让小任务不被大任务长期饿死,同时保持高利用率。
- 把 job 和 instance 分开:一个 job 可以有多次执行尝试,失败、重试、抢占都能进入状态机,而不是简单“任务失败”。
- 支持抢占:在容量紧张时,scheduler 可以主动释放低优先级或长任务资源,让高价值任务尽快返回结果。
- 客户端和 REST API 完整:研究员、服务端系统、以及把 Cook 当调度后端的 Spark 都可以走同一个 scheduler。
今天它已经归档,但它代表了一类成熟批处理调度器的完整问题集:公平性、抢占、重试、状态持久化、后端抽象。
源码级目录结构和模块分工:
1cook/
2 scheduler/src/cook/rest/ HTTP API、认证、授权、中间件
3 scheduler/src/cook/scheduler/ 调度循环、offer 匹配、优化器、公平性
4 scheduler/src/cook/{datomic,postgres,schema,queries}.clj 状态模型和查询
5 scheduler/src/cook/kubernetes/ Kubernetes compute cluster 后端
6 scheduler/src/cook/mesos*.clj Mesos framework 后端
7 cli/cook/ Python CLI 和子命令
8 executor/cook/ 容器内任务执行器
9 sidecar/cook/ 文件服务、进度跟踪、退出哨兵
10 simulator/src/main/cook/sim/ 调度器仿真和容量实验
Cook 最值得读的是 scheduler/src/cook/scheduler/scheduler.clj。这里是调度循环的主干,负责把待运行 instance、资源 offer、pool、quota、约束和抢占策略组织起来。offer.clj 处理资源 offer 匹配,optimizer.clj、share.clj、constraints.clj 体现公平性和可调度性判断,rebalancer.clj、monitor.clj 处理长期运行中的资源再平衡和健康检查。
状态层分布在 datomic.clj、postgres.clj、schema.clj、queries.clj、cached_queries.clj。Cook 的 job/instance 不是一次性对象,而是有完整生命周期和历史记录;这也是它能做重试、抢占和审计的基础。后端适配在 kubernetes/api.clj、kubernetes/controller.clj、mesos.clj 等文件里,说明 scheduler 核心并不直接绑定某一种集群系统。
用户侧是 Python。cli/cook/cli.py 建立命令入口,subcommands/submit.py、jobs.py、kill.py、show.py、tail.py、ssh.py 把常见操作映射到 REST API。executor/cook/executor.py 负责实际容器内命令执行和状态上报,sidecar/cook/sidecar/file_server.py、progress.py 解决日志、进度和结果查看。这个仓库虽然归档,但源码结构完整展示了一个批处理平台从 API、调度、执行到观察性的全链路。
安装/本地运行:README 里的 Mesos quickstart 是:
1cd scheduler
2bin/build-docker-image.sh
3../travis/minimesos up
4bin/run-docker.sh
提交一个简单任务:
1cs submit \
2 --pool k8s-alpha \
3 --cpu 0.5 \
4 --mem 32 \
5 --docker-image gcr.io/google-containers/alpine-with-bash:1.0 \
6 ls
采用建议:只适合历史研究,不建议新项目采用。README 第一屏就写了 Cook Scheduler 开发已经停止,项目已归档。今天如果要做批处理调度,更应该优先看 Kubernetes Job、Argo Workflows、Ray Jobs、Airflow、Dagster、Prefect、Slurm,或者云厂商托管队列。Cook 的价值在于理解当年 on-prem、资源受限集群里批处理调度器要解决什么问题。
五、Man Group:金融数据工作台
Man Group 这组项目更贴近日常数据工作:时间序列数据存储、DataFrame 探索、notebook 报告调度、HTML/PDF 报告生成。整体比 Two Sigma 这组更“现在能用”。
5. man-group/ArcticDB:面向 DataFrame 的时间序列数据库
GitHub:man-group/ArcticDB
默认分支最后提交:2026-06-05
PyPI:arcticdb==6.18.0
许可证:Business Source License 1.1,生产和 Database Service 有授权限制
ArcticDB 是这份清单里最值得认真看的项目之一。它把金融时间序列数据存到 S3、LMDB、Azure Blob 等后端里,但使用体验尽量贴近 pandas。它的定位很清楚:Pandas in, Pandas out。
完整架构与实现原理:
graph TB
subgraph "源码边界"
S1["python/arcticdb
Python API 和 binding"]
S2["cpp/arcticdb
C++ storage engine"]
S3["cpp/arcticdb/version
版本、snapshot、metadata"]
S4["cpp/arcticdb/pipeline
查询计划和执行"]
S5["cpp/arcticdb/storage
后端适配器"]
end
subgraph "用户输入"
A1["pandas DataFrame"]
A2["NumPy array / Python native types"]
A3["symbol
例如 BTCUSDT.bars"]
A4["library
research / prod / backtest"]
A5["URI
lmdb:// / s3:// / azure://"]
end
subgraph "Python API 层"
B1["Arctic(uri)"]
B2["create_library / get_library"]
B3["write / append / update"]
B4["read / read_batch"]
B5["QueryBuilder
filter / project / date_range"]
end
subgraph "版本化数据模型"
C1["Library"]
C2["Symbol"]
C3["Version id"]
C4["Snapshot"]
C5["User metadata"]
C6["Tombstone / compaction"]
end
subgraph "C++ 写入和查询引擎"
D1["DataFrame normalization"]
D2["Columnar segment builder"]
D3["index segment
时间范围和列信息"]
D4["compression / encoding"]
D5["pipeline execution
scan / filter / project"]
D6["range pruning
少读无关 segment"]
end
subgraph "存储后端"
E1["LMDB
本地 PoC"]
E2["S3 / MinIO / Ceph"]
E3["Azure Blob"]
E4["In-memory"]
end
subgraph "输出和采用边界"
F1["VersionedItem"]
F2[".data -> pandas DataFrame"]
F3[".metadata / .version"]
F4["收益:版本化、列裁剪、时间范围读取"]
F5["风险:BSL 授权、并发写、备份恢复"]
end
S1 --> B1
S2 --> D1
S3 --> C1
S4 --> D5
S5 --> E1
A1 --> B3
A2 --> B3
A3 --> B3
A4 --> B2
A5 --> B1
B1 --> B2
B2 --> C1 --> C2 --> C3
B3 --> D1 --> D2 --> D3 --> D4
B5 --> D5 --> D6
C3 --> C4
C3 --> C5
C3 --> C6
D4 --> E1
D4 --> E2
D4 --> E3
D4 --> E4
D6 --> F1
F1 --> F2
F1 --> F3
F2 --> F4
E2 --> F5
ArcticDB 的关键设计是“DataFrame 数据库,但不强制数据库服务常驻”。上层是 Python API,研究员按 library/symbol 写入和读取;中间是版本化数据模型;底层 C++ 引擎把 DataFrame 拆成列式 segment,做压缩、索引、裁剪和查询;最终落到 S3、Azure Blob、LMDB 或内存后端。
值得单独讲清的是它**“无服务器为什么还能原子提交”——这正是“版本化数据模型”底下真正的机制。ArcticDB 没有数据库服务进程,就是一个 Python wheel + C++ 扩展,任意数量互不协调的客户端直接连对象存储读写;它在普通 KV 对象存储上做到原子性,靠的是一套四层键 + 自底向上写**的物理模型:
- 数据层(
tdata):不可变、压缩后的列式 segment。DataFrame 会被同时按行和列切片(tiling,默认约 10 万行 × 127 列一块)。 - 索引层(
tindex):不可变,对数据层建 B-Tree 索引,记录每个 segment 的时间范围与列信息,读取时据此做 range pruning,只读相关 segment。 - 版本层(
ver):不可变的逆时序链表,每个节点指向“上一个版本”和“本版本的 index key”;新版本复用未变的 data block(去重),只写新增部分。 - 引用层(
vref):唯一的可变层,一个指向版本链表头的指针。
写入严格自底向上:先写 data(键不可发现、对 reader 不可见)→ 写 index → 写 version 节点 → 最后翻转 vref 指针。在指针翻转之前新数据对任何读者都不可见,任何中途崩溃都不会留下可观测的半成品——这就是它“无服务器也能原子提交、写过的版本永不被破坏”的根本原因。代价是它只保证原子性/一致性/持久性、不做事务隔离(是 OLAP 不是 OLTP):同一 symbol 的并发写是 last-writer-wins,要安全并发写同一 symbol 必须改用 staged / parallel writes(先暂存再 finalize);不同 symbol 之间则可自由并发,因此天然横向扩展。
它为什么好:
- 研究接口贴近 pandas:对研究员来说,读写单位仍然是 DataFrame,不需要先建复杂 schema。
- symbol 天然适合金融数据:一个股票、合约、因子表、特征集都可以是 symbol;symbol 之间相互独立,便于横向扩展。
- 版本和 snapshot 是内置语义:数据回补、供应商修正、清洗逻辑变更都不会覆盖掉历史版本。
- 对象存储友好:S3 这类后端便宜、容量大、可横向扩展,适合长期行情和研究结果存档。
- C++ 引擎承担重活:过滤、列裁剪、压缩和聚合不完全依赖 Python 循环,性能上比“自己管理一堆 CSV/Parquet”更可控。
它的局限在于许可证和部署边界:技术上很像研究数据基础设施,但生产商业使用要先确认 BSL 授权。
源码级目录结构和模块分工:
1ArcticDB/
2 python/arcticdb/arctic.py Python 顶层 Arctic 入口
3 python/arcticdb/version_store/ Library、VersionStore、读写 API
4 python/arcticdb/adapters/ S3、LMDB、Mongo、Azure、GCP、内存后端适配
5 cpp/arcticdb/pipeline/ DataFrame 读写管线、列裁剪、时间范围过滤
6 cpp/arcticdb/processing/ QueryBuilder 的过滤、投影、聚合执行
7 cpp/arcticdb/column_store/ 列式 segment、chunked buffer、string pool
8 cpp/arcticdb/codec/ 压缩、编码、segment header
9 cpp/arcticdb/storage/ library manager 和存储抽象
10 cpp/arcticdb/python/ Python/C++ 绑定和 pandas 转换
11 python/tests/ 与 python/benchmarks/ 功能测试、存储夹具、性能基准
Python 入口在 python/arcticdb/arctic.py,Arctic("s3://...") 或 Arctic("lmdb://...") 会根据 URI 选择 adapter。version_store/library.py 是研究员最常接触的 lib.write/read/update/append API,_store.py 则把这些操作下沉到 C++ engine。_normalization.py、read_result.py、processing.py 负责 pandas/NumPy 类型归一化、读回包装和 QueryBuilder 语义。
C++ 侧才是性能核心。cpp/arcticdb/pipeline/write_frame.cpp、read_pipeline.cpp、read_query.cpp 负责把 DataFrame 变成内部 frame slice 并按条件读回;column_store/memory_segment.hpp、column.cpp、string_pool.cpp 管列式内存结构;codec/encode_v1.cpp、encode_v2.cpp、zstd.hpp、lz4.hpp 负责压缩编码;processing/query_planner.cpp、expression_node.cpp、operation_dispatch*.cpp 把 Python 的过滤和聚合表达式编译成 C++ 执行路径。
后端抽象很清楚:python/arcticdb/adapters/s3_library_adapter.py、lmdb_library_adapter.py、azure_library_adapter.py 等先解决配置和 library 管理,底层 cpp/arcticdb/storage/ 再统一读写 key/segment。它之所以适合金融时间序列,是因为 symbol/version/snapshot 是数据模型的一部分,列式 segment、索引裁剪和对象存储后端是物理实现的一部分,两边没有脱节。
安装:
1pip install arcticdb
2# 或者
3conda install -c conda-forge arcticdb
本地 LMDB 示例:
1import arcticdb as adb
2import pandas as pd
3
4ac = adb.Arctic("lmdb:///tmp/arcticdb")
5lib = ac.get_library("market", create_if_missing=True)
6
7df = pd.DataFrame(
8 {"price": [100.0, 101.5, 99.8]},
9 index=pd.date_range("2026-01-01", periods=3, freq="min"),
10)
11
12lib.write("BTCUSDT_1m", df)
13back = lib.read("BTCUSDT_1m").data
14print(back)
S3 示例:
1import arcticdb as adb
2
3ac = adb.Arctic("s3://MY_ENDPOINT:MY_BUCKET?aws_auth=true")
4lib = ac.get_library("ticks", create_if_missing=True)
它适合的场景:
- tick、bar、因子、特征、回测中间结果
- 需要版本、snapshot、append、update 的研究数据
- 想保留 pandas 使用体验,但 CSV/Parquet 文件堆已经管不住了
采用建议:技术上很可用,但商业使用前必须先看许可证。ArcticDB 的 README 明确写了生产使用、商业环境和 Database Service 需要付费授权。它是 source-available,不是普通 OSI 许可证下的开源数据库。补一个常被忽略的细节:BSL 1.1 自带“到期自动开源”条款——每个发布版本会在它各自发布日约 2 年后(最迟不超过首次公开发布的第 4 周年)自动转为 Apache 2.0;许可证里署名的授权主体是 Man Group Operations Limited。如果你只是个人研究、内部原型和非商业实验,它非常值得试。如果你要在公司生产系统里用,先让法务和授权流程介入。
6. man-group/dtale:浏览器里的 pandas DataFrame 分析界面
GitHub:man-group/dtale
默认分支最后提交:2026-05-11
PyPI:dtale==3.22.0
许可证:LGPL-2.1
D-Tale 是最容易马上用起来的项目。它把 pandas DataFrame 交给 Flask 后端和 React 前端展示,让你在浏览器里筛选、排序、画图、看相关性、做缺失值分析。
完整架构与实现原理:
graph LR
subgraph "源码边界"
S1["dtale/app.py
Flask app 和路由注册"]
S2["dtale/views.py
数据查询 API"]
S3["dtale/global_state.py
DataFrame session store"]
S4["dtale/computations
统计、相关性、缺失值"]
S5["frontend/static/dtale
React 表格主界面"]
S6["frontend/static/popups
图表、筛选、设置弹窗"]
end
subgraph "Python 侧入口"
A1["pandas DataFrame / Series / Index"]
A2["dtale.show(df)"]
A3["instance id"]
A4["in-memory dataset registration"]
A5["可选:subprocess / host / port"]
end
subgraph "后端服务层"
B1["Flask web server"]
B2["REST endpoints"]
B3["data slicing
分页、排序、过滤"]
B4["statistics
describe / corr / missing"]
B5["chart data builders"]
B6["code export
把 UI 操作转 pandas 代码"]
end
subgraph "前端交互层"
C1["React app"]
C2["grid virtualization"]
C3["column menu / filters"]
C4["charts / correlations"]
C5["state management"]
C6["export controls"]
end
subgraph "输出和边界"
D1["浏览器里的 DataFrame inspector"]
D2["CSV / JSON / PNG / generated code"]
D3["收益:探索快、低接入成本"]
D4["限制:不替代长期 dashboard 和数据治理"]
end
S1 --> B1
S2 --> B2
S3 --> A4
S4 --> B4
S5 --> C1
S6 --> C4
A1 --> A2 --> A3 --> A4
A5 --> B1
A4 <--> B1
B1 --> B2
B2 --> B3
B2 --> B4
B2 --> B5
B2 --> B6
B3 --> C1
B4 --> C4
B5 --> C4
C1 --> C2
C1 --> C3
C1 --> C5
C1 --> C6
C2 --> D1
C6 --> D2
D1 --> D3
D1 --> D4
D-Tale 的架构是典型的“Python 对象 + 本地 Web UI”。dtale.show(df) 不会把数据导入外部数据库,而是在当前 Python 进程中注册一个 DataFrame 实例,Flask 暴露查询和变换接口,React 前端按 instance id 请求数据、应用筛选和展示图表。
它为什么好:
- 一行代码启动 UI:研究过程不需要搭 BI 服务,也不需要把数据搬出 notebook。
- 状态留在 Python 进程里:适合临时探索,数据生命周期和当前研究会话一致。
- 前端承担交互复杂度:排序、列菜单、图表、相关性、缺失分析都在浏览器里组织成可点击工作流。
- 可以导出代码:这点对研究很重要,点出来的筛选和图表如果能转回 Python 代码,就不会变成不可复现的手工操作。
- JupyterHub 适配:server proxy 支持让它能在多用户 notebook 环境里跑。
它的边界是内存和安全:DataFrame 仍在 Python 进程里,超大数据、多人共享权限、长期 dashboard 都不是它最擅长的场景。
源码级目录结构和模块分工:
1dtale/
2 dtale/app.py 与 views.py Flask 应用、路由注册、DataFrame REST API
3 dtale/{query,describe,...}.py 筛选、描述统计、相关性、列分析、数据整形
4 dtale/cli/ 命令行启动和 csv/json/excel/parquet/arctic 加载器
5 dtale/dash_application/ 图表和 Dash 扩展页面
6 dtale/templates/ 与 static/ Flask 模板和静态资源
7 frontend/static/ React、Redux、表格、弹窗、图表、筛选前端源码
8 tests/dtale/ API、前端行为、加载器和集成测试
后端入口在 dtale/app.py,它创建 Flask app、配置实例状态并注册路由;dtale/views.py 是 API 的集中区域,浏览器里看到的大部分 grid、列菜单、筛选、导出请求都会打到这里。DataFrame 不是落库对象,而是挂在进程内的实例状态里,所以 dtale.show(df) 能一行启动,也因此受限于当前 Python 进程内存。
数据变换逻辑分散在一组小模块:query.py 解析筛选表达式,describe.py 处理统计摘要,column_analysis.py 做单列分布和缺失值分析,correlations.py 做相关性,data_reshapers.py 做 reshape,pandas_util.py 封装 pandas 兼容细节。CLI 在 dtale/cli/script.py,cli/loaders/ 下的 csv_loader.py、parquet_loader.py、arcticdb_loader.py 说明它不只服务 notebook,也能从命令行直接打开数据文件。
前端在 frontend/static/,dtale/、popups/、redux/、filters/、dash/ 对应主表格、弹窗、状态管理、筛选器和图表页面。D-Tale 的好用来自这个分工:Python 后端只做数据查询和计算,复杂交互留给 React 前端,用户操作再通过 REST API 转成 pandas 变换或导出代码。
安装:
1pip install dtale
2# 或者
3conda install dtale -c conda-forge
最小用法:
1import pandas as pd
2import dtale
3
4df = pd.read_parquet("features.parquet")
5d = dtale.show(df)
6d.open_browser()
在脚本里阻塞运行:
1import pandas as pd
2import dtale
3
4if __name__ == "__main__":
5 dtale.show(pd.DataFrame([1, 2, 3, 4, 5]), subprocess=False)
在 JupyterHub 后面使用时,可以打开 server proxy:
1import pandas as pd
2import dtale
3import dtale.app as dtale_app
4
5dtale_app.JUPYTER_SERVER_PROXY = True
6dtale.show(pd.DataFrame([1, 2, 3]))
采用建议:非常适合个人和团队的数据探索。它不会替代严肃的数据质量系统,也不适合作为长期线上 dashboard,但作为研究阶段的 DataFrame inspector 很好用。你如果经常在 notebook 里反复 df.head()、df.describe()、df.corr(),D-Tale 值得直接装。
7. man-group/notebooker:把 notebook 生产化成定时报表
GitHub:man-group/notebooker
默认分支最后提交:2026-06-05
PyPI:notebooker==0.8.1
许可证:AGPL-3.0
Notebooker 解决的是一个很常见的问题:研究逻辑写在 Jupyter notebook 里,但最后你需要每天定时跑、带参数跑、把结果保存成网页报告,还要能从 Web UI 搜索历史结果。
完整架构与实现原理:
graph TB
subgraph "源码边界"
S1["notebooker/cli.py
命令入口"]
S2["notebooker/scheduler
定时执行和清理"]
S3["notebooker/webapp
Web 展示和运行入口"]
S4["notebooker/core
模板发现、执行、存储"]
S5["notebooker_kernel
执行 kernel 适配"]
end
subgraph "报告定义"
A1["Git repo"]
A2["Notebook templates"]
A3["参数默认值 / schema"]
A4["requirements / kernel name"]
A5["输出模板和静态资源"]
end
subgraph "触发入口"
B1["Web UI run report"]
B2["notebooker-cli execute-notebook"]
B3["start-scheduler"]
B4["外部 cron / Airflow / 手动触发"]
end
subgraph "执行管线"
C1["Template discovery"]
C2["Parameter injection"]
C3["Papermill execution"]
C4["Jupyter kernel"]
C5["Executed notebook"]
C6["nbconvert HTML"]
C7["失败捕获
kernel error / timeout / missing parameter"]
end
subgraph "状态和结果库"
D1["MongoDB"]
D2["report definition"]
D3["run metadata"]
D4["latest successful snapshot"]
D5["rendered HTML / notebook output"]
end
subgraph "展示和采用边界"
E1["Notebooker Webapp"]
E2["历史结果搜索"]
E3["最新成功结果"]
E4["收益:notebook 报告服务化"]
E5["限制:需要 MongoDB、kernel、权限隔离"]
end
S1 --> B2
S2 --> B3
S3 --> B1
S4 --> C1
S5 --> C4
A1 --> A2 --> C1
A3 --> C2
A4 --> C4
A5 --> C6
B1 --> C1
B2 --> C1
B3 --> C1
B4 --> B2
C1 --> C2 --> C3 --> C4 --> C5 --> C6
C3 --> C7
C6 --> D1
C5 --> D1
C7 --> D1
D1 --> D2
D1 --> D3
D1 --> D4
D1 --> D5
D2 --> E1
D3 --> E2
D4 --> E3
D5 --> E1
E1 --> E4
E1 --> E5
Notebooker 的完整架构可以理解为“notebook 模板仓库 + 执行引擎 + 报告结果库 + Web 展示”。模板来自 git,执行由 Papermill 负责参数注入和运行,nbconvert 把执行后的 notebook 变成 HTML,MongoDB 存报告结果和元数据,Web UI 提供运行、搜索和查看。
它为什么好:
- 保留 notebook 的低门槛:研究员仍然用熟悉的 notebook 写逻辑,而不是一开始就重写成服务。
- 把一次性 notebook 变成模板:参数化之后,同一份 notebook 可以生成不同日期、组合、策略、市场的报告。
- 结果可追溯:每次运行的参数、模板、执行结果和 HTML 都进 MongoDB,便于追历史。
- 执行和展示解耦:运行 notebook 是计算任务,查看 HTML 是读取结果;这比每次打开 notebook 重新执行更稳定。
- 适合日报和监控:风险、策略表现、组合暴露、数据质量都可以用这个模式先跑起来。
它的成本也来自这里:你要管理 MongoDB、kernel、依赖、执行权限和 notebook 安全,不是一个轻量前端组件。
源码级目录结构和模块分工:
1notebooker/
2 notebooker/execute_notebook.py notebook 执行入口
3 notebooker/scheduler_core.py 定时任务和执行调度核心
4 notebooker/serialization/ MongoDB 序列化和结果持久化
5 notebooker/web/app.py Flask Web 应用入口
6 notebooker/web/routes/ 模板、运行、结果、调度、Prometheus 路由
7 notebooker/web/templates/ 报告列表、运行表单、结果展示页面
8 notebooker/web/static/notebooker/ 前端 JS、cron 工具、页面交互
9 notebooker/utils/ 模板发现、结果处理、邮件、清理、缓存
10 tests/{unit,integration,...}/ 单元、集成、回归和 sanity 测试
执行链路从 notebooker/execute_notebook.py 进入,核心工作是加载模板、注入参数、调用 Papermill 执行、再把执行结果交给转换和存储层。utils/notebook_execution.py、utils/templates.py、utils/results.py 是最值得读的辅助模块,分别处理 notebook 运行、模板元数据和结果归档。convert_to_py.py 说明它还考虑过把 notebook 转成 Python 脚本来管理或测试。
服务端在 notebooker/web/app.py,路由拆得比较清楚:web/routes/templates.py 管模板列表,report_execution.py 管运行请求,pending_results.py 和 serve_results.py 管结果查询和展示,scheduling.py 管调度页面,prometheus.py 暴露监控指标。serialization/mongo.py、serializers/pymongo.py 把报告结果、参数和元数据落到 MongoDB。
调度相关代码在 scheduler_core.py、standalone_scheduler.py、web/scheduler.py。这几个文件把“马上执行一次”和“按 cron 周期执行”分开。前端不是复杂 SPA,而是 web/templates/ 加 web/static/notebooker/*.js,足够支撑运行表单、历史结果、调度配置这类内部工具页面。
它的组件包括:
- notebook 模板
- 参数化执行
- webapp
- MongoDB 结果存储
- CLI:
notebooker-cli
安装和启动的核心步骤:
1pip install notebooker
2
3python -m ipykernel install --user --name=notebooker_kernel
4
5notebooker-cli \
6 --mongo-host localhost:27017 \
7 --mongo-user jon \
8 --mongo-password hello \
9 start-webapp \
10 --port 11828
如果只是想快速看 demo:
1cd docker
2docker-compose up
采用建议:有真实价值,但不是轻量工具。它需要 MongoDB、kernel 环境、权限管理、执行隔离,还要考虑 notebook 里代码的安全边界。另一个重要点是 AGPL-3.0,如果你要在公司内网服务化使用,需要认真评估许可证。它适合已有大量 notebook 报告、又不想马上重写成正式报表系统的团队。
8. man-group/PyBloqs:用 Python 生成静态 HTML/PDF 报告
GitHub:man-group/PyBloqs
默认分支最后提交:2025-10-23
PyPI:pybloqs==1.4.3
许可证:LGPL-2.1
PyBloqs 解决的是“分析代码到可读报告”的最后一段距离。它把文本、表格、图表、图片封装成 block,再组合成 HTML、PDF、SVG、PNG。
完整架构与实现原理:
graph LR
subgraph "输入对象"
A["Text / Markdown"]
B["pandas DataFrame"]
C["matplotlib / plotly / highcharts"]
D["Image / raw HTML"]
end
subgraph "Block 模型"
E["Block"]
F["Table block"]
G["Plot block"]
H["Text block"]
I["Style dict / CSS"]
end
subgraph "布局层"
J["VStack"]
K["HStack"]
L["Nested report tree"]
end
subgraph "渲染层"
M["HTML renderer"]
N["Browser preview"]
O["PDF export"]
P["SVG / PNG export"]
end
A --> H
B --> F
C --> G
D --> E
F --> J
G --> K
H --> J
E --> K
I --> L
J --> L
K --> L
L --> M
M --> N
M --> O
M --> P
PyBloqs 的架构是“对象转 block,block 组成布局树,布局树渲染成静态产物”。每个表格、图、文本都是一个 block;HStack 和 VStack 只负责组合;最终 renderer 把嵌套树转成 HTML,再进一步导出 PDF、SVG、PNG。
它为什么好:
- 报告结构可组合:比手写 HTML 更适合从分析代码里拼报告。
- 静态输出友好:很多金融报告只需要邮件、PDF、文件系统归档,不需要在线服务。
- 开发反馈快:单个 block 可以单独 show,调完再组合成完整报告。
- 和 pandas/matplotlib 生态贴近:研究员不需要换工具链。
- CSS 可控:对“够好”的内部报告来说,样式可配置比全功能 BI 更重要。
它不是实时 dashboard。它好的地方正是它克制:把 Python 分析结果变成一份稳定、可发送、可归档的静态报告。
源码级目录结构和模块分工:
1PyBloqs/
2 pybloqs/block/ Block 基类、文本、表格、图片、代码、布局和 formatter
3 pybloqs/plot/ matplotlib 等图形对象封装
4 pybloqs/htmlconv/ HTML 到 PDF/PNG/SVG 的浏览器或 wkhtmltox 转换
5 pybloqs/server/ htmx 风格的轻量动态 block 服务
6 pybloqs/jinja/ HTML 模板
7 pybloqs/static/ CSS、JavaScript、DataTables、样式资源
8 tests/regression/ 输入脚本和黄金 HTML 输出
核心入口是 pybloqs/block/base.py。它定义所有 block 的公共协议:接收 Python 对象、保存样式、生成 HTML 片段。text.py、table.py、image.py、code.py、altair.py 分别处理不同内容类型;layout.py 里的 HStack、VStack 负责组合树;table_formatters.py 和 table_formatter_builder.py 负责把 pandas 表格里的数值、颜色、格式规则转成 HTML/CSS。
渲染层在 pybloqs/html.py 和 jinja/table.html。它把 block 树渲染成完整 HTML,再通过 htmlconv/html_converter.py 选择转换后端。chrome_headless.py、wkhtmltox.py、puppeteer.js 说明 PyBloqs 支持多种 HTML 转图片/PDF 路径。tests/regression/pybloqs_input/ 和 html_output/ 是判断它是否稳定的关键:报告生成工具最怕“今天样式变一点,明天 PDF 断版”,所以黄金输出测试很重要。
server/ 不是主线能力,但能看出作者想把静态 block 扩展成轻量交互组件。server/block/select.py、poll.py、refresh.py、tabs.py 通过 htmx 做选择、轮询、刷新和 tab,这比直接上完整前端框架更符合内部报告工具的定位。
安装:
1pip install pybloqs
最小示例:
1from pybloqs import Block, HStack, VStack
2import pandas as pd
3from matplotlib import pyplot as plt
4
5text_block = Block(
6 "Daily risk report",
7 styles={"text-align": "center", "color": "blue"},
8)
9
10df = pd.DataFrame([[1.0, 2.0], [3.0, 4.0]], columns=["a", "b"])
11table_block = Block(df)
12
13plt.figure()
14plt.plot(df["a"], df["b"])
15plot_block = Block(plt.gcf())
16
17report = VStack([text_block, HStack([plot_block, table_block])])
18report.save("report.pdf")
采用建议:适合静态报告,不适合复杂交互分析。如果你只是要每天生成一份“够好”的 HTML/PDF,PyBloqs 可以用。如果你要权限、筛选、钻取、实时刷新,应该看 Superset、Metabase、Streamlit、Dash 或内部 BI 系统。
六、Jane Street:OCaml、性能追踪和硬件设计
Jane Street 的开源项目很有辨识度:它们不讨好 Python 研究员,而是在展示一套长期押注 OCaml 的工程体系。对普通量化研究员来说,最容易独立使用的是 magic-trace;Core、Async、Hardcaml 都要求你接受 OCaml 技术栈。
9. janestreet/core:OCaml 的工业级标准库替代
GitHub:janestreet/core
默认分支最后提交:2026-05-15
许可证:MIT
Core 是 Jane Street 的 OCaml 标准库替代方案。它在 Base 之上提供更多模块、稳定序列化、时间、命令行、容器等能力。仓库里的 core.opam 显示它要求 OCaml >= 5.1.0、Dune >= 3.17.0。
完整架构与实现原理:
graph TB
subgraph "应用入口"
A["OCaml source files"]
B["open! Core"]
C["Dune build"]
end
subgraph "Core public surface"
D["core/core library"]
E["Command CLI"]
F["Time_ns / Date / Time_zone"]
G["Map / Set / Hashtbl with comparator witnesses"]
H["List / Array / Sequence / Or_error"]
I["Stable modules"]
end
subgraph "Jane Street 基础层"
J["Base"]
K["Stdio"]
L["sexplib / bin_prot"]
M["ppx_jane / ppx_sexp_conv"]
end
subgraph "工程化支撑"
N["tests / expect tests"]
O["bench-bin benchmarks"]
P["native / js_of_ocaml runtime support"]
end
A --> B
C --> B
B --> D
D --> E
D --> F
D --> G
D --> H
D --> I
D --> J
D --> K
D --> L
M --> D
D --> N
D --> O
D --> P
Core 不是简单“加一些工具函数”,而是把 OCaml 默认标准库的很多入口替换成 Jane Street 自己长期使用的基础层。open! Core 会把应用代码带入一套更一致的命名、错误处理、容器、时间、序列化和命令行约定里;底层依赖 Base 和 Stdio,上层再叠加 Command、Time_ns、Map/Set/Hashtbl、Stable 等模块。
它为什么好:
- 统一标准库行为:OCaml 原生 stdlib 历史包袱比较多,Core 用一套一致 API 降低团队协作成本。
- 类型系统承载更多约束:比如 comparator-aware 容器让 Map/Set 的比较逻辑成为类型的一部分,减少隐式排序错误。
- 序列化是一等公民:
sexplib适合配置和可读调试,bin_prot适合高效二进制协议,Stable模块约定适合长期数据格式演进。 - 命令行和测试生态完整:
Command、expect tests、benchmarks 都在同一工程栈里,库不是只服务 toy program。 - PPX 深度集成:大量样板代码通过派生生成,数据结构、序列化、比较、打印都能保持一致。
它的代价也很明确:如果你不是 OCaml 项目,Core 没有直接复用价值;如果你是小型 OCaml 项目,它比 Base 更重,迁移时还会影响默认 stdlib 习惯。
源码级目录结构和模块分工:
1core/
2 core/src/ Core 主库,容器、时间、错误、序列化、系统工具
3 command/src/ Command 命令行 DSL
4 validate/src/ 数据校验组合器
5 filename_base/src/ 文件名基础工具
6 heap_block/ OCaml runtime/heap block 相关支持
7 base_for_tests/ 测试辅助库
8 core/bench-bin/ 容器、字符串、时间等 benchmark
9 core/test/ expect tests 和回归测试
源码入口不是某一个巨大的 stdlib.ml,而是 core/src/core.ml。它把大量模块重新导出成 open! Core 的公共表面。容器相关可以从 map.ml、set.ml、hashtbl.ml、hash_set.ml、container.ml 读起,重点看 comparator.ml、comparable.ml、hashable.ml 如何把比较和哈希能力做成显式模块协议。Core 的一个关键工程选择是避免随手用 polymorphic compare,让排序和相等语义更可审查。
错误和序列化在 error.ml、or_error.ml、result.ml、core_bin_prot.ml、stable.ml、stable_module_types.ml。Stable 模块约定很有 Jane Street 特色:长期系统里的数据结构不能只看“现在能不能序列化”,还要考虑格式升级。时间体系可以读 time_float.ml、time_ns_alternate_sexp.ml、date.ml、timezone_js_loader.ml。命令行能力不在主目录下,而是在 command/src/command.ml、shape.ml、env_var.ml,说明 Command 是独立子包再被 Core 生态复用。
如果你想理解它为什么“好”,不要只看函数数量,而要看接口文件 .mli。Core 大量复杂性体现在模块类型和 functor 上,例如 hashtbl_intf.ml、map_intf.ml、container_intf.ml。这些接口把容器行为、比较器、序列化、衍生模块组织成统一模板,团队代码长期使用时就能保持一致风格。
安装通常走 opam:
1opam install core
使用时在 OCaml 文件开头:
1open! Core
2
3let xs = List.range 0 10
4let () = printf "%s\n" (Sexp.to_string ([%sexp_of: int list] xs))
采用建议:如果你写 OCaml,就值得认真用;如果你不写 OCaml,就不用强行研究 API。它的价值在于展示 Jane Street 如何把语言生态打磨成生产系统基础,而不是给 Python 量化研究直接复用。
10. janestreet/magic-trace:Intel PT 级别的高分辨率追踪
GitHub:janestreet/magic-trace
默认分支最后提交:2026-05-29
最新 GitHub release:v1.2.4,2025-04-13
许可证:MIT
magic-trace 是这份清单里低延迟工程价值最高的工具之一。它不是传统 profiler。它基于 Intel Processor Trace,记录的是一段时间内的控制流,再用浏览器 UI 展示函数调用时间线。
完整架构与实现原理:
graph TB
subgraph "源码边界"
S1["bin/magic_trace_bin.ml
CLI 入口"]
S2["src/subcommand.ml
attach / run / snapshot"]
S3["src/perf_tool_backend.ml
调用 perf 工具链"]
S4["src/direct_backend
直接处理 PT 数据"]
S5["src/trace_writer.ml
写 Perfetto 格式"]
S6["src/perf_capabilities.ml
检查 CPU、权限、perf"]
end
subgraph "采集入口"
A1["magic-trace attach -pid"]
A2["magic-trace run command"]
A3["trigger
Ctrl+C / breakpoint / symbol"]
A4["目标进程和符号表"]
end
subgraph "Linux 和 CPU 层"
B1["perf_event_open / perf"]
B2["Intel Processor Trace"]
B3["per-cpu mmap ring buffer"]
B4["ptrace / breakpoint stubs"]
B5["权限和内核参数"]
end
subgraph "解码和符号化"
C1["收集触发点前后窗口"]
C2["perf_decode / PT packet decode"]
C3["ELF symbols"]
C4["perf map / JIT metadata"]
C5["demangle / symbolizer"]
C6["callstack reconstruction"]
C7["time range compression"]
end
subgraph "输出和采用边界"
D1["trace.fxt.gz"]
D2["Perfetto trace model"]
D3["magic-trace.org timeline"]
D4["收益:看到 sampling profiler 看不到的控制流细节"]
D5["限制:Linux + Intel PT + perf + 权限"]
end
S1 --> S2
S2 --> A1
S2 --> A2
S3 --> B1
S4 --> C2
S5 --> D1
S6 --> B5
A1 --> A4
A2 --> A4
A3 --> B4
A4 --> B1
B5 --> B1
B1 --> B2 --> B3
B4 --> B3
B3 --> C1
C1 --> C2
C2 --> C3
C2 --> C4
C3 --> C5
C4 --> C5
C5 --> C6
C6 --> C7
C7 --> D1
D1 --> D2 --> D3
D3 --> D4
D3 --> D5
magic-trace 的关键不是 sampling profiler,而是“硬件控制流追踪 + 事后解码”。Intel PT 在 CPU 层记录分支和控制流信息,perf 把数据放进 ring buffer;当你按 Ctrl+C 或命中某个函数 trigger 时,magic-trace 把触发点前后的 buffer 取出来,结合 ELF 符号、perf map、JIT 信息和 demangle 结果重建调用栈,再压缩成 Perfetto 可读的 trace.fxt.gz。
它为什么好:
- 时间分辨率极高:sampling profiler 只能看到抽样点,Intel PT 把每一次分支/调用/返回都记进硬件 trace,控制流分辨率约 40ns(注意 Intel PT 大约每 5 个事件才更新一次时间戳,因此极短的调用可能显示为“零时长”),适合微秒级甚至更短的路径分析。
- 低侵入:目标程序通常不用改代码,不需要在每个函数里埋点。
- 适合稀有延迟尖刺:ring buffer 模式特别适合“问题发生前几毫秒到底做了什么”这种问题。
- 语言支持有明确边界:官方完整支持的是 OCaml / C / C++ / Rust;Python 只能解码到 C 帧(解释器层),Go / Java / JIT 并不在官方承诺的支持矩阵里。它靠识别 call/ret/tail-jmp 指令重建调用栈,所以 C++ 异常、
longjmp会破坏栈重建(README 明确写 “Exceptions are not currently supported”)。 - 输出接近可视化事实:Perfetto 时间线比一堆堆栈统计更适合定位“谁先阻塞、谁后调用、哪段异常变长”。
限制也很硬:Linux、Intel PT、perf 权限、符号质量、物理机支持都会影响可用性。它好是因为靠硬件能力做了普通 profiler 做不到的事,不是因为 UI 漂亮。
源码级目录结构和模块分工:
1magic-trace/
2 bin/magic_trace_bin.ml CLI 入口
3 src/subcommand.ml attach/run 等子命令调度
4 src/perf_tool_backend.ml 通过 perf tool 采集和解码
5 direct_backend/ 直接读取 perf/PT 数据的后端
6 src/perf_decode.ml trace packet 到事件的解码管线
7 src/elf.ml 与 symbolizer.ml ELF、符号化、demangle
8 src/trace_writer*.ml 生成 Perfetto/fxt 输出
9 src/breakpoint*.c/ml breakpoint 和 ptrace 触发支持
10 src/perf_dlfilter.c perf 动态过滤器
11 demo/ 与 test/ 多语言 demo 和固定 perf 回归样本
CLI 从 bin/magic_trace_bin.ml 进入,随后交给 src/subcommand.ml 解析 attach、run、snapshot 等模式。采集后端有两条路径:src/perf_tool_backend.ml 借助外部 perf 工具链,direct_backend/decoding.ml、manual_perf.ml 则展示了更直接的 PT 数据处理路径。perf_capabilities.ml 会检查当前机器、权限和 CPU 能力,这也是 magic-trace 可用性强依赖环境的原因。
解码链路集中在 src/perf_decode.ml、decode_result.ml、event.ml、real_trace.ml。Intel PT 给出的不是“函数调用列表”,而是压缩后的控制流信息,所以它必须结合 elf.ml、perf_map.ml、perf_map_location.ml、symbolizer.ml、demangle_ocaml_symbols.ml 把地址还原成符号。callstack_compression.ml 负责把重建出的调用栈压缩到适合可视化的大小。
触发机制在 breakpoint.ml、ptrace.ml 以及对应 C stubs,输出在 trace_writer.ml、new_trace_writer.ml。这些文件共同解释了 magic-trace 为什么适合定位“触发点之前发生了什么”:它先让 PT ring buffer 持续记录,命中触发条件后再把窗口内数据切出来并写成 Perfetto trace。
安装方式:
1# 下载 GitHub release 里的二进制后
2chmod +x magic-trace
3./magic-trace -help
4
5# 或者安装 deb 包
6sudo dpkg -i magic-trace*.deb
一个最小演示流程:
1gcc demo.c -ldl -o demo
2./demo
3
4magic-trace attach -pid $(pidof demo)
5# 等几秒后 Ctrl+C
6# 生成 trace.fxt.gz
然后打开 magic-trace.org,加载 trace.fxt.gz。
它的硬约束非常重要:
- Linux only
- Intel CPU,通常要 Skylake 或更新
- 大多数 VM 不支持
- 底层依赖 perf / Intel PT
采用建议:真正做低延迟系统的人值得装一台合适的 Linux 机器试。它不只是性能调优,也适合回答“程序在极短时间里到底做了什么”。但它不是 MacBook 上随手能跑的工具。
11. janestreet/async:OCaml 协作式并发库
GitHub:janestreet/async
默认分支最后提交:2026-05-15
许可证:MIT
Async 是 Jane Street 的 OCaml 异步编程库。它服务的是事件循环、网络、磁盘、超时、RPC 这类外部事件驱动程序。async.opam 显示它依赖 core、async_kernel、async_unix 等 Jane Street 生态组件。
完整架构与实现原理:
graph TB
subgraph "用户代码"
A["open! Async"]
B["Deferred.t"]
C["bind / map / upon"]
D["don't_wait_for"]
end
subgraph "Async_kernel"
E["Ivar"]
F["Scheduler job queue"]
G["Monitor"]
H["Clock / after"]
end
subgraph "Async_unix"
I["file descriptor watchers"]
J["Reader / Writer"]
K["Tcp / Unix / Signal"]
L["thread pool for blocking work"]
end
subgraph "外部世界"
M["network events"]
N["disk / process / timer"]
O["callbacks resume jobs"]
end
A --> B
B --> C
D --> F
C --> E
E --> F
F --> G
H --> F
I --> F
J --> I
K --> I
L --> F
M --> I
N --> I
F --> O
Async 的核心抽象是 Deferred.t:一个未来会完成的值。用户代码通过 bind、map、upon 描述“完成之后继续做什么”,底层由 scheduler 维护 job queue。async_kernel 提供不依赖 Unix 的并发核心,async_unix 把 fd、socket、timer、signal、process 等外部事件接进 scheduler,Monitor 负责异步异常传播和隔离。
它为什么好:
- 并发模型可控:主路径是协作式单线程事件循环,减少共享内存线程模型里的锁和竞态。
- 等待显式化:阻塞点以
Deferred形式出现在类型里,代码审查时更容易看到异步边界。 - 异常不会自然消失:Monitor 让后台任务的错误能被收集、隔离、上抛或记录。
- I/O 抽象完整:Reader、Writer、Tcp、Clock、Signal、Process 等模块组成了服务端程序需要的常用运行时。
- 和 Core 同栈:错误类型、时间类型、命令行、序列化都能沿用同一套约定。
缺点是生态锁定很强:它最适合 Jane Street 风格 OCaml 服务,不适合作为 Python/Go/Rust 项目的间接依赖。
源码级目录结构和模块分工:
1async/
2 src/async.ml 顶层 Async 聚合模块
3 async_command/src/ 支持 Async 的 Command 命令行封装
4 async_rpc/src/ RPC、连接、传输和低延迟 transport
5 async_quickcheck/src/ 异步场景下的 Quickcheck 支持
6 lock_file_async/src/ 异步文件锁
7 persistent_connection/src/ 自动重连的持久连接抽象
8 unpack_sequence/src/ 流式 unpack/parse 序列
9 example/ 与 bench/ 常见 I/O 示例和 scheduler benchmark
这个仓库要特别注意边界:src/async.ml 是顶层聚合模块,它把 core、async_kernel、async_unix 里的能力组织成用户的 open! Async 入口,但真正 scheduler、Ivar、Deferred、Unix fd watcher 的核心实现主要在外部依赖包里。换句话说,本仓库展示的是 Jane Street Async 生态的公共入口、扩展子包和示例,而不是全部调度器源码。
当前仓库里最有工程内容的是 async_rpc/src/。rpc.ml 定义 RPC 接口和调用语义,rpc_transport.ml 管消息传输,rpc_transport_low_latency.ml 与 rpc_transport_low_latency_stubs.c 展示它对低延迟路径的优化。persistent_connection/src/persistent_connection.ml 很适合读:它把“连接断了之后怎么重试、状态怎么暴露、调用者怎么感知”封装成可复用抽象。
示例目录也很有价值。example/server.ml、socket.ml、timeouts.ml、signals.ml、thread_pool_not_stuck.ml 覆盖了服务端程序最常见的事件源;bench/nanos_per_job.ml、handlers.ml、loop.ml 反映了 Async 团队关心 scheduler 每个 job 的开销。这些源码说明 Async 的优势不只是 API 漂亮,而是围绕事件循环成本、异常传播和连接生命周期做了长期工程化。
安装:
1opam install async
概念上你会写这样的代码:
1open! Core
2open! Async
3
4let main () =
5 after (Time_ns.Span.of_sec 1.0)
6 >>= fun () ->
7 printf "done\n";
8 return ()
9
10let () =
11 don't_wait_for (main ());
12 never_returns (Scheduler.go ())
采用建议:只推荐给 OCaml 服务端或交易系统团队。如果你的主要技术栈是 Python、Go、Rust、C++,不需要为了“Jane Street 用它”而引入 OCaml Async。它值得学习的是:交易系统很重视可控的并发模型。
12. janestreet/hardcaml:用 OCaml 设计和仿真硬件
GitHub:janestreet/hardcaml
默认分支最后提交:2026-05-15
许可证:MIT
Hardcaml 是 OCaml 里的硬件设计 DSL。它可以表达硬件设计、做仿真、生成 Verilog/VHDL,并围绕 FPGA / ASIC 工作流做扩展。
完整架构与实现原理:
graph TB
subgraph "设计入口"
A["OCaml modules / functors"]
B["Signal DSL"]
C["ppx_hardcaml interfaces"]
D["Always / Reg_spec / Clocking"]
end
subgraph "中间表示"
E["Circuit graph"]
F["typed input/output interfaces"]
G["names / scopes / hierarchy"]
H["memory / registers / combinational logic"]
end
subgraph "验证路径"
I["Cyclesim"]
J["waveform / waveterm"]
K["expect tests / coverage"]
end
subgraph "交付路径"
L["RTL generation"]
M["Verilog output"]
N["Verilator / synthesis tools"]
O["FPGA / ASIC workflow"]
end
A --> B
C --> F
D --> H
B --> E
F --> E
G --> E
H --> E
E --> I
I --> J
I --> K
E --> L
L --> M
M --> N
N --> O
Hardcaml 的核心是把硬件结构当成 OCaml 程序生成。用户用 Signal DSL、module、functor、PPX interface 描述电路;Hardcaml 把这些描述组织成 circuit graph,保留端口、层级、命名、寄存器、组合逻辑和 memory 信息。验证路径走 Cyclesim、waveform、expect tests;交付路径把同一个 circuit graph 转成 RTL/Verilog,再交给 Verilator、综合工具和 FPGA/ASIC 流程。
它为什么好:
- 硬件接口类型化:输入输出接口不再只是散落的 wire 名字,PPX 能生成 map、iter、端口命名和连接代码。
- 参数化能力强:OCaml 的函数、module、functor 很适合生成宽度、流水线深度、端口数量不同的电路族。
- 仿真先于综合:
Cyclesim和 waveform 让设计能在软件侧快速验证,不必每次都进慢速综合流程。 - 层级和命名可控:硬件调试很依赖信号名,Hardcaml 专门处理 scopes、names、hierarchy 这类问题。
- 适合复杂生成逻辑:当硬件结构本身需要大量程序化生成时,通用语言 DSL 比裸 Verilog 更可维护。
代价是学习曲线高:你需要同时理解 OCaml、硬件时序和后端 FPGA/ASIC 工具链。它不是普通量化研究的库,而是低延迟硬件团队的生产力工具。
源码级目录结构和模块分工:
1hardcaml/
2 src/signal*.ml 与 comb*.ml Signal DSL、组合逻辑、位宽和常量
3 src/circuit*.ml 与 hierarchy.ml circuit graph、层级、命名和数据库
4 src/always*.ml 与 reg_spec.ml 时序逻辑、寄存器、状态机风格 DSL
5 src/cyclesim*.ml cycle-accurate 软件仿真器
6 src/rtl*.ml RTL AST、Verilog/VHDL 生成
7 src/interface*.ml 类型化输入输出接口
8 ppx/src/ ppx_hardcaml 接口派生
9 interface_types/ 接口类型运行时支持
10 docs/lib/ 与 test/ 示例电路、文档样例和回归测试
设计入口通常从 src/signal.ml、comb.ml、bits.ml、constant.ml 开始。它们定义硬件信号的表达能力,包括位宽、常量、组合运算、切片、拼接等。circuit.ml、signal_graph.ml、hierarchy.ml、scope.ml 把这些 signal 组织成可遍历的 circuit graph,并保留命名、层级和源码位置信息。硬件 DSL 好不好,很大程度取决于这些元信息能否在生成 RTL 和调试波形时保留下来。
时序逻辑在 always.ml、always_prim.ml、reg_spec.ml、clocking.ml、clock_domain.ml。这些文件把“下一个 cycle 更新什么寄存器”“哪个 clock domain”“reset 和 enable 怎么处理”从裸 wire 连接提升成更可组合的 OCaml DSL。fifo.ml、ram.ml、multiport_memory.ml、async_fifo.ml 则是可复用硬件组件。
验证和交付是两条并行路径。cyclesim.ml、cyclesim_compile.ml、cyclesim_lookup.ml 负责把 circuit graph 变成软件可跑的 cycle simulator;rtl_ast.ml、rtl_verilog_of_ast.ml、rtl_vhdl_of_ast.ml 负责把同一个 graph 转成 RTL 文本。ppx/src/deriving_hardcaml_records.ml、deriving_hardcaml_variants.ml 是 Hardcaml 好用的关键:它自动生成端口 map、iter、命名和连接代码,让硬件接口更像强类型数据结构。
安装:
1opam install hardcaml ppx_hardcaml hardcaml_waveterm
典型用途:
- 用 OCaml 的类型系统表达硬件信号
- 用函数、functor、list/map 生成可复用电路
- 用
Hardcaml_waveterm打印波形 - 转 Verilog/VHDL 接入后端工具链
采用建议:适合 FPGA / 硬件加速方向的人,不适合普通量化研究员直接采用。它的重要性在于告诉你:低延迟交易公司不只优化软件,也会把部分逻辑推到硬件开发链条里。
七、D.E. Shaw:研究效率和数据可复现性
D.E. Shaw 这组项目看起来不如 ArcticDB 或 magic-trace “大”,但很贴近真实研究流程:自动 import、Python 调 Java、HDF5 文件版本管理、notebook 输出清理。这些小工具的共同点是减少研究过程中的摩擦。
13. deshaw/pyflyby:自动 import 和 import 整理
GitHub:deshaw/pyflyby
默认分支最后提交:2026-06-05
PyPI:pyflyby==1.10.6
许可证:MIT / X11(见 LICENSE.txt,PyPI 也标 MIT;GitHub 的 license 字段因分类器未自动识别会显示 NOASSERTION,应以 README/PyPI 为准)
Pyflyby 的定位是 Python 研究效率工具,核心是给交互式会话和源码 import 管理降噪。
完整架构与实现原理:
graph TB
subgraph "输入场景"
A["IPython / Jupyter REPL"]
B["Python source files"]
C["CLI: py / tidy-imports / find-import"]
end
subgraph "符号知识库"
D["built-in import database"]
E["project .pyflyby files"]
F["collected imports from codebase"]
G["module introspection"]
end
subgraph "解析和重写"
H["unknown symbol detector"]
I["import resolver"]
J["AST/token aware import editor"]
K["unused import detector"]
end
subgraph "输出结果"
L["autoimport into live session"]
M["normalized import block"]
N["missing imports added"]
O["unused imports removed"]
end
A --> H
B --> J
C --> H
C --> J
D --> I
E --> I
F --> I
G --> I
H --> I
I --> L
I --> N
J --> K
K --> M
K --> O
Pyflyby 的核心不是格式化,而是“符号到 import 的解析”,而且这一步发生在 parse time(代码执行之前)——不是靠捕获 NameError 事后兜底,也不是用 proxy 惰性代理对象。它维护一套 import database,既有内置常见库,也可以通过项目级 .pyflyby 文件补充团队自己的别名和模块规则。交互式环境里,autoimporter 每输入一行就先把它解析成 AST、静态扫出“被引用但当前 namespace 里还没定义”的自由名字,再去 database 查这些符号该从哪 import,在代码真正执行之前把对应 import 注入并执行——所以 namespace 里永远不会出现“尚未导入”的名字条目,isinstance 这类操作也不会因为 proxy 伪装而行为错乱。源码模式下,tidy-imports 会解析代码使用到的符号、已有 import 和未使用 import,然后重写 import block。
它为什么好:
- 正好解决 notebook 高频摩擦:研究员经常先试表达式再想 import,autoimport 可以把这种打断降到最低。
- 知识库可项目化:团队内部库、常用别名、特定模块都能沉淀到
.pyflyby,不是只靠全局猜测。 - 交互和源码两条路径统一:同一套 import database 同时服务 REPL 和文件整理。
- 比单纯 isort 更懂缺失 import:isort 主要排序,pyflyby 还关心“这个符号应该从哪里来”。
- 适合研究脚本而非强工程管控:它把研究流里的小阻塞消掉,而不是替代类型检查、lint 和 CI。
它的边界是静态分析深度:动态 import、复杂 alias、运行时 monkey patch 不可能完全推断。工程项目仍然应该搭配 Ruff、pyright/mypy 和测试使用。
源码级目录结构和模块分工:
1pyflyby/
2 bin/ autoipython、tidy-imports、find-import 等 CLI
3 lib/python/pyflyby/ Python 包主体
4 lib/python/pyflyby/_importdb.py import database
5 lib/python/pyflyby/_autoimp.py 交互式 autoimport
6 lib/python/pyflyby/_importclns.py import 清理和重写
7 lib/python/pyflyby/_parse.py Python 源码解析
8 etc/pyflyby/ 内置 std、numpy、common import 规则
9 doc/api/ 与 doc/cli/ API 和命令文档
10 tests/ CLI、解析、importdb、Jupyter 集成测试
读源码从 lib/python/pyflyby/_importdb.py 开始最合适。它把“符号应该从哪里 import”表达成数据库,项目级 .pyflyby 文件和 etc/pyflyby/std.py、numpy.py、common.py 都会进入这套知识库。_imports2s.py、_importstmt.py 负责把 import 语句解析成结构化对象,_idents.py 处理 Python 标识符和 dotted name,这些是后续自动补 import 的基础。
交互式路径在 _autoimp.py、_interactive.py、_comms.py。当 IPython/Jupyter 里出现未知符号时,autoimporter 会查 import database,再把对应 import 注入当前 namespace。源码重写路径在 _importclns.py、_format.py、_import_sorting.py、_parse.py,它们负责找出已用符号、缺失 import、unused import,并保持 import block 格式稳定。
CLI 并不是单独实现一套逻辑,bin/tidy-imports、find-import、collect-imports、transform-imports 最终都会调用 lib/python/pyflyby/_cmdline.py。这让 pyflyby 的设计比较统一:同一套 import database 和 parser 同时服务 REPL、Jupyter 和源码文件。
它提供:
py:命令行 Python multitoolautoimporter:IPython/Jupyter 自动 importtidy-imports:补齐缺失 import、删除 unused import、整理 import blockfind-import、collect-imports、transform-imports等工具
安装:
1pip install pyflyby
2pip install ipython
进入增强版 IPython:
1py
示例:
1In [1]: re.search("[a-z]+", "....hello...").group(0)
2[PYFLYBY] import re
3Out[1]: 'hello'
在现有 IPython 会话里临时加载:
1%load_ext pyflyby
自动加载到 IPython/Jupyter:
1py pyflyby.install_in_ipython_config_file
整理 import:
1tidy-imports foo.py
采用建议:非常适合 notebook 和研究脚本密集的人。它不是替代 Ruff、isort、pyright 的现代工程工具,而是更偏交互式研究环境。对于每天在 notebook 里试东西的量化研究员,它的收益来自高频小摩擦的减少。
14. deshaw/pjrmi:Python 和 Java 之间的 RMI/RPC
GitHub:deshaw/pjrmi
默认分支最后提交:2026-05-26
PyPI:pjrmi==1.13.17
许可证:BSD-3-Clause
PJRMI 解决的是 Python 调 Java 对象的问题。大型金融机构很容易出现这种技术栈:研究侧 Python,生产服务和历史系统偏 Java。PJRMI 让 Python 端拿到 Java 对象代理,像调用 Python 对象一样调用 Java 方法。
完整架构与实现原理:
graph TB
subgraph "Python 侧"
A["Python research code"]
B["pjrmi.connect_*"]
C["Java proxy object"]
D["Python callback / PythonObject"]
E["C++ extension for fast path"]
end
subgraph "传输层"
F["SocketTransport / SSL"]
G["PipedTransport"]
H["UnixFifoTransport"]
I["JNI in-process bridge"]
end
subgraph "Java 侧"
J["PJRmi server / agent"]
K["MethodUtil reflection"]
L["JavaProxyBase"]
M["object registry"]
N["Java classpath objects"]
end
subgraph "调用语义"
O["method dispatch"]
P["argument conversion"]
Q["return conversion"]
R["exception propagation"]
S["tab completion / doc metadata"]
end
A --> B
B --> C
C --> P
D --> P
E --> P
P --> F
P --> G
P --> H
P --> I
F --> J
G --> J
H --> J
I --> J
J --> K
K --> L
L --> M
M --> N
N --> O
O --> Q
O --> R
K --> S
Q --> C
R --> C
S --> C
PJRMI 的实现重点是“Python 代理对象 + Java 反射 + 多种传输”。Python 端通过 connect_to_child_jvm 等入口拿到 Java 对象代理;代理上的方法调用会被转换成远程调用请求,经 socket、pipe、Unix FIFO 或 JNI 进入 Java 侧。Java 侧的 PJRmi、MethodUtil、JavaProxyBase、object registry 负责定位对象、选择重载方法、转换参数和返回值。项目里还包含 PythonObject、PythonFunction、PythonMinion 这类反向调用能力,说明它不是单向 RPC,而是允许 Java 回调 Python。
它为什么好:
- 跨技术栈时很实用:金融机构常见“研究 Python,生产 Java”的历史结构,PJRMI 正好补这条缝。
- 对象语义比 HTTP RPC 更自然:研究员可以直接拿 Java 类、Java 对象、方法和属性做交互探索。
- 反射提升可用性:方法签名、doc、补全、重载解析都能从 Java 侧暴露出来,体验不像裸 socket。
- 传输模式灵活:本地 child JVM、in-process JNI、网络 server 都有对应路径,适合不同部署距离。
- 支持双向桥接:Java 可以持有 Python callback,这对复杂集成比“Python 调一次 Java 函数”强很多。
它的风险也很实际:跨语言对象生命周期、异常边界、序列化成本、权限隔离、JVM classpath 和版本冲突都会变成运维问题。只有已有 Java 资产时才值得引入。
源码级目录结构和模块分工:
1pjrmi/
2 python/pjrmi/ Python 连接入口和工具函数
3 python/extension/extension.cpp Python C++ extension 快路径
4 java/src/main/java/com/deshaw/pjrmi/ Java RMI、proxy、transport、callback 核心
5 java/src/main/java/com/deshaw/python/ pickle、numpy、Python 数据结构转换
6 java/src/main/java/com/deshaw/hypercube/ 多维数组/Hypercube 桥接类型
7 java/jni/src/main/cpp/ JNI in-process bridge
8 cpp/src/main/cpp/ native PJRMI 支撑代码
9 examples/ 与 python/tests/ 示例和跨语言 notebook 测试
Python 入口在 python/pjrmi/__init__.py。这里暴露 connect_to_child_jvm 等连接函数,并把 Python 用户拿到的 Java proxy 包装成自然的 Python 对象。python/pjrmi/_util.py 是辅助工具,python/extension/extension.cpp 用 C++ extension 优化部分调用和转换路径。
Java 侧核心在 java/src/main/java/com/deshaw/pjrmi/PJRmi.java 和 PJRmiAgent.java。PJRmi 管远程调用协议和对象注册,PJRmiAgent 管 agent 生命周期。JavaProxyBase.java 定义 Python 侧代理背后的 Java proxy 基类,MethodUtil.java 负责反射、重载匹配和方法分派,PythonObject.java、PythonFunction.java、PythonMinion.java 处理 Java 调 Python 的反向路径。传输实现分散在 SocketTransport.java、SSLSocketTransport.java、PipedTransport.java、UnixFifoTransport.java、JniPJRmi.java。
数据转换层比表面看起来更重要。com/deshaw/python/PythonPickle.java、PythonUnpickle.java、NumpyArray.java 处理 Python 数据结构和 numpy 风格数组;com/deshaw/hypercube/ 下大量 DoubleHypercube、IntegerHypercube、MaskedHypercube、SlicedHypercube 说明 PJRMI 不只是调几个 Java 方法,还试图让大数组、多维数据和数值计算对象跨语言保持可用。
有两个文章容易一笔带过、但其实是 PJRMI 杀手锏的能力:
- 共享内存零拷贝:对数组密集型负载,
connect_*(..., use_shm_arg_passing=True)会走/dev/shm+mmap+memcpy(仅两进程同主机时有效);更进一步,Java 侧的DoubleMappedHypercube('/dev/shm/x.dat', ...)能和 Python 的numpy.memmap映射同一块共享内存,做到一侧改、另一侧立刻可见,彻底省掉序列化。常规路径则是 socket/pipe 上的帧化序列化消息,大数组用python-snappy压缩——它是 PyPI 上明列的运行依赖,容易被漏装。 - 异步调用与代码热补丁:用
__pjrmi_sync_mode__ = c.SYNC_MODE_JAVA_THREAD让调用立刻返回 JavaFuture、再用c.collect(futures)收割;还能c.inject_source(name, source)动态编译 Java 源码、c.replace_class(Foo, 'Foo.class')热替换正在运行的 JVM 里的类——相当于把一个活的 Java 进程当成可交互、可热改的对象。
也正因为能力这么强,它的安全模型必须当回事:默认 connect_to_socket 未加密未认证,连上基本等于能在 server 进程里执行任意代码;生产场景要靠 PKCS12 keystore + 客户端 CN 传给 Java 侧 isUserPermitted() 做授权,并用 isClassPermitted() 做类白名单。
安装:
1pip install pjrmi
如果要本地构建带 C++ 扩展的 wheel:
1./gradlew wheel
2pip install dist/*.whl
最小示例:
1import pjrmi
2
3c = pjrmi.connect_to_child_jvm()
4
5ArrayList = c.javaclass.java.util.ArrayList
6a = ArrayList([1, 2, 3])
7
8print(a.size()) # 3
9print(a.get(1)) # 2
10print(str(a)) # [1, 2, 3]
采用建议:有真实生产价值,但只有跨 Python/Java 边界时才值得引入。如果你只是普通 Python 项目,不需要它。如果你已经有 Java 服务、Java 风控库、Java 数据访问层,又希望研究员在 Python 里调用,那 PJRMI 值得认真看。
15. deshaw/versioned-hdf5:给 HDF5 文件加版本
GitHub:deshaw/versioned-hdf5
默认分支最后提交:2026-06-03
PyPI:versioned-hdf5==2.4.0
许可证:README 写 BSD-3-Clause
Versioned HDF5 是基于 h5py 的 HDF5 版本管理层。它适合科学数据、金融大文件、实验结果文件这类场景:你希望记录每次修改,能回到旧版本,但又不想把大文件拆成一堆副本。
完整架构与实现原理:
graph TB
subgraph "源码边界"
S1["versioned_hdf5/api.py
VersionedHDF5File API"]
S2["wrappers.py
InMemoryGroup / Dataset wrappers"]
S3["backend.py
写入 chunk 和版本数据"]
S4["versions.py
版本 metadata 和查找"]
S5["hashtable.py
chunk dedup"]
end
subgraph "用户 API"
A1["h5py.File"]
A2["VersionedHDF5File"]
A3["stage_version(name)"]
A4["versioned_file['v1']"]
A5["delete_versions / current_version"]
end
subgraph "写入事务"
B1["打开 staged group"]
B2["create_dataset / resize / delete"]
B3["dirty dataset tracking"]
B4["chunk hashing"]
B5["commit_version"]
end
subgraph "HDF5 内部布局"
C1["_versioned_data group"]
C2["versions table
name / timestamp / attrs"]
C3["raw_data chunks"]
C4["hash table
重复 chunk 去重"]
C5["subchunk_map
版本到 chunk 的映射"]
C6["dataset metadata
shape / dtype / fillvalue"]
end
subgraph "读取和采用边界"
D1["immutable version view"]
D2["h5py compatible dataset"]
D3["旧版本回看"]
D4["收益:同一文件内复现实验数据版本"]
D5["限制:HDF5 并发、文件锁、文件膨胀需要压测"]
end
S1 --> A2
S2 --> B1
S3 --> C1
S4 --> C2
S5 --> C4
A1 --> A2
A2 --> A3
A2 --> A4
A2 --> A5
A3 --> B1 --> B2 --> B3 --> B4 --> B5
B5 --> C1
C1 --> C2
C1 --> C3
C3 --> C4
C3 --> C5
C3 --> C6
A4 --> D1
D1 --> D2
D1 --> D3
D3 --> D4
D3 --> D5
Versioned HDF5 的核心是在一个普通 HDF5 文件内部维护 _versioned_data 结构。用户通过 VersionedHDF5File 包装 h5py.File,用 stage_version(name) 开启一次版本写入;上下文里的 group/dataset 修改先进入 wrappers 和 staged changes,提交时写入版本 metadata、chunk 数据、hash table 和 subchunk map。已提交版本以只读 view 暴露,读取时仍然像操作 h5py dataset 一样访问。
它的实现可以归结为两个 HDF5 原生原语——chunk 去重 + Virtual Dataset:
- 内容寻址去重:dataset 第一次写入被切成定长 chunk,每个 chunk 用 SHA256 求内容 hash,去
hash_table里查(hash_table 把SHA256 -> (start, stop)当字典用)。命中就复用,未命中才把新 chunk 追加到该 dataset 专属的raw_data里。注意raw_data是每个 dataset 各自一个、在它所有版本之间共享,不是跨 dataset 的全局池。 - 版本即 VDS 视图:提交一个版本时,并不复制整份数据,而是为该版本的每个 dataset 创建一个 HDF5 Virtual Dataset(VDS),它的各个区域映射回共享
raw_data里对应 chunk 的切片,从而“拼”出该版本完整的逻辑数组。下一个版本若只改了几个 chunk,就只追加这几个被改的 chunk、再建一个新 VDS,未改的 chunk 直接指向旧位置——这就是 chunk 级 copy-on-write。VDS 正是“已提交版本表现为一个只读、自洽、连纯 h5py 或其他语言的 HDF5 库都能直接读出的普通 group”的实现基础。 版本元数据不是一张集中的 versions 表,而是每个版本一个 group、用attrs记录 parent version,从而构成版本 DAG。
它为什么好:
- 不需要换掉 HDF5 生态:已有 h5py、HDF5 文件和科学计算工具链可以继续用。
- 版本是文件内部概念:不必把每个版本复制成一个完整文件,历史版本和当前版本在同一个容器里。
- chunk 去重降低空间成本:变更很少时,hash table 和 subchunk map 可以避免重复存大量相同数据块。
- 读接口接近 h5py:使用者不需要学习一套完全不同的数据访问模型。
- 适合可复现实验:数据修正、回补、实验复跑都能定位到具体版本,而不是依赖文件名约定。
它的技术边界是 HDF5 本身:并发写入、对象布局、超大规模分布式访问都不是它的强项。另一个关键约束是不要绕过 versioned-hdf5 直接改底层 HDF5,否则版本结构会失真。
源码级目录结构和模块分工:
1versioned-hdf5/
2 versioned_hdf5/api.py VersionedHDF5File 用户 API
3 versioned_hdf5/versions.py version metadata、stage/commit/read
4 versioned_hdf5/backend.py chunk 写入、读取、去重和底层 HDF5 组织
5 versioned_hdf5/hashtable.py chunk hash table
6 versioned_hdf5/staged_changes.py staged transaction 变更记录
7 versioned_hdf5/wrappers.py h5py group/dataset 包装
8 versioned_hdf5/subchunk_map.py subchunk 映射和局部复用
9 versioned_hdf5/slicetools.pyx 高性能 slice 工具
10 tests/ 与 benchmarks/ 版本语义、chunk、性能和字符串测试
用户 API 从 api.py 进入,VersionedHDF5File 包装普通 h5py.File,再把 stage_version() 交给 versions.py 管理。versions.py 是版本生命周期的主线:创建版本、提交版本、按名字或时间读取版本、删除或重放版本都在这里组织。wrappers.py 则把 h5py group/dataset 包成受控对象,避免用户直接绕过版本系统修改底层数据。
真正的数据结构在 backend.py、hashtable.py、subchunk_map.py。backend.py 决定 chunk 如何写入 _versioned_data,hashtable.py 用内容 hash 判断 chunk 是否已经存在,subchunk_map.py 处理更细粒度的数据块映射。staged_changes.py 记录一次 version transaction 里发生了哪些 create、resize、delete、modify。提交时,这些 staged changes 才会变成版本 metadata 和 chunk 引用。
性能相关可以看 slicetools.pyx、cytools.py 以及 benchmarks/。Versioned HDF5 的难点不在“给文件加个版本号”,而在局部修改时如何少复制数据、如何保持 h5py 访问语义、如何让历史版本成为不可变 view。这几个模块正好对应这三个问题。
安装:
1conda install -c conda-forge versioned-hdf5
2# 或者
3pip install versioned-hdf5
最小示例:
1import h5py
2import numpy as np
3from versioned_hdf5 import VersionedHDF5File
4
5fileobject = h5py.File("market.h5", "w")
6versioned_file = VersionedHDF5File(fileobject)
7
8with versioned_file.stage_version("v1") as group:
9 group["close"] = np.ones(10000)
10
11with versioned_file.stage_version("v2") as group:
12 group["close"] = np.arange(10000)
13
14v1 = versioned_file["v1"]["close"][()]
15v2 = versioned_file["v2"]["close"][()]
16
17versioned_file.close()
18fileobject.close()
采用建议:适合已有 HDF5 工作流的人。如果你的数据已经是 Parquet、Iceberg、Delta Lake、ArcticDB,就不一定需要它。如果你所在领域本来就大量使用 HDF5,它能解决数据回补、修正、复现实验结果的问题。
16. deshaw/nbstripout-fast:更快的 notebook 输出清理
GitHub:deshaw/nbstripout-fast
默认分支最后提交:2026-04-21
PyPI:nbstripout-fast==1.1.2
许可证:BSD-3-Clause
这个项目非常朴素,但很实用。notebook 输出如果直接进 git,会导致 diff 巨大、仓库膨胀、审查困难。nbstripout-fast 用 Rust 重写了清理逻辑,README 里给出的目标就是比 Python 版 nbstripout 快很多。
完整架构与实现原理:
graph LR
subgraph "Git 工作流"
A["working tree .ipynb"]
B["git add"]
C[".gitattributes filter=jupyter"]
D["git clean filter"]
E["git object database"]
F["git checkout"]
G["smudge = cat"]
end
subgraph "Rust 清理器"
H["nbstripout-fast CLI"]
I["serde_json parse notebook"]
J[".git-nbconfig.yaml"]
K["strip outputs"]
L["strip execution_count"]
M["strip or keep metadata keys"]
N["optional drop empty cells"]
end
subgraph "结果"
O["small deterministic diffs"]
P["local notebook outputs preserved before add"]
Q["repo history stays clean"]
end
A --> B
B --> C
C --> D
D --> H
H --> I
J --> H
I --> K
I --> L
I --> M
I --> N
K --> E
L --> E
M --> E
N --> E
F --> G
G --> A
E --> O
A --> P
E --> Q
nbstripout-fast 的实现路径是 Git filter。.gitattributes 把 .ipynb 交给 filter.jupyter.clean;git add 时 clean filter 调用 Rust CLI,用 JSON parser 读取 notebook,根据 .git-nbconfig.yaml 删除 output、execution_count、指定 metadata 和空 cell;写入 git object database 的是清理后的 notebook。checkout 时 smudge 通常配置成 cat,所以不会在检出阶段额外生成内容。
它为什么好:
- Rust 版本启动和解析都更轻:相比 Python 版 nbstripout,大仓库里批量处理 notebook 时差距会明显。
- Git 层自动执行:不依赖每个人提交前手动跑命令,规约直接嵌入仓库。
- diff 稳定:输出、执行序号、噪音 metadata 不进提交,code review 只看真实输入变化。
- 本地体验不被破坏:clean filter 作用于进入 git 的内容,不要求研究员每次都清空自己 notebook 的展示结果。
- 配置可审查:保留哪些 key、丢哪些 key 写在
.git-nbconfig.yaml,团队能统一讨论。
它的边界是“清理”,不是 notebook 安全执行或格式校验。它能让仓库干净,但不能保证 notebook 逻辑可复现;这仍然需要依赖锁定、数据快照和 CI 执行检查。
源码级目录结构和模块分工:
1nbstripout-fast/
2 src/main.rs CLI 参数解析和 stdin/stdout 文件处理入口
3 src/lib.rs Python wheel / Rust library 导出
4 src/stripoutlib.rs notebook JSON 清理核心
5 Cargo.toml Rust crate、依赖和构建配置
6 pyproject.toml maturin/Python wheel 打包
7 examples/.git-nbconfig.yaml Git filter 配置示例
8 tests/test_nbstripout_fast.py 功能和兼容性测试
实现非常集中。src/main.rs 负责命令行入口,读取文件或 stdin,然后把 notebook JSON 交给库函数处理。src/stripoutlib.rs 是核心:它解析 notebook 的 cell 列表,根据配置删除 outputs、execution_count、部分 metadata,并按选项丢弃空 cell。src/lib.rs 把这套逻辑导出给 Python wheel,所以 pip install nbstripout-fast 后实际运行的是 Rust 实现。
stripoutlib.rs 里有几个实现细节值得看:pop_recursive 支持删除 metadata.foo.bar 这类嵌套 key;determine_keep_output 同时识别 cell metadata 和 tags 里的 keep_output,并在两者冲突时返回错误;output_matches_regex 会读取 stream、display_data、execute_result 的文本输出,用正则决定是否强制清理。strip_output 则把配置拆成 notebook metadata 级别和 cell 级别的 extra keys,再按 cell 遍历执行清理。这个设计比“删掉所有 outputs”更细,因为团队有时确实要保留少量展示结果或清理特定敏感输出。
这个项目的架构优点正是简单:没有服务端、没有复杂状态,只在 Git clean filter 路径上做一个确定性 JSON 变换。examples/comparison.py 用来和 Python 版 nbstripout 做速度对比,tests/test_notebook.ipynb 和 tests/test_nbstripout_fast.py 验证清理结果。对团队来说,关键不是代码量,而是它把 notebook 仓库治理放到了 Git 的确定性入口上。
安装:
1pip install nbstripout-fast
仓库级配置:
1# .git-nbconfig.yaml
2nbstripout_fast:
3 keep_count: false
4 keep_output: false
5 drop_empty_cells: true
6 extra_keys: []
7 keep_keys: []
配置 git filter:
1echo "*.ipynb filter=jupyter" >> .gitattributes
2
3git config filter.jupyter.clean nbstripout-fast
4git config filter.jupyter.smudge cat
清理已经被 git 跟踪的 notebook:
1git add --renormalize .
2git commit -m "Clean Jupyter notebooks"
采用建议:如果你的仓库里有 notebook,值得直接用。它不是宏大系统,但这类小工具很可能是研究团队代码库长期健康的关键。
八、HRT:C++ 并发、SystemVerilog 和告警 DSL
Hudson River Trading 这组项目贴近低延迟系统的边缘:C++20 协程、SystemVerilog LSP、告警查询 DSL。它们不一定每个都大众化,但都很能说明高频交易团队的工程关注点。
17. hudson-trading/corral:C++20 结构化并发
GitHub:hudson-trading/corral
默认分支最后提交:2026-02-26
许可证:MIT
Corral 是 C++20 的协作式、单线程、结构化并发库。它关注的是 coroutine 任务怎么组织成父子树、怎么取消、怎么传播异常、怎么跟 callback 旧代码桥接。
完整架构与实现原理:
graph TB
subgraph "源码边界"
S1["corral/Task.h
Task 和 coroutine promise"]
S2["corral/Nursery.h
结构化并发作用域"]
S3["corral/Executor.h
事件循环抽象"]
S4["corral/Channel.h / Event.h / Semaphore.h
同步原语"]
S5["corral/asio.h / qt.h
外部 event loop 适配"]
S6["examples / tests
生命周期和取消语义"]
end
subgraph "外部异步来源"
A1["Boost.Asio / standalone Asio"]
A2["callback-based API"]
A3["Qt / custom event loop"]
A4["timer / socket / thread pool boundary"]
end
subgraph "核心 coroutine 抽象"
B1["Task of T"]
B2["promise_type"]
B3["TaskAwaiter"]
B4["Executor"]
B5["ParkingLot"]
B6["CBPortal
callback bridge"]
end
subgraph "结构化并发语义"
C1["Nursery scope"]
C2["parent-child task tree"]
C3["allOf / anyOf"]
C4["sleepFor"]
C5["Channel / Event / Semaphore"]
C6["ErrorPolicy"]
end
subgraph "运行时结果"
D1["co_await 恢复"]
D2["structured cancellation"]
D3["exception propagation"]
D4["scope exit waits for children"]
D5["收益:避免 detached coroutine 泄漏"]
D6["限制:需要 C++20 和团队理解 coroutine 生命周期"]
end
S1 --> B1
S2 --> C1
S3 --> B4
S4 --> C5
S5 --> A1
S6 --> C2
A1 --> B6
A2 --> B6
A3 --> B4
A4 --> B4
B6 --> B3
B1 --> B2
B2 --> B3
B3 --> D1
B4 --> B5
B5 --> D1
B1 --> C1
C1 --> C2
C2 --> C3
C2 --> C4
C2 --> C5
C6 --> D3
C2 --> D2
C2 --> D4
D4 --> D5
D4 --> D6
Corral 的核心原理是 C++20 coroutine 上的结构化并发。Task<T> 表示一个可等待任务,TaskAwaiter 把 coroutine suspension/resume 接到 Corral 的调度语义里,Nursery 负责创建和管理一组子任务,Executor 对接实际事件循环。项目里的 CBPortal、asio.h、Channel、Event、Semaphore、ErrorPolicy 说明它不是只封装 co_await,而是在解决 callback 迁移、任务生命周期、取消、错误传播和同步原语这整套问题。
它为什么好:
- 结构化生命周期:子任务挂在 nursery 下,父作用域退出时必须处理子任务,不容易留下悬空 coroutine。
- 取消和异常有路径:错误不再藏在 callback 深处,可以按父子任务树传播或被策略处理。
- 能渐进接入旧系统:
CBPortal和 Asio adapter 让已有 callback/asio 代码可以一段段迁移到 coroutine。 - 单线程协作式模型清晰:适合低延迟系统里常见的 event loop 思维,避免默认引入复杂多线程调度。
- header-only 便于嵌入:对 C++ 基础设施库来说,部署门槛比单独运行时低。
它的边界也要说清:Corral 不是通用多线程任务系统,也不是业务框架。它更像一套给已有事件循环补结构化并发语义的底层库。
源码级目录结构和模块分工:
1corral/
2 corral/Task.h coroutine task 和 promise 基础
3 corral/Nursery.h 结构化并发的父子任务作用域
4 corral/Executor.h 事件循环适配抽象
5 corral/CBPortal.h callback API 到 coroutine 的桥
6 corral/wait.h anyOf/allOf 等等待组合
7 corral/Channel.h|Event.h|Semaphore.h 同步原语
8 corral/ParkingLot.h 等待/唤醒队列
9 corral/ErrorPolicy.h 异常和取消策略
10 corral/asio.h Boost.Asio 适配层
11 corral/detail/ promise、awaiter、intrusive list 等内部实现
12 examples/ 与 test/ Asio、Qt、channel、nursery、error policy 测试
Corral 是 header-only,主线入口是 corral/corral.h,但理解源码要从 Task.h、Nursery.h、Executor.h 开始。Task.h 定义 coroutine 返回对象和 promise 语义,detail/TaskAwaiter.h、detail/Promise.h 处理 suspend/resume、完成回调和结果存储。Nursery.h 是结构化并发的核心:新任务不是随便 detach,而是挂在某个作用域下,父作用域退出时必须处理子任务完成、取消或异常。
事件循环适配在 Executor.h 和 asio.h。Corral 自己不试图成为一个全平台事件循环,而是让外部 loop 驱动 coroutine resume。CBPortal.h 是很实用的桥接模块,它把旧 callback 风格 API 变成可以 co_await 的对象。同步原语在 Channel.h、Event.h、Semaphore.h,等待组合在 wait.h。内部 detail/IntrusiveList.h、detail/ParkingLot.h、detail/Queue.h 说明它为了低开销等待队列做了自己的侵入式结构,而不是堆一层通用容器。
接入方式很直接,因为它是 header-only:
1# 最简单方式
2cp -R corral /path/to/your_project/include/
Asio 示例风格:
1#include <corral/asio.h>
2
3using tcp = boost::asio::tcp;
4
5corral::Task<tcp::socket> connect_first(
6 boost::asio::io_service& io,
7 tcp::endpoint main,
8 tcp::endpoint backup) {
9
10 tcp::socket main_sock(io), backup_sock(io);
11
12 auto [main_err, backup_err, timeout] = co_await corral::anyOf(
13 main_sock.async_connect(main, corral::asio_nothrow_awaitable),
14 [&]() -> corral::Task<boost::system::error_code> {
15 co_await corral::sleepFor(io, 100ms);
16 co_return co_await backup_sock.async_connect(
17 backup,
18 corral::asio_nothrow_awaitable);
19 },
20 corral::sleepFor(io, 3s));
21
22 if (main_err && !*main_err) co_return main_sock;
23 if (backup_err && !*backup_err) co_return backup_sock;
24 throw std::runtime_error("both connections failed");
25}
采用建议:值得 C++20 异步系统团队研究。它不是通用业务框架,也不是多线程调度器。它最适合已有事件循环或异步 I/O 框架的系统,让你在局部区域逐步引入 coroutine。
18. hudson-trading/slang-server:SystemVerilog language server
GitHub:hudson-trading/slang-server
默认分支最后提交:2026-06-02
许可证:MIT
slang-server 是基于 Slang 的 SystemVerilog LSP。它给 Verilog/SystemVerilog 开发提供 lint、hover、go to definition、references、inlay hints、补全、层次结构等功能。
完整架构与实现原理:
graph LR
subgraph "编辑器侧"
A["VS Code extension"]
B["Neovim client"]
C["generic LSP client"]
end
subgraph "LSP server"
D["server_main"]
E["SlangServer"]
F["ServerDriver"]
G["Config"]
end
subgraph "工程理解"
H[".slang / filelist / top module"]
I["ServerCompilation"]
J["Slang parser and elaboration"]
K["ServerDiagClient"]
end
subgraph "索引和语言能力"
L["SyntaxIndexer"]
M["SymbolIndexer"]
N["ReferenceIndexer"]
O["InstanceIndexer / hierarchy"]
P["Hovers"]
Q["CompletionDispatch"]
R["InlayHintCollector"]
S["CodeActionDispatch"]
end
A <--> C
B <--> C
C <--> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
J --> K
J --> L
J --> M
M --> N
M --> O
M --> P
M --> Q
M --> R
M --> S
K --> C
P --> C
Q --> C
R --> C
S --> C
slang-server 的架构是标准 LSP 外壳加 compiler-grade SystemVerilog 分析。编辑器只通过 LSP 发请求;server 侧的 ServerDriver 和 Config 读取 .slang、filelist、top module 等工程信息,ServerCompilation 调 Slang 完成解析、预处理和 elaboration,然后 SyntaxIndexer、SymbolIndexer、ReferenceIndexer、InstanceIndexer 等组件构建可查询索引,最后由 hover、completion、inlay hint、code action、diagnostics 模块响应编辑器请求。
它为什么好:
- 基于真实编译器前端:SystemVerilog 语法和宏、include、层次结构很复杂,靠正则或轻量 parser 很难做好。
- 工程级上下文:LSP 能力不是只看当前文件,而是结合 filelist、top module 和 elaboration 后的设计层级。
- 编辑器无关:VS Code、Neovim 或其他 LSP client 都能复用同一个 server。
- 硬件开发体验补齐:hover、goto references、inlay hints、hierarchy view 对 HDL 生产力影响很大。
- 有扩展能力:代码里已经有 WCP 波形联动、elaborated hierarchy 浏览、code actions(add define / expand macro)等 HRT 自己需要的工程能力。
它的限制主要是配置:HDL 项目没有正确 filelist、include path、宏定义和 top module 时,server 很难给出可靠语义分析。
源码级目录结构和模块分工:
1slang-server/
2 src/server_main.cpp LSP server 进程入口
3 src/SlangServer.cpp LSP 请求调度
4 src/ServerDriver.cpp workspace、编译、索引主控
5 src/Config.cpp .slang 和用户配置解析
6 src/ast/ ServerCompilation、层次分析、WCP
7 src/document/ Syntax/Symbol index、inlay hint、shallow analysis
8 src/completions/ 补全上下文和分发
9 src/codeactions/ 代码动作,例如 add define、expand macro
10 src/lsp/ 与 include/lsp/ JSON-RPC/LSP 类型和 URI
11 clients/vscode/ 与 clients/neovim/ 编辑器插件
12 tests/cpp/ LSP、索引、hover、completion、diagnostic 测试
进程从 src/server_main.cpp 启动,进入 SlangServer.cpp 处理 LSP 生命周期和请求分发。ServerDriver.cpp 是工程状态主控,负责打开 workspace、读取配置、触发 compilation、更新索引并响应编辑器变更。配置解析在 Config.cpp。这里要纠正一个常见误解:它的配置不是单个 .slang 文件,而是分层的三个 JSON——${workspaceFolder}/.slang/server.json(团队共享、入库)、~/.slang/server.json(个人全局默认)、${workspaceFolder}/.slang/local/server.json(本地覆盖、建议 gitignore);合并规则是标量后者覆盖前者、列表追加(flags 是特例:workspace 的 flags 先替换 user 的作为 base,local 的再追加在后)。真正的源文件清单走 Slang 标准的 .f command file(在配置的 flags 里用 -f path/to/x.f 引用,文件内用 -f 嵌套、-I include、-D NAME=VALUE 宏定义、top-level 指定等)。这就是 include path、宏定义、filelist、top module 对结果影响巨大的原因。
语义核心在 src/ast/ServerCompilation.cpp 和 ServerCompilationAnalysis.cpp。这里调用 Slang parser/elaboration,把 SystemVerilog 工程变成可查询的 compilation。文档和索引模块拆在 src/document/:SyntaxIndexer.cpp 收集语法层符号,SymbolIndexer.cpp 和 SymbolTreeVisitor.cpp 组织符号树,InlayHintCollector.cpp 生成端口和宏相关提示,ShallowAnalysis.cpp 在还没完整 elaboration 时提供轻量分析。
语言能力按功能拆分:hover 在 Hovers.cpp,completion 在 completions/CompletionDispatch.cpp、CompletionContext.cpp,code action 在 codeactions/CodeActionDispatch.cpp、AddDefine.cpp、ExpandMacro.cpp。tests/cpp/golden/ 里有大量 golden JSON/文本输出,这类测试对 LSP 很关键,因为编辑器功能是否正确,最终要看“同一份 HDL 工程能不能稳定返回同一组符号、诊断和补全”。
还有一块文章几乎没展开、但其实是 slang-server 区别于其他 SystemVerilog LSP 的重头戏——WCP(Waveform Control Protocol,波形控制协议):它仿照 LSP 设计、基于 JSON 命令/响应、可走 socket/stdio(客户端实现在 src/ast/WcpClient.cpp)。波形查看器(Surfer,或带 WCP 分支的 GTKWave)作 host、编辑器侧作 client:编辑器能把感兴趣的信号送进波形、跳到某个时间戳、在波形上标注,反过来也能从波形信号跳回源码位置。这把“elaborated 出来的设计层级”和“波形里的信号层级”打通,正是 HRT 这种自研 FPGA/ASIC 团队需要的工作流闭环。配置项 wcpCommand(如 surfer --wcp-initiate {},{} 替换为 WCP 端口)和 buildPattern(把波形文件名映射到对应的 build .f)就是为它服务的。
另外纠正语义机制的一个关键点:slang-server 之所以必须正确配置 top/filelist,是因为它刻意利用了 Slang 的两阶段编译——没有 design 时只能靠 Shallow Compilation(浅编译、不做完整 elaboration)给出快速诊断;一旦配了 design,则每次按键跑 shallow compilation 出快速诊断、每次保存跑完整 design compilation,hover 里的 resolved type、位宽、常量值、macro origin 都来自完整 elaboration 这一层。
VS Code 使用:
1安装 VS Code Marketplace 里的 Hudson River Trading.vscode-slang 扩展。
2扩展会提示自动安装 server binary。
Neovim 0.11+ 配置:
1vim.lsp.config("slang-server", {
2 cmd = { "slang-server" },
3 root_markers = { ".git", ".slang" },
4 filetypes = {
5 "systemverilog",
6 "verilog",
7 },
8})
9
10vim.lsp.enable("slang-server")
采用建议:如果你写 SystemVerilog,这就是非常实用的工具。它也说明 HRT 这类高频交易公司确实会投入硬件开发体验,不只是写 C++ 服务。
19. hudson-trading/heracles-ql:用 Python 写 VictoriaMetrics 告警
GitHub:hudson-trading/heracles-ql
最新 release v0.2.1:2025-05-08(项目仍处早期、低频更新、自述 “under construction”)
PyPI:heracles-ql==0.2.1
许可证:MIT
HeraclesQL 是一个 Python 内嵌 DSL。它的官方定位是 “写告警”——MetricsQL 查询只是它的构件,真正目标是让告警与录制规则可以复用、参数化、类型检查,而不是在 YAML 里复制粘贴字符串。HRT 自己把它类比成“监控告警界的 SQLAlchemy”。
完整架构与实现原理:
graph LR
subgraph "Python DSL"
A["alert/query Python code"]
B["Selector factory"]
C["Duration objects"]
D["handwritten funcs"]
E["generated funcs"]
end
subgraph "表达式模型"
F["typed query nodes"]
G["annotations / assertions"]
H["format/render"]
I["py.typed for type checkers"]
end
subgraph "规则工程化"
J["config.rule"]
K["contexts / reducers"]
L["generation utilities"]
M["unittest generators"]
end
subgraph "外部工具"
N["formatter Go binary"]
O["hermes / delos helpers"]
P["MetricsQL string"]
Q["VictoriaMetrics / vmalert"]
end
A --> B
A --> C
B --> F
C --> F
D --> F
E --> F
F --> G
F --> H
I --> A
F --> J
J --> K
K --> L
L --> M
H --> N
N --> P
H --> P
P --> Q
O --> L
HeraclesQL 的思路是把 MetricsQL 字符串提升成 Python 表达式树。Selector、Duration、handwritten/generated functions 生成 typed query nodes;format/render 再把 AST 输出为 MetricsQL。仓库里还有 config.rule、contexts、reducers、generation、unittest generators,以及 Go 写的 formatter/hermes/delos 辅助工具,说明它想解决的不只是“拼字符串”,而是把查询、告警规则、生成和测试都工程化。
它真正的创新是 meta-alert(元告警):因为每个表达式对象内部都完整保留了 AST,注册在 rules.context(...) 里的 post-processor 能遍历这棵 AST,自动派生出配套告警。最典型的 AlertForMissingData 会找出表达式里所有根级 selector,自动生成对应的 absent(...) 告警——这恰好堵住监控里最隐蔽的一个坑:动态注册的指标一旦缺失,selector 返回空集,普通告警就永远不会触发。AlertForAssertions 则处理 .annotate(...) 这类断言(如校验基数不变)来生成元告警。这两者本质上“只是处理表达式 AST 的 Python 代码”,可自定义——这才是它和 Jinja 那种把查询当文本的字符串模板彻底拉开差距的地方。另外要点明:渲染输出的合法性由独立的 Go formatter 保证,它直接 require VictoriaMetrics 官方的 metricsql 解析器来格式化/校验,所以非 manylinux 平台从 sdist 安装时需要一个现代 Go 编译器。
它为什么好:
- 消灭字符串复制粘贴:告警表达式变成 Python 函数和对象,可以复用、组合、参数化。
- 类型检查提前发现问题:项目提供
py.typed,让 mypy/pyright 这类工具能参与检查。 - 规则生成可测试:同一份 Python 逻辑可以生成多组 alert rule,并用 unittest 验证输出。
- 适合大规模告警库:当服务、region、cluster、label 维度变多,DSL 比 YAML 字符串更可维护。
- formatter 独立:Go formatter 帮它把输出查询整理成稳定格式,减少 review 噪音。
它的短板是成熟度:0.2.1 和 “under construction” 意味着 API 可能变化。适合借鉴架构或小范围试用,不适合没评估就替换核心告警体系。
源码级目录结构和模块分工:
1heracles-ql/
2 heracles/ql/prelude.py MetricsQL AST、向量类型、render 协议
3 heracles/ql/factory.py Selector 工厂和 metric 选择器
4 heracles/ql/duration.py Duration 常量和单位
5 heracles/ql/funcs/generated.py 生成的 MetricsQL 内置函数
6 heracles/ql/funcs/handwritten.py 手写特殊函数和 label 操作
7 heracles/config/ alert rule、context、reducer、YAML 生成
8 heracles/unittest/ hermes 测试模型和时间序列样本生成器
9 formatter/formatter.go MetricsQL formatter
10 hermes/ 与 delos/ Go 辅助工具和 TUI/执行器
11 test/ DSL、配置生成、stdlib、unittest 测试
DSL 核心在 heracles/ql/prelude.py。它定义 InstantVector、RangeVector、BinaryOp、UnaryOp、BuiltinFunc、Duration、Matcher 等表达式节点,并让每个节点实现 render()。factory.py 里的 Selector 让 v.my_metric(label="x") 这类 Python 调用生成 MetricsQL selector;duration.py 和 prelude 里的 Duration 类型把 5 * ql.Minute 变成 [5m] 这种查询片段。
函数分两类:funcs/generated.py 是批量生成的 MetricsQL 函数包装,funcs/handwritten.py 放 label_set、label_move、quantiles 这类更难机械生成的函数。告警工程化在 heracles/config/,rule.py 表达 alert rule,contexts.py 给 rule 批量附加 service/label/context,generation.py 生成 Prometheus/VictoriaMetrics rule YAML,reducers.py 决定多层 context 怎么合并。
测试思路也值得看。heracles/unittest/hermes.py 用 pydantic 描述测试输入、输出 alert 和表达式测试结果,generators.py 能生成 square wave、sample 这类合成时间序列。也就是说,它不是只把字符串拼出来,而是试图把“查询 DSL、规则生成、告警测试”放在一套可类型检查、可单测的 Python 工程里。
安装:
1pip install heracles-ql
最小示例:
1from heracles import ql
2
3v = ql.Selector()
4
5my_query = ql.rate(v.my_interesting_metric(useful="label")[5 * ql.Minute])
6
7print(ql.format(my_query.render()))
8# rate(my_interesting_metric{useful="label"}[5m])
采用建议:有意思,但仍要谨慎。README 自己写着 repo still under construction,PyPI 版本也是 0.2.1。适合观察“如何把告警规则工程化”,也适合内部小范围试用,但不要一上来就把核心生产告警全部迁进去。
九、Optiver:时间精度和数据库链路
Optiver 这两个项目都围绕数据库链路,一个是 Postgres 纳秒时间戳类型,一个是 asyncpg fork。它们的共同点是:场景很窄,但能看出做市商对时间精度和写入链路的关注。
20. optiver/timestamp9:Postgres 纳秒级时间戳类型
GitHub:optiver/timestamp9
默认分支最后提交:2023-10-05
许可证:MIT
Postgres 原生 timestamp 精度通常到微秒。timestamp9 提供纳秒级时间戳类型,内部用 64-bit 整数存 UNIX epoch 以来的纳秒数。
完整架构与实现原理:
graph LR
subgraph "源码和安装边界"
S1["timestamp9.control
扩展元信息"]
S2["timestamp9.sql
SQL 类型、函数、opclass"]
S3["timestamp9.c
C 实现"]
S4["CMakeLists.txt / pg_config
绑定目标 Postgres"]
S5["regression tests
输入、比较、索引行为"]
end
subgraph "安装到 Postgres"
A1["编译 shared library"]
A2["复制 control/sql/so 到 extension 目录"]
A3["CREATE EXTENSION timestamp9"]
A4["目标数据库版本和 ABI 必须匹配"]
end
subgraph "类型输入输出"
B1["text input"]
B2["bigint nanoseconds input"]
B3["timestamp/timestamptz casts"]
B4["timestamp9_in / timestamp9_out"]
B5["int64 nanoseconds since Unix epoch"]
B6["range / overflow checks"]
end
subgraph "SQL 运算和索引"
C1["comparison operators"]
C2["btree opclass"]
C3["hash opclass"]
C4["interval arithmetic"]
C5["extract / date_part"]
C6["planner 可把它当原生类型排序和 join"]
end
subgraph "输出和采用边界"
D1["nanosecond-preserving result"]
D2["index scan / sort / join"]
D3["收益:避免微秒 timestamp 截断"]
D4["限制:构建强依赖 pg_config 和目标 Postgres"]
end
S1 --> A3
S2 --> A3
S3 --> A1
S4 --> A1
S5 --> C6
A1 --> A2 --> A3
A4 --> A1
A3 --> B4
B1 --> B4
B2 --> B4
B3 --> B4
B4 --> B5
B5 --> B6
B5 --> C1
B5 --> C2
B5 --> C3
B5 --> C4
B5 --> C5
C1 --> C6
C2 --> C6
C3 --> C6
C6 --> D2 --> D1
D1 --> D3
D1 --> D4
timestamp9 的实现方式是 Postgres C 扩展。timestamp9.control 和 SQL 文件把新类型、cast、operator、opclass 注册到数据库;timestamp9.c/h 实现输入输出、文本解析、timestamp/timestamptz 转换、比较、interval 运算和边界检查。内部表示是 signed 64-bit Unix epoch nanoseconds,所以 SQL 层看到的是一个原生类型,而不是每次都手写 bigint 转换。
它为什么好:
- 纳秒精度进入类型系统:列类型本身就是
timestamp9,不是“约定这个 bigint 是纳秒”。 - 索引和比较自然:btree/hash operator class 让排序、范围查询、join 能走数据库常规优化路径。
- SQL 可读性更好:cast、interval 运算、展示格式都接近 timestamp,而不是到处写转换函数。
- 减少应用层错误:单位换算、时区解释、字符串格式在扩展层集中处理。
- 适合 tick / event 数据:交易和行情链路里纳秒字段很多,类型化可以让 schema 更诚实。
它的核心限制也来自 int64 nanoseconds:大致范围落在 1677 到 2262 年之间;另外 C 扩展需要每个 Postgres 环境安装和维护,升级数据库时要重新验证。
源码级目录结构和模块分工:
1timestamp9/
2 src/timestamp9.h timestamp9 = long long 类型定义
3 src/timestamp9.c C 扩展函数实现
4 src/timestamp9.sql CREATE TYPE、函数、cast、operator、opclass
5 src/timestamp9--*.sql 版本升级迁移脚本
6 timestamp9.control Postgres extension 元信息
7 CMakeLists.txt 与 build.sh 构建和安装脚本
8 tests/sql/basics.sql pg_regress SQL 测试
9 tests/expected/basics.out 期望输出
timestamp9.h 只有一个关键定义:typedef long long timestamp9;。这意味着扩展内部把时间戳当成 int64 纳秒数,而不是复用 Postgres 的微秒 timestamp 表示。timestamp9.c 里的 PG_FUNCTION_INFO_V1 列出了所有暴露给 SQL 的 C 函数:timestamp9_in/out/recv/send 管输入输出和二进制传输,timestamp9_eq/ne/lt/le/gt/ge 管比较,bt_timestamp9_cmp 管 btree 排序,timestamp9_to_timestamptz、timestamptz_to_timestamp9 等函数管 cast。
SQL 层在 src/timestamp9.sql。这里通过 CREATE TYPE timestamp9 注册新类型,再声明 cast、比较 operator、btree/hash operator class、min/max aggregate、与 interval 的 +/- 运算。升级脚本 timestamp9--0.1.0--0.2.0.sql 到 timestamp9--1.3.0--1.4.0.sql 表示这个扩展的 SQL surface 是逐步演进的,生产安装时要按 Postgres extension 版本管理走。
测试是标准 PostgreSQL extension 思路:tests/sql/basics.sql 发 SQL,tests/expected/basics.out 校验输出。源码层面最值得关注的是两个边界:字符串解析/格式化是否保留 9 位小数,int64 纳秒和 Postgres TimestampTz 微秒之间转换是否正确处理溢出和舍入。
构建安装:
1git clone https://github.com/optiver/timestamp9.git
2cd timestamp9
3mkdir build
4cd build
5cmake ..
6# 或者指定 pg_config
7cmake .. -DPG_CONFIG=/path/to/pg_config
8make
9sudo make install
Postgres 用法:
1select '2019-09-19 08:30:05.123456789 +0200'::timestamp9;
2
3select '2019-09-19'::timestamp9 < '2019-09-20'::timestamp9;
4
5select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1d';
采用建议:窄场景可用,但要自己承担维护成本。如果你确实要在 Postgres 里保留纳秒时间戳,并且能接受安装 C 扩展,它值得看。更常见的方案是把纳秒时间存在 bigint 字段里,展示时再转换。这个项目适合对 SQL 类型、索引和时间表达有明确需求的团队。
21. optiver/optiver-asyncpg:asyncpg 的生产 fork
GitHub:optiver/optiver-asyncpg
默认分支最后提交:2022-12-02
GitHub fork 来源:MagicStack/asyncpg
许可证:Apache-2.0
这是 asyncpg 的 fork。README 基本还是官方 asyncpg 的说明:它是 Python asyncio 生态里高性能 PostgreSQL 客户端。
完整架构与实现原理:
graph LR
subgraph "源码和构建边界"
S1["asyncpg/connection.py
Connection API"]
S2["asyncpg/pool.py
Pool 管理"]
S3["asyncpg/protocol/*.pyx
Cython 协议层"]
S4["asyncpg/pgproto
Postgres 类型和消息编码"]
S5["setup.py / Cython
构建生成 C 扩展"]
S6["tests
协议、事务、pool 行为"]
end
subgraph "Python asyncio API"
A1["asyncpg.connect"]
A2["Connection"]
A3["Pool"]
A4["Transaction"]
A5["Cursor"]
A6["PreparedStatement"]
end
subgraph "协议和编解码"
B1["protocol.pyx"]
B2["coreproto.pyx"]
B3["prepared_stmt.pyx"]
B4["recordobj.c"]
B5["type introspection"]
B6["binary codecs
array / range / record"]
B7["statement cache"]
end
subgraph "PostgreSQL extended protocol"
C1["Startup / auth / SSL / SCRAM"]
C2["Parse"]
C3["Bind"]
C4["Execute"]
C5["COPY / LISTEN / NOTIFY"]
C6["rows / records"]
end
subgraph "fork 采用边界"
D1["收益:能学习金融机构如何 fork 高性能客户端"]
D2["风险:fork 和上游 asyncpg 语义可能分叉"]
D3["构建卡点:pgproto submodule / setuptools / Cython"]
D4["验证必须连真实 Postgres"]
end
S1 --> A2
S2 --> A3
S3 --> B1
S4 --> B6
S5 --> B1
S6 --> D4
A1 --> A2
A3 --> A2
A2 --> A4
A2 --> A5
A2 --> A6
A2 --> B1
B1 --> B2
B1 --> B3
B1 --> B4
B2 --> C1
B3 --> B7
B5 --> B6
B6 --> C2
C1 --> C2 --> C3 --> C4 --> C6
C5 --> B1
C6 --> B4 --> A2
A2 --> D1
S5 --> D3
A2 --> D2
C6 --> D4
asyncpg 的核心是绕开传统 DB-API,直接实现 PostgreSQL extended query/binary protocol。Python 层提供 Connection、Pool、Transaction、Cursor、PreparedStatement;Cython 层的 protocol.pyx、coreproto.pyx、prepared_stmt.pyx 处理 Parse/Bind/Execute、prepared statement、认证、网络协议;codecs 负责把 Postgres 类型以二进制形式映射成 Python 对象;recordobj.c 提供高效 row/record 表示。
它为什么好:
- asyncio 原生:不是在线程池里包同步驱动,连接、查询、游标、池都按 async 模型设计。
- binary protocol 减少开销:类型编解码更直接,很多场景比文本协议和 DB-API 包装更快。
- prepared statement 是核心路径:高频重复查询和写入能利用 statement cache 和 extended protocol。
- 类型系统覆盖深:array、range、record、自定义 codec 都是性能和正确性关键。
- 连接池是内建能力:服务端应用不需要额外拼一套池管理。
Optiver fork 的价值在于说明金融机构可能会 fork 上游以满足内部生产需求,但这个仓库最后提交停在 2022 年。真要依赖,必须先和 MagicStack/asyncpg 上游做 diff,确认 fork 的改动仍然值得承担维护成本。
源码级目录结构和模块分工:
1optiver-asyncpg/
2 asyncpg/connection.py Connection API、query、execute、prepared statement
3 asyncpg/pool.py Pool、holder、proxy、acquire/release 生命周期
4 asyncpg/transaction.py transaction context manager
5 asyncpg/cursor.py cursor 和 async iterator
6 asyncpg/prepared_stmt.py Python prepared statement wrapper
7 asyncpg/connect_utils.py DSN、SSL、pgpass、连接重试、TCP_NODELAY
8 asyncpg/protocol/*.pyx Cython PostgreSQL wire protocol
9 asyncpg/protocol/codecs/ 二进制类型 codecs
10 asyncpg/protocol/record/ 高性能 Record C 对象
11 tools/ exception/type map 代码生成
12 tests/ 连接、池、事务、类型、COPY、取消等测试
Python API 主线从 asyncpg/__init__.py 导出 connect 和 create_pool,落到 connection.py 与 pool.py。Connection 管单连接的 query、execute、fetch、prepared statement、listener 和 COPY;Pool 管连接 holder、proxy、acquire/release、过期和重连。transaction.py、cursor.py、prepared_stmt.py 分别把事务、游标、prepared statement 包成 Python 对象。
性能核心在 Cython。asyncpg/protocol/protocol.pyx 连接 Python 层和协议状态机,coreproto.pyx 实现 PostgreSQL frontend/backend protocol,prepared_stmt.pyx 处理 prepared statement,settings.pyx 管运行参数,scram.pyx 管认证,encodings.pyx 管字符编码。protocol/codecs/ 负责 int、text、array、range、json、numeric 等类型的二进制编解码,protocol/record/recordobj.c 则让查询结果 row 不必退化成慢速 dict。
这个 fork 是否值得用,源码层面要做两件事:一是读 git diff 对比 MagicStack 上游,确认 Optiver 改了哪些协议、类型或连接行为;二是跑 tests/test_connect.py、test_pool.py、test_transaction.py、test_codecs.py 这类高风险测试。因为数据库客户端的风险不在“能不能连上”,而在取消、超时、事务状态、statement cache 和类型 codec 的边界行为。
官方 asyncpg 的常规用法:
1pip install asyncpg
1import asyncio
2import asyncpg
3
4async def run():
5 conn = await asyncpg.connect(
6 user="user",
7 password="password",
8 database="database",
9 host="127.0.0.1",
10 )
11 values = await conn.fetch("SELECT * FROM mytable WHERE id = $1", 10)
12 await conn.close()
13 return values
14
15asyncio.run(run())
采用建议:不要把这个 fork 当成新项目默认依赖。它最后提交在 2022 年,且是官方 asyncpg 的 fork。真正要用异步 Postgres 客户端,优先评估 MagicStack/asyncpg 官方版本。这个 fork 的价值在于观察金融机构可能会如何维护自己的数据库客户端改动,而不是替代上游。
十、WorldQuant Alpha101:适合学习,不适合直接生产
22. yli188/WorldQuant_alpha101_code:101 Formulaic Alphas 的社区实现
GitHub:yli188/WorldQuant_alpha101_code
默认分支最后提交:2019-03-07
许可证:仓库未声明
这个项目不是 WorldQuant 官方仓库,而是社区对 101 Formulaic Alphas 的 Python 实现。它的价值是学习 alpha 公式怎么表达:横截面 rank、时间序列 rolling、价格量能关系、相关性、delay、delta、ts_rank 等。
完整架构与实现原理:
graph TB
subgraph "现有社区代码"
A["raw DataFrame with expected columns"]
B["field adapter by column names"]
C["helper primitives"]
D["Alphas class"]
E["alpha001 ... alpha101 methods"]
F["factor Series/DataFrame output"]
end
subgraph "公式算子"
G["rank / scale / signedpower"]
H["delay / delta / ts_rank"]
I["rolling corr / cov / sum / std"]
J["decay / product / argmax / argmin"]
end
subgraph "生产因子库应补齐但缺失"
K["data validation"]
L["calendar and universe alignment"]
M["corporate actions / adjusted prices"]
N["suspension / limit-up-down handling"]
O["industry and size neutralization"]
P["unit tests per alpha"]
Q["backtest and IC validation"]
end
A --> B
B --> C
C --> G
C --> H
C --> I
C --> J
G --> D
H --> D
I --> D
J --> D
D --> E
E --> F
F --> K
F --> L
F --> M
F --> N
F --> O
F --> P
F --> Q
Alpha101 的实现原理是把论文公式翻译成 pandas 上的横截面和时间序列算子:rank、scale、delay、delta、rolling correlation、covariance、ts_rank、decay 等。这个仓库把这些 helper primitive 包进 Alphas 类,再提供 alpha001 到 alpha101 的方法。它能帮助你理解公式语言,但它缺少真正生产因子库最关键的一圈:数据校验、交易日历、股票池对齐、复权、停牌涨跌停处理、中性化、单因子测试和回测验证。
它为什么仍然有价值:
- 公式翻译直观:可以快速看到 WorldQuant Alpha101 这类公式怎么落到 pandas 运算。
- 学习算子组合:rank、rolling、delay、correlation 的组合方式比单个因子结果更值得读。
- 适合做重实现参考:你可以按自己的数据模型重新实现,并把这里当对照。
为什么不能直接生产:
- 数据模型过于隐式:字段名、频率、复权口径、收益定义都靠约定,没有 schema。
- 缺少质量控制:没有测试、没有空值策略、没有极端值处理、没有交易状态处理。
- 代码本身不完整:一个文件已经语法错误,说明维护质量不足。
- 没有研究闭环:因子输出之后还缺 neutralization、IC、分组收益、换手、成本和回测。
仓库只有三个主要文件:
1101Alpha_code_1.py
2101Alpha_code_2.py
3101 Formulaic Alphas.pdf
源码级目录结构和模块分工:
1WorldQuant_alpha101_code/
2 101 Formulaic Alphas.pdf 公式原文或参考 PDF
3 101Alpha_code_1.py helper primitive、get_alpha、Alphas 类
4 101Alpha_code_2.py 另一套实现:Quantopian/Zipline Pipeline 的 CustomFactor 版本(与 code_1 框架/数据模型完全不同),当前有语法错误
101Alpha_code_1.py 的前半部分是 primitive 算子:ts_sum、sma、stddev、correlation、covariance、ts_rank、product、ts_min、ts_max、delta、delay、rank、scale、ts_argmax、ts_argmin、decay_linear。这些函数基本都是 pandas rolling、shift、rank 和 numpy 组合。源码里的 rank(df) 用 df.rank(pct=True),这里要特别小心:Alpha101 公式通常需要按日期做横截面 rank,如果输入 DataFrame 行列方向不符合预期,结果会完全变味。
get_alpha(df) 负责从原始字段里抽出 S_DQ_OPEN、S_DQ_HIGH、S_DQ_LOW、S_DQ_CLOSE、S_DQ_VOLUME、S_DQ_PCTCHANGE、S_DQ_AMOUNT 等列,再构造 Alphas 对象。class Alphas 里每个 alphaXXX 方法就是一条公式翻译。这个结构适合学习,因为公式和实现放得很近;但它不适合生产,因为没有 schema 校验、没有交易日历对齐、没有复权口径、没有行业/市值中性化,也没有每条 alpha 的 golden test。
源码里也能直接看到实现并不是完整 101 条:alpha001 到 alpha101 中间缺少不少编号,例如 alpha048、alpha056、alpha058、alpha059、alpha063、alpha067、alpha069、alpha070、alpha076、alpha079、alpha080、alpha082、alpha087、alpha089、alpha090、alpha091、alpha093、alpha097、alpha100 等没有对应方法。部分公式直接返回 None 或注释掉实现,这说明它更像“社区翻译草稿”,不是严肃的完整因子库。真正生产化时,至少要为每个 alpha 建立输入样例、期望输出、NaN 策略、横截面方向、窗口边界和字段含义校验。
源码还有明显旧 pandas 痕迹,例如 decay_linear 里使用 df.as_matrix()。在现代 pandas 里应该改成 df.to_numpy(),并且要重新验证 shape、NaN 填充和滚动窗口结果。
这里要澄清一个常见误读:101Alpha_code_1.py 和 101Alpha_code_2.py 不是“同一套实现的两个版本”,而是两套互不相关、互不导入的框架。code_1 是上面讲的纯 pandas + S_DQ_ 字段实现;code_2 则是 Quantopian/Zipline Pipeline 的 CustomFactor 实现——它用 USEquityPricing、Returns、AverageDollarVolume、VWAP 等 Quantopian 内建数据对象,依赖 Quantopian Research 环境,和 code_1 的数据模型完全不同。code_2 的报错根因很具体:第 6 行 def make_factors() 前多了一个前导空格,触发 IndentationError。无论如何,一个文件直接语法错误,说明这个仓库不能按“可直接导入的因子库”对待。
基础语法检查结果也能说明它的维护质量:
1python3 -m py_compile 101Alpha_code_1.py
2# 通过
3
4python3 -m py_compile 101Alpha_code_2.py
5# IndentationError: unexpected indent (101Alpha_code_2.py, line 6)
101Alpha_code_1.py 的使用方式大概是把行情数据整理成它预期的字段名:
1from importlib.machinery import SourceFileLoader
2
3alpha_mod = SourceFileLoader(
4 "alpha101",
5 "101Alpha_code_1.py",
6).load_module()
7
8stock = alpha_mod.Alphas(df_data)
9factor = stock.alpha001()
它预期的字段包括:
1S_DQ_OPEN
2S_DQ_HIGH
3S_DQ_LOW
4S_DQ_CLOSE
5S_DQ_VOLUME
6S_DQ_PCTCHANGE
7S_DQ_AMOUNT
采用建议:适合学习,不适合直接当生产因子库。原因很明确:
- 没有 README
- 没有依赖声明
- 没有测试
- 一个 Python 文件直接语法错误
- 部分代码依赖旧 pandas 写法和特定字段名
- 没有因子对齐、停牌、复权、幸存者偏差、行业中性、市值中性等生产研究必须处理的问题
如果你要学习 Alpha101,可以读它。如果你要做生产因子库,应该把公式重新实现到自己的数据模型里,并为每个因子写单元测试、空值处理、截面处理和回测验证。
十一、按使用场景重新整理
如果你是量化研究员,优先看:
| 场景 | 推荐项目 |
|---|---|
| DataFrame 探索 | D-Tale |
| 时间序列数据存储 | ArcticDB |
| notebook 研究效率 | pyflyby、nbstripout-fast |
| alpha 公式学习 | WorldQuant Alpha101 |
| notebook 报告生产化 | Notebooker |
如果你是金融数据工程师,优先看:
| 场景 | 推荐项目 |
|---|---|
| 大规模时间序列存储 | ArcticDB |
| HDF5 文件版本化 | versioned-hdf5 |
| 批处理调度设计参考 | Cook |
| Spark 时间序列 join 设计参考 | Flint |
| Java 服务给 Python 调用 | PJRMI |
如果你做低延迟交易系统,优先看:
| 场景 | 推荐项目 |
|---|---|
| 极细粒度性能追踪 | magic-trace |
| C++20 协程结构化并发 | Corral |
| Postgres 纳秒时间戳 | timestamp9 |
| 数据库客户端 fork 思路 | optiver-asyncpg |
如果你关注语言和硬件工程,优先看:
| 场景 | 推荐项目 |
|---|---|
| OCaml 标准库 | Jane Street Core |
| OCaml 并发 | Jane Street Async |
| OCaml 硬件设计 | Hardcaml |
| SystemVerilog LSP | slang-server |
十二、真实试用:哪些能跑,哪些会卡住?
源码结构看清楚之后,还需要回答一个更现实的问题:这些工具到底是不是能被普通工程环境直接用起来?
这里要避免一个误区:pip install 成功不等于项目可用,cmake 能配置也不等于能进生产。真正有参考价值的试用,至少要看四件事:
- 能不能完成一个最小闭环
- 依赖是否会污染现有研究环境
- 失败点出现在 Python 层、JVM 层、C++ 构建层,还是外部基础设施层
- 如果要继续引入,下一步需要补齐什么
这一轮真实试用的环境可以理解为一台普通 macOS arm64 开发机,Python 版本为 3.11。这个环境不适合完整验证 Intel Processor Trace、Mesos、Kubernetes、Postgres 扩展安装、OCaml 工业项目和 Spark 集群,但足够暴露很多项目的开箱可用性问题。
需要先把这一节的边界说清楚:它是在单台机器、某个时间点做的一轮开箱冒烟测试,不是锁定版本、可逐条复现的严格基准。下面的「跑通 / 能安装 / 会卡住」反映的是当时那台机器上的开箱体验;换操作系统、换依赖版本或换时间点,结论都可能不同。如果你要据此做采用决策,请以各项目当下的 README、你自己环境里跑出来的实际报错为准,把这里的判断当作起点而不是结论。
整体试用路径可以抽象成下面这张图:
flowchart TD
subgraph "第一层:静态判断"
A["拿到开源项目"]
B["README / license / release / archive 状态"]
C["源码目录
入口、核心包、测试、构建文件"]
D["依赖图
Python / JVM / C++ / Spark / Postgres / OS"]
end
subgraph "第二层:最小运行"
E["创建隔离环境"]
F["安装或构建"]
G{"能否 import / 启动 / 编译?"}
H["准备最小输入
DataFrame / notebook / SQL / test case"]
I{"能否得到明确输出?"}
end
subgraph "第三层:失败归因"
J["Python 包问题
缺依赖 / 入口脚本 / 旧 setuptools"]
K["JVM 或 Spark 问题
Java 版本 / classpath / jar"]
L["C/C++ 构建问题
compiler / cmake / headers"]
M["外部基础设施问题
MongoDB / Postgres / Linux perf / submodule"]
end
subgraph "第四层:采用结论"
N["可直接试用
低依赖、闭环清楚"]
O["需要 PoC
价值明确但环境复杂"]
P["只适合架构参考
旧生态、已归档或场景太窄"]
Q["进入真实业务验证
数据量、并发、权限、授权、备份"]
end
A --> B --> C --> D --> E --> F --> G
G -->|可以| H --> I
G -->|不可以| J
G -->|JVM/Spark| K
G -->|C/C++| L
G -->|外部服务| M
I -->|可以| N --> Q
I -->|部分可以| O --> Q
I -->|不可以| P
J --> O
K --> O
L --> O
M --> O
12.1 最小闭环已经跑通的项目
这些项目不是说一定适合生产,而是已经能在普通开发环境里完成一个清晰的最小用法。
| 项目 | 最小用法 | 试用结果 | 真实问题 |
|---|---|---|---|
| D-Tale | 用浏览器打开 pandas DataFrame | 可以启动本地 Web 页面,并返回 HTTP 200 | 如果用阻塞模式启动,脚本会卡住;自动化脚本里要避免把服务直接跑在前台 |
| ArcticDB | 用 LMDB 后端写入和读取 DataFrame | 可以创建 library、写入 symbol、读回数据和 metadata | 生产使用前要确认存储后端、并发写入模式和 BSL 授权 |
| versioned-hdf5 | 在同一个 HDF5 文件里写入 v1、v2 两个版本 | 可以读回旧版本和当前版本 | HDF5 文件锁、并发写、清理警告和文件膨胀需要额外压测 |
| nbstripout-fast | 清理 notebook 的 execution_count 和 outputs | 可以把输出清干净 | 要配合 Git filter 使用,否则只能手动运行 |
| pyflyby | 整理 import、删除无用 import | 可以删除无用 import 并保留实际使用的 import | 命令行入口可能绑定到错误 Python,需要确认虚拟环境激活方式 |
| heracles-ql | 用 Python DSL 生成 VictoriaMetrics 查询 | 可以生成 rate(metric{label="value"}[5m]) 这类查询 |
它只解决查询表达式生成,不负责指标采集、存储和告警执行 |
| Marbles | 运行 unittest 风格断言并输出更丰富失败信息 | 可以显示失败表达式、局部变量和 note | 直接用普通 Python 方式调用不一定合适,更适合通过 python -m marbles 跑测试文件 |
| PyBloqs | 用 Python 生成 HTML 报告 | 可以生成包含文本和表格的静态 HTML | HTML 很顺,PDF/PNG 通常还需要额外渲染依赖 |
| PJRMI | Python 启动子 JVM 并调用 Java 对象 | 在 Java 版本正确时可以调用 java.util.ArrayList |
依赖 psutil,并且 JAVA_HOME 必须匹配 jar 的 class version |
| Corral | 编译一个 C++20 coroutine 返回 Task<int> 的最小程序 |
header-only 核心可以被 clang++ 编译 | 更完整的示例会牵涉 Asio、Qt、Catch2 等额外依赖 |
以 D-Tale 为例,真实使用的入口非常轻:
1import pandas as pd
2import dtale
3
4df = pd.DataFrame({
5 "symbol": ["BTCUSDT", "ETHUSDT"],
6 "price": [64000.0, 3200.0],
7})
8
9dtale.show(df, open_browser=False, host="127.0.0.1")
它适合放在研究阶段:DataFrame 已经在内存里,研究员不想写一堆临时可视化代码,就可以把数据丢给 D-Tale 做排序、过滤、图表和异常值观察。它解决的是“看数据太慢”的问题,不是“存数据”或“调度任务”的问题。
ArcticDB 的最小闭环也很清楚:
1import arcticdb as adb
2
3ac = adb.Arctic("lmdb://research-store")
4lib = ac.create_library("research")
5
6lib.write("BTCUSDT.bars", df, metadata={"source": "daily-research"})
7item = lib.read("BTCUSDT.bars")
它的输入是 pandas DataFrame,输出仍然是 DataFrame,但中间多了 symbol、library、version 和 metadata 的组织方式。这个设计适合行情、因子、回测结果和研究中间表,不适合替代所有数据库。
versioned-hdf5 的价值则更偏“文件可复现”:
1from versioned_hdf5 import VersionedHDF5File
2
3with VersionedHDF5File(h5file) as vf:
4 with vf.stage_version("v1") as group:
5 group.create_dataset("close", data=[1.0, 2.0, 3.0])
6
7 with vf.stage_version("v2") as group:
8 group["close"][1] = 2.5
这类工具最适合研究数据的“版本回看”:同一个数据文件里既能保留旧版本,又能读当前版本。它带来的效果不是更快,而是减少“这个结果到底用的是哪份数据”这种沟通成本。
12.2 能安装或导入,但不能算开箱即用的项目
这一组项目有明确价值,但直接引入时会遇到版本、运行时或外部服务问题。它们需要先做环境隔离和 PoC,不能直接塞进现有研究环境。
| 项目 | 试用状态 | 卡点 | 真实采用建议 |
|---|---|---|---|
| Notebooker | CLI 可以启动帮助信息,依赖可以安装 | 依赖 Jupyter、Papermill、MongoDB、模板目录和 notebook kernel | 适合作为报表服务设计参考;真正采用前先搭一个隔离环境 |
| BeakerX | 降级 setuptools 后可以 import | 依赖旧 pkg_resources,还会拉入 Jupyter 和 PySpark 相关依赖 |
更适合研究多语言 notebook 架构,不建议新项目直接押注 |
| ts-flint / Flint Python 绑定 | Python 包可以安装,补 pyarrow 后可以 import | 本地 Spark 启动后仍缺 Flint JVM jar,出现 JavaPackage 不可调用 |
不能只装 Python 包,必须同时处理 Spark、Scala 和 Flint jar 版本 |
| timestamp9 | CMake 可以配置到 Postgres 开发环境 | 链接阶段受 pg_config 和 Postgres 安装路径影响 |
需要在目标 Postgres 版本上重新构建和安装,不能脱离数据库环境评估 |
| slang-server | CMake 入口存在 | 子模块没有初始化时,ctre、reflect-cpp、slang 都缺失 | 必须完整 clone submodule,再看 LLVM、slang 和 LSP 构建链 |
| optiver-asyncpg | 普通 PyPI asyncpg 可用,但 Optiver fork 不能等同看待 | fork 的 pgproto 子模块缺失会导致构建失败,旧构建链还依赖 setuptools/Cython 组合 |
需要完整 submodule、固定构建环境,并连接 Postgres 做协议层测试 |
Notebooker 是这里最容易被误判的项目。表面上它只是“把 notebook 跑成报告”,实际依赖的是一套服务化链路:
flowchart TB
subgraph "报告资产"
A1["notebook 模板"]
A2["参数定义"]
A3["kernel name"]
A4["依赖包和数据访问权限"]
end
subgraph "触发和调度"
B1["notebooker-cli"]
B2["scheduler"]
B3["webapp run button"]
B4["cleanup / snapshot latest successful"]
end
subgraph "执行链路"
C1["template discovery"]
C2["parameter injection"]
C3["Papermill execute"]
C4["Jupyter kernel process"]
C5["executed notebook"]
C6["nbconvert HTML"]
end
subgraph "状态和展示"
D1["MongoDB"]
D2["run status"]
D3["error / traceback"]
D4["rendered report"]
D5["latest successful report"]
D6["Notebooker Webapp"]
end
subgraph "真实卡点"
E1["没有 MongoDB 就不是完整服务"]
E2["kernel 依赖不一致会导致报告失败"]
E3["执行 notebook 需要权限隔离"]
E4["长任务需要 timeout 和重试策略"]
end
A1 --> C1
A2 --> C2
A3 --> C4
A4 --> C4
B1 --> C1
B2 --> C1
B3 --> C1
B4 --> D5
C1 --> C2 --> C3 --> C4 --> C5 --> C6
C3 --> D2
C3 --> D3
C6 --> D4
D2 --> D1
D3 --> D1
D4 --> D1
D1 --> D5
D5 --> D6
D6 --> E1
C4 --> E2
C4 --> E3
C3 --> E4
所以它的输入不是单个 notebook 文件这么简单,还包括参数、模板目录、执行 kernel、数据库连接和输出目录。它解决的是“定期生成研究报告并展示最新结果”的问题,不是“让 notebook 更好写”的问题。
BeakerX 的问题也类似:它的技术想法很好,多语言 notebook、交互式表格、JVM 语言支持都很有价值。但从现在的生态看,它已经明显依赖旧 Jupyter 时代的包结构。新项目如果只是想要交互表格,D-Tale、Jupyter widgets、Plotly Dash 或 Streamlit 往往更现实。
Flint 和 ts-flint 的试用最能说明“Python 包可安装”和“系统可运行”之间的差距。时间序列 join 的核心在 Spark/JVM 侧,Python 只是入口。如果 Spark classpath 里没有匹配的 Flint jar,Python 层调用到 JVM 时就会失败。也就是说,Flint 的真实输入是 Spark DataFrame 和时间序列 join 语义,输出也是 Spark DataFrame;它真正解决的是大规模、分布式、按时间对齐的数据 join,而不是 pandas 级别的数据处理。
12.3 需要特定基础设施,不能在普通开发机完整验证的项目
这类项目不是“没用”,而是它们本来就不属于普通 Python 项目的使用方式。正确评估它们,需要还原它们所在的系统环境。
| 项目 | 为什么普通环境不适合验证 | 真正需要的环境 | 价值判断 |
|---|---|---|---|
| magic-trace | macOS arm64 没有 Linux perf 和 Intel Processor Trace | Linux、Intel CPU、perf、OCaml/dune/opam | 低延迟服务非常有价值,普通研究环境用不上 |
| Jane Street Core | 不是 Python 包,也不是单独脚本 | OCaml opam switch、dune 项目 | 适合 OCaml 工程,不适合跨语言硬塞 |
| Jane Street Async | 需要 OCaml 异步运行时和 dune 工程 | OCaml + Async 生态 | 适合学习协作式并发模型 |
| Hardcaml | 需要 OCaml 硬件设计和仿真链路 | OCaml、dune、硬件设计工作流 | 适合 FPGA/硬件团队,不适合普通量化研究 |
| Cook | 已归档,且是分布式调度系统 | Clojure、Mesos 或 Kubernetes、数据库、集群资源 | 更适合研究调度器设计,不适合新系统直接采用 |
| WorldQuant Alpha101 | 社区代码不完整,一个文件存在语法问题 | 需要重新整理依赖、字段、数据模型和测试 | 适合学习公式,不适合作为生产因子库 |
magic-trace 的定位尤其要说清楚。它不是 profiling 的“更漂亮界面”,而是为了回答低延迟系统里最难的问题:一次请求或一次交易路径中,CPU 到底在哪些函数之间跳转,哪些分支和调用造成了尾部延迟。它的输入是正在运行的进程和 Intel PT 追踪数据,输出是高精度的执行时间线。因此它能带来的效果是定位微秒级甚至更细粒度的延迟来源,而不是提升 notebook 或数据处理效率。
WorldQuant Alpha101 则是另一个极端。它看起来离量化研究最近,但真实可用性反而最弱。因为生产因子库最难的不是把公式写出来,而是数据字段定义、复权、停牌、截面标准化、行业/市值中性、缺失值处理、延迟对齐和回测口径一致。社区代码可以帮助理解 alpha 公式表达,但不能直接承担生产研究任务。
12.4 真实采用顺序
按照试用结果,采用顺序应该更保守:
第一类,马上可以进入日常研究工作流:
1pip install dtale pyflyby nbstripout-fast versioned-hdf5
再根据数据规模补 ArcticDB:
1pip install arcticdb
第二类,需要单独开 PoC 环境:
- Notebooker:先验证定时报表、模板、MongoDB 和权限隔离
- PJRMI:先固定 Java 版本,再验证 Python 到 JVM 的调用链
- Corral:先确认 C++20 编译器和团队是否接受 coroutine 风格
- timestamp9:先在目标 Postgres 版本上构建和安装
第三类,只在特定系统里评估:
- magic-trace:低延迟 Linux + Intel CPU 服务
- Flint:Spark 集群和 JVM 依赖完整时再评估
- slang-server:SystemVerilog 工程和完整 submodule 环境
- Core / Async / Hardcaml:OCaml 工程里评估
第四类,先当架构或学习材料:
- BeakerX
- Cook
- optiver-asyncpg
- WorldQuant Alpha101
这个结论比单纯看 README 更重要。真正能带来效果的工具,不一定是最有名的;最适合先用的,往往是依赖轻、输入输出清楚、失败边界明确的工具。
十三、真正采用时怎么选?
真正采用时,不应该把这 22 个项目全部装进同一个环境。更合理的方式,是先把项目按“使用场景”和“引入风险”拆开。
如果目标是提升个人研究效率,优先选依赖轻、输入输出清楚的工具:
1pip install dtale pyflyby nbstripout-fast versioned-hdf5
这一组工具的共同点是:不需要改数据平台,不需要引入集群,也不要求团队切换语言。D-Tale 负责把 DataFrame 快速看清楚,pyflyby 负责减少 notebook import 混乱,nbstripout-fast 负责让 notebook 进入 Git 时更干净,versioned-hdf5 负责让 HDF5 文件有历史版本。
如果团队已经有 notebook 仓库,可以先给 Git 配置输出清理:
1echo "*.ipynb filter=jupyter" >> .gitattributes
2git config filter.jupyter.clean nbstripout-fast
3git config filter.jupyter.smudge cat
如果目标是搭研究数据平台,再引入 ArcticDB:
1pip install arcticdb
ArcticDB 的收益来自 symbol、library、version 和 DataFrame 读写模型。它适合行情、因子、回测结果和研究中间表,但在公司使用前必须确认 BSL 授权、存储后端、并发写入和备份恢复。
如果目标是把研究报告服务化,再看 Notebooker 和 PyBloqs。PyBloqs 更轻,适合生成静态报告;Notebooker 更像一个小型报表平台,适合定时执行 notebook、记录结果并展示最新报告。前者可以较快使用,后者需要 MongoDB、Jupyter kernel、Papermill 和权限隔离。
如果目标是跨语言或系统工程,再单独评估这些项目:
| 场景 | 项目 | 判断 |
|---|---|---|
| Python 调 Java 服务 | PJRMI | 可用,但要固定 Java 版本和 JVM 依赖 |
| C++20 结构化并发 | Corral | 核心可编译,适合 C++ 团队进一步 PoC |
| Postgres 纳秒时间戳 | timestamp9 | 必须放到目标 Postgres 环境验证 |
| SystemVerilog LSP | slang-server | 先补齐 submodule 和构建链 |
| OCaml 工程 | Core / Async / Hardcaml | 只适合 OCaml 团队 |
| 低延迟追踪 | magic-trace | 需要 Linux + Intel PT,不是普通开发机工具 |
只当参考材料的项目也很有价值,但不要直接引入生产:
- Flint:学习时间序列 join 设计
- BeakerX:学习多语言 notebook 设计
- Cook:学习旧时代大规模 batch scheduler
- optiver-asyncpg:学习金融机构 fork 上游库的方式
- WorldQuant Alpha101:学习 alpha 公式表达,不直接生产使用
十四、最后的判断
这 22 个项目不会告诉你顶级量化机构怎么赚钱。真正的策略、信号、数据、执行逻辑都不在 GitHub 上。
但它们会告诉你顶级量化机构日常在解决什么工程问题:
- 数据要能版本化、可复现、可回放
- notebook 不是玩具,它需要被清理、参数化、生产化
- DataFrame 探索效率会影响研究迭代速度
- 时间序列 join、tick 写入、纳秒时间戳都不是边缘问题
- 低延迟系统需要看到普通 profiler 看不到的时间尺度
- 大公司愿意开源的通常是工具链和工程文化,不是交易秘密
如果只选几个真正值得现在动手试的,优先级可以这样排:
- D-Tale
- ArcticDB
- pyflyby
- nbstripout-fast
- versioned-hdf5
- PyBloqs
- HeraclesQL
- Corral
magic-trace、PJRMI、timestamp9、slang-server、Core、Async、Hardcaml 不是优先级低,而是使用条件更窄。它们应该在真实业务场景出现之后再引入,而不是因为来自顶级量化机构就提前塞进技术栈。
其他项目可以读,但不要因为它来自顶级量化机构就默认适合你的项目。开源项目能不能用,最终还是要看维护状态、依赖栈、许可证、安装路径、测试和你的真实场景。