CZSC / Rust 内核与迁移
czsc 的 Rust 内核:为什么要重写,以及 0.9 怎么升上来
1.0 的变化不在接口,而在实现:分型、笔、中枢这些核心算法从 Python 搬到了 Rust,Python 侧退化成透传层。这对使用者意味着两件事 —— 装上就能跑(官方发预编译轮子),但要改实现就必须懂 Rust。这一页把九个 crate 的分工、构建配置与迁移清单摊开讲。
九个 crate,各管一层
官方 README 列出九个 crate 的名称,开发文档补充了各自职责。下表按职责维度整理,并标明谁是唯一的绑定入口。
| crate | 职责 | 关键内容 | 适用场景 | 注意点 |
|---|---|---|---|---|
| czsc-core | 缠论核心 | CZSC、FX、BI、ZS、RawBar、NewBar 与算法函数 | 纯 Rust 用户的核心依赖 | 1.0.1 起成笔阈值不再硬编码 |
| czsc-signals | 信号函数 | 实测 23 个 .rs 源文件(官方文档写 22) | 需要信号库时 | 数量口径差异见信号函数页 |
| czsc-trader | 交易器与联立 | CzscSignals、CzscTrader、Position、Event、策略门面、优化器 | 多级别决策 | 优化器在清理中迁到了工具模块 |
| czsc-utils | 工具层 | K 线合成、重采样、交易时间、单调性、周期数据 | 数据编排 | 重采样与合成器共用聚合逻辑 |
| czsc-ta | 技术指标算子 | 供信号函数内部使用 | 信号内部计算 | Python 顶层只保留五个算子别名 |
| czsc-signal-macros | 过程宏 | 信号注册宏 | 新增信号函数时 | 编译期注册,运行时靠注册表查 |
| czsc-derive | 派生宏 | 类型派生辅助 | 扩展类型时 | 属内部支撑 crate |
| czsc | Rust 侧门面 | 把上述能力聚合起来供 Rust 用户直接使用 | 不用 Python 的 Rust 用户 | 官方强调 Rust 用户与 Python 用户行为一致 |
| czsc-python | PyO3 绑定入口 | 九个 crate 中唯一启用扩展模块特性的 | 生成 Python 扩展时 | 构建脚本会在 Python 版本过低时提前失败 |
构建配置与 ABI 选择
这套混合架构的打包方式决定了「为什么一条 cp310-abi3 轮子能覆盖多个 Python 版本」。下表字段来自官方的包配置与 Rust 工作区配置。
| 配置项 | 取值 | 作用 | 注意点 |
|---|---|---|---|
| 构建后端 | maturin(要求 1.7 及以上、2.0 以下) | 把 Rust 扩展与 Python 源码一起打成轮子 | 源码构建前需要先装 maturin |
| 扩展模块名 | czsc._native | Python 侧导入入口 | 信号与核心类型都挂在这个模块下 |
| ABI 策略 | abi3-py310 + 扩展模块特性 | 用稳定 ABI 发布,启动时动态加载 | 因此只需一条 cp310-abi3 轮子即可被 3.10 及以上复用 |
| Rust edition | 2024 | 语言版本 | 需要较新的工具链 |
| PyO3 版本 | 0.28 | Rust 与 Python 的绑定层 | 工作区统一声明,由绑定 crate 叠加扩展模块特性 |
| 发布优化 | 链接时优化开启、优化级别 3、代码生成单元 1 | 换取运行时性能 | 代价是编译时间明显变长 |
| 版本来源 | Cargo.toml 工作区版本作为唯一版本源 | maturin 在动态版本模式下自动注入轮子元数据 | 避免 Python 与 Rust 两侧版本号漂移 |
| 内部依赖锁定 | 工作区内部 crate 用路径加精确版本双声明 | 发布时锁定同一版本 | 早期曾出现预发布版本被解析成更高稳定版的问题 |
一个被写进注释的依赖死锁
官方在依赖声明处留了一段注释,说明某个常用库为什么被刻意停在旧版本。这类信息通常只存在于源码里,值得单独看。
| 项 | 内容 | 影响 | 现状 |
|---|---|---|---|
| 被限制的依赖 | 数据处理库被暂停在 0.52.0 版本 | 不能用该库更新的版本 | 官方注释说明等待上游放宽后再生级 |
| 冲突链条 | 该库新版本的子依赖给时间库加了上限约束,而存根生成工具要求时间库更高版本,两者不可同时满足 | 强行升级会导致依赖解析失败 | 因此选择停在旧版本 |
| 对使用者的影响 | 几乎无感 —— 官方发的是预编译轮子 | 只有从源码构建时才会遇到 | 不要自行改动这条版本约束 |
| 同类风险 | Rust 生态里时间库、序列化库的版本约束经常互相打架 | 源码构建失败常见于此 | 遇到解析失败先看工作区注释,再动版本 |
开发宪法:Rust 与 Python 必须行为一致
官方把一条规则写成了项目宪法第一条,并声明任何改动都不得违反。它直接决定了你能在哪里改代码。
| 条款 | 要求 | 对使用者的含义 |
|---|---|---|
| 行为一致 | 需要 Rust 实现的部分,必须同时满足 Rust crate 与 Python 轮子行为一致:同样输入产生同样输出,默认参数、错误处理、边界条件、字段命名都一致 | 不管你用 Rust 还是 Python,同一功能的结论相同 |
| Python 只做两类事 | 一是纯透传,二是不可避免的绑定边界处理(例如表格数据与内存格式互转、路径类型转换) | Python 侧不会有额外逻辑,读源码时不必找隐藏分支 |
| 禁止适配层 | 禁止在 Python 侧做参数归一化、默认值补齐、返回字段重命名、错误码翻译、类型多态分支 | 想改行为只能改 Rust,或换参数 |
| 无 Python 回退 | 官方明确核心分析统一由 Rust 实现,不存在 Python 回退路径 | 不存在「装不上就退回纯 Python」这条路 |
| 一致性落地实例 | 策略门面的部分方法已整体下沉到 Rust,Python 侧只剩一行透传;文件完整性校验改用规范化 JSON 的哈希,两端可逐字节一致复现 | 这类校验结果在两种语言下可交叉验证 |
五类官方点名的违规信号
官方在同一节里列了「在代码审查中视为红线」的具体信号,并说明存在源码级测试防止适配层回流。之所以值得看,是因为它们也解释了为什么 Python 侧这么薄。
| 违规信号 | 为什么算违规 | 正确做法 |
|---|---|---|
| Python 函数体里出现按输入类型分支的多态判断 | 典型适配层写法,两端行为会分叉 | 把分支逻辑下沉到 Rust,Python 只透传 |
| 返回字段顺序或命名与 Rust 侧不一致 | 同一功能的输出形态不同,跨语言结果不可比 | 由 Rust 定义输出结构,Python 原样暴露 |
| Python 测试覆盖完整但 Rust 没有等价用例 | 一致性没有被真正验证 | 两端都要有对应测试 |
| 变更记录只写了 Python 端默认参数变化而 Rust 无对应改动 | 说明改了适配层而不是实现 | 实现层同步修改 |
| 适配层文件持续膨胀 | 与「逐步搬空到 Rust」的方向相反 | 新增逻辑默认进 Rust |
0.9.X 升到 1.0.X:清单式迁移
官方 README 的第一条提醒就是两者不兼容。下表把散落在变更记录与文档里的迁移点集中起来,便于逐项核对。
| 迁移项 | 0.9.X | 1.0.X | 处理方式 |
|---|---|---|---|
| 核心算法实现 | Python 实现 | Rust 实现,经扩展模块暴露 | 想了解旧逻辑可查看 0.9.X 分支 |
| 信号导入路径 | czsc.signals 命名空间 | 该命名空间层已删除 | 改用扩展模块下的信号路径,或经封装接口调用 |
| 带时区时间 | 静默转成 UTC(会导致周期桶错位) | 直接拒绝 | 入参前去掉时区信息 |
| 缺失行情值 | 成交量相加会沿桶传染 | 显式返回错误 | 先清洗数据或捕获异常 |
| 交易器更新 | 遇错静默继续 | 硬错抛 ValueError | 补异常处理 |
| 成笔阈值 | 环境变量名义存在但无效 | 真正生效,构造参数与环境变量均可控 | 重新核对参数,结果可能与之前不同 |
| 优化器位置 | 位于交易器模块 | 迁至工具模块 | 改导入路径 |
| 顶层 TA 别名 | 可能直接调用多个算子 | 仅保留五个别名,其余仅供信号内部使用 | 改用保留别名或自行实现 |
常见问题
架构与迁移相关的表述以官方 README、开发文档与变更记录为准。
必须装 Rust 才能用吗?
不是。官方在 PyPI 上发布预编译轮子,用普通安装命令即可,前提是 Python 版本达到 3.10 及以上。只有当你需要从源码构建(例如要改核心实现)时,才需要 Rust 工具链与 maturin。
为什么一个轮子能覆盖多个 Python 版本?
因为发布时使用了稳定 ABI 策略:扩展模块以 cp310 为基准声明稳定 ABI 并在 Python 启动时动态加载,因此同一条轮子可被 3.10 及以上版本复用。这也解释了官方包分类里为什么列出 3.10 到 3.13 四个版本。
我能在 Python 侧包装一层改行为吗?
官方明确不鼓励,并把这类做法写成了开发宪法的违规信号:Python 侧只允许纯透传与不可避免的绑定边界处理,禁止参数归一化、默认值补齐、字段重命名、错误码翻译与多态分支。而且存在源码级测试拦截这类回流。
从 0.9 升级,最小的改动是什么?
如果只是调用而不改实现,主要改三处:信号导入路径改为扩展模块下的路径;入参去掉时区信息;交易器与信号分析器的更新调用加上异常处理。若此前用环境变量调过成笔长度,升级后需要重新核对参数,因为该变量在 1.0.1 才真正生效。
为什么依赖被停在旧版本?
官方在依赖声明处写明:某个数据处理库的子依赖给时间库加了上限约束,而存根生成工具要求时间库更高版本,两者不可同时满足,因此选择暂停在旧版本等待上游放宽。使用预编译轮子的用户几乎无感,只有源码构建时会遇到。
Rust 内核这块 EasyClaw 有关系吗?
没有。EasyClaw 不提供这套 Rust 扩展的安装或运行环境,两者也没有已证实的集成关系。如果你不想接触构建工具链,可以先走 EasyClaw 技能路线完成指标与图表类研究,但缠论结构识别仍需自建。