Technical Documentation: waditu/czsc
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
czsc - 缠中说禅技术分析工具
项目文档 |
投研数据共享 |
信号函数编写规范 |
DEVIN生成的文档
| 胜可知 · 大模型量化策略研究平台(内测)
使用大模型辅助量化策略研究,当前重点支持时序量价择时策略
邀请码:5862EDBH2H9CDTGZ
| 新手路线 · 研究方法 · 使用案例 · 产品介绍 |
|---|
1.0.X 版本开始,缠论核心算法(分型、笔、中枢等)已全部迁移到 Rust 实现,通过 PyO3 扩展(
czsc._native)暴露给 Python。 需要了解旧 Python 实现逻辑的,可查看 0.9.X 版本。
源于缠中说缠博客,原始博客中的内容不太完整,且没有评论,以下是网友整理的原文备份
备份网址1:http://www.fxgan.com
备份网址2:https://chzhshch.blog
已经开始用czsc库进行量化研究的朋友,欢迎加入飞书群,快点击加入吧!
架构概览
CZSC 1.0 采用 Rust + Python 混合架构:
czsc (Python 包)
├── czsc._native ← Rust 扩展(PyO3),缠论核心
│ ├── CZSC / FX / BI / ZS / RawBar / NewBar / BarGenerator
│ ├── Freq / Mark / Direction / Signal / Event / Position / Operate
│ ├── CzscTrader / CzscSignals / generate_czsc_signals
│ ├── signals.* ← 220+ 信号函数(Python 端 7 个子模块;详见 crates/czsc-signals/src/)
│ └── ta.* ← Rust TA 算子 (ema/sma/boll 等,本次清理 起仅内部使用)
├── czsc.traders ← Python 门面,汇聚 Rust 交易 API
├── czsc.utils ← 工具函数(绘图/缓存/统计/交易工具)
├── czsc.connectors ← 数据源连接器(天勤/Tushare/CCXT/本地缓存)
├── czsc.strategies ← 策略门面(CzscStrategyBase/CzscJsonStrategy)
├── czsc.fsa ← 飞书自动化工具
├── czsc.mock ← 测试用模拟数据(转发自 wbt)
└── czsc.envs ← 环境变量管理底层 Rust workspace 包含 9 个 crate:czsc / czsc-core / czsc-derive / czsc-signals /czsc-trader / czsc-utils / czsc-ta / czsc-signal-macros /czsc-python(PyO3 绑定入口)。
项目贡献
- 缠论的
分型、笔的自动识别,由 Rust 实现并通过czsc._native暴露 - 定义并实现
信号-事件-交易量化交易逻辑体系,事件通过signals_all/signals_any/signals_not实现信号的逻辑组合 - 定义并实现了 220+ 信号函数(Rust 实现),见
czsc._native.signals - 缠论多级别联立决策分析交易,详见
CzscTrader - HTML 可视化(plotly + lightweight-charts):
czsc.utils.plotting.{kline,weight,lightweight}.*
安装使用
注意: Python 版本必须 ≥ 3.10
从 PyPI 安装预编译版本(推荐):
pip install czsc -U使用 uv 安装(推荐开发环境):
uv pip install czsc从源码构建(需要 Rust 工具链和 maturin):
# 安装 Rust:https://rustup.rs/
# 安装 maturin
pip install maturin
# 克隆并构建
git clone https://github.com/waditu/czsc.git
cd czsc
maturin develop --releaseRust 构建环境约束:底层依赖
pyo3/pyo3-stub-gen0.22 要求 Python ≥ 3.10。
当系统默认 Python 低于 3.10 时,请通过环境变量显式指定:bashexport PYO3_PYTHON=$(which python3.12) # 或任意 3.10+ 的解释器否则
cargo build/cargo test会在crates/czsc-python/build.rs提前 panic 并给出修复建议。
用uv sync --extra dev走 UV 流程时,UV 会自动选择项目声明的 Python,不需要额外设置。
快速开始
核心缠论分析
import czsc
from czsc import CZSC, Freq, format_standard_kline
from czsc.mock import generate_symbol_kines
# 生成模拟 K 线数据
df = generate_symbol_kines('000001', '30分钟', '20240101', '20240601')
# 转换为 RawBar 对象列表
bars = format_standard_kline(df, freq=Freq.F30)
# 创建 CZSC 分析对象(自动识别分型、笔、中枢)
czsc_obj = CZSC(bars)
print(f"笔数量:{len(czsc_obj.bi_list)}")
print(f"中枢数量:{len(czsc_obj.zs_list)}")K 线合成与多级别分析
from czsc import BarGenerator, Freq
# 使用 BarGenerator 进行 K 线合成
bg = BarGenerator(base_freq='1分钟', freqs=['5分钟', '30分钟', '日线'])
for bar in raw_bars:
bg.update(bar)
# 获取各周期 K 线
bars_5m = bg.bars['5分钟']
bars_30m = bg.bars['30分钟']信号生成
from czsc import generate_czsc_signals, get_signals_config, get_signals_freqs
# 配置信号序列(使用 Rust 实现的信号函数)
signals_seq = [
"czsc._native.signals.bar.bar_end_V230331",
"czsc._native.signals.cxt.cxt_bi_status_V230101",
]
# 解析信号所需的周期配置
freqs = get_signals_freqs(signals_seq)
config = get_signals_config(signals_seq)
# 生成信号序列
results = generate_czsc_signals(bars, signals_seq)权重回测
from czsc import WeightBacktest
from czsc.mock import generate_klines_with_weights
# 生成带权重的模拟数据
dfw = generate_klines_with_weights(seed=42)
# 运行权重回测
wb = WeightBacktest(dfw, fee_rate=0.0002)
print(wb.stats) # 回测统计汇总策略研究
from czsc import run_research, run_replay
# 单品种回放
run_replay(bars, signals_seq, pos_seq, res_path='./results/')
# 批量品种研究
run_research(symbols, signals_seq, pos_seq, res_path='./results/')缠论可视化与回测报告
# 缠论 K 线(多周期联立,自包含 HTML,离线即可打开)
from czsc.utils.plotting.lightweight import plot_czsc, plot_czsc_trader
html = plot_czsc(c, output="html") # 单周期
plot_czsc_trader(ct, output="html", path="trader.html") # 多周期
# 回测 HTML 报告:用 wbt.generate_backtest_report 一键产出
from wbt import generate_backtest_report
generate_backtest_report(df=dfw, output_path="report.html", weight_type="ts")使用案例
更多端到端案例汇总见飞书 wiki:CZSC 1.0.X 版本使用说明和案例。
| 序号 | 案例 | 一句话介绍 |
|---|---|---|
| 1 | 基于 Event 的策略回测 + wbt HTML 报告 | 5 行命令跑通 Event → Position → 回测 → 可交互 HTML 报告的完整闭环。 |
| 2 | lightweight_charts 缠论可视化 | 几行代码把缠论结构(分型/笔/线段/中枢)导出为自包含离线 HTML,便于分享与嵌入。 |
| 3 | 把信号函数画到 K 线主图(plot_czsc_signals) | 把信号触发点叠在 K 线主图上,直观调试 signals_config 与多周期联立。 |
| 4 | 性能基准测试:在本地复现 CZSC / CzscTrader 吞吐量 | 一行命令本地评估吞吐量,配套关键指标速查表,用于容量规划与加速效果核对。 |
| 5 | 信号函数模块深入介绍 | 系统讲解 czsc-signals 架构与 246 个信号函数的模块划分、参数模板、信号表达示例。 |
核心 API 一览
| 类型 | 符号 | 说明 |
|---|---|---|
| 缠论对象 | CZSC, FX, BI, ZS |
缠论核心数据结构(Rust) |
| K线对象 | RawBar, NewBar, BarGenerator |
K线与合成器(Rust) |
| 枚举 | Freq, Mark, Direction, Operate |
方向/频率等枚举(Rust) |
| 信号/事件 | Signal, Event, Position |
信号与持仓逻辑(Rust) |
| 分析工具 | check_fx, check_bi, remove_include |
分型/笔校验工具(Rust) |
| TA算子顶层别名 | czsc.ema, czsc.sma, czsc.rolling_rank, czsc.boll_positions, czsc.ultimate_smoother |
技术指标算子顶层别名(Rust) |
| 交易器 | CzscTrader, CzscSignals |
多级别交易决策(Rust) |
| 信号生成 | generate_czsc_signals |
批量信号生成(Rust) |
| 权重回测 | WeightBacktest |
权重序列回测(来自 wbt) |
| 策略 | CzscStrategyBase, CzscJsonStrategy |
策略封装(Python) |
| 模拟数据 | generate_symbol_kines |
测试用 K线数据(来自 wbt) |
| 格式转换 | format_standard_kline |
DataFrame → RawBar 列表 |
数据源连接器
czsc.connectors 提供多个数据源适配器:
| 模块 | 数据源 | 说明 |
|---|---|---|
tq_connector.py |
天勤(TQSdk) | 期货实时/历史行情 |
ts_connector.py |
Tushare | A股历史数据 |
ccxt_connector.py |
CCXT | 数字货币交易所 |
local_data.py |
投研数据本地缓存 | CZSC 共享数据本地读取入口 |
可视化(HTML 输出)
可视化统一以 plotly + lightweight-charts 输出 HTML:
| 模块 | 功能 |
|---|---|
czsc.utils.plotting.kline |
单周期 K 线 + 缠论结构(plotly Figure) |
czsc.utils.plotting.weight |
权重时序图(plotly Figure) |
czsc.utils.plotting.lightweight |
lightweight-charts 自包含 HTML,多周期联立 + 信号叠加 |
| 累计收益 / 回撤 / 月度热力图 / 综合回测概览 | 改用 wbt.generate_backtest_report 或直接 plotly,见 docs/migration/cleanup-non-czsc-core.md |
使用 plot_czsc、plot_czsc_trader 或 plot_czsc_signals 生成自包含 HTML 后,可直接在浏览器中打开。
相关项目(生态依赖)
czsc 在权重回测、权重落地与 TA 算子一致性校验上依赖以下三个独立开源项目,它们与 czsc 形成"分析 + 回测 + 落地 + 校验"的完整闭环:
| 项目 | GitHub | 角色 | 与 czsc 的关系 |
|---|---|---|---|
| wbt | https://github.com/zengbin93/wbt | 策略持仓权重回测引擎(Weight Back Test) | 硬依赖。提供 WeightBacktest / daily_performance / top_drawdowns / generate_backtest_report 等;czsc.mock 也转发自 wbt.mock。czsc 顶层 from czsc import WeightBacktest 即来自此包。 |
| wmr | https://github.com/zengbin93/wmr | 策略持仓权重管理系统(Weight Manager,DuckDB / ClickHouse 双后端) | 下游配套(非硬依赖)。wbt 负责"离线回测权重",wmr 负责"实盘 / 投研环境下权重的持久化、版本管理与查询"。czsc 产出信号 / 持仓 → wbt 跑回测 → wmr 落库供下游消费。 |
| talib-rs | https://github.com/0xcjun/talib-rs | 纯 Rust 实现的 TA-Lib 替代库 | 测试可选依赖。仅 tests/unit/test_ta_parity.py 用它对 czsc._native.ta(EMA / SMA 等)做数值 parity 校验,确保 czsc 自研 TA 算子与 TA-Lib 行为一致。运行时代码不依赖。 |
简言之:wbt = 回测,wmr = 权重落地,talib-rs = TA 基准;czsc 自身专注缠论核心算法(Rust)与信号-事件-交易体系,其余环节通过这三个项目解耦协同。
开发环境搭建
# 使用 UV 管理依赖(推荐)
uv sync --extra dev
# 构建 Rust 扩展(开发模式)
maturin develop
# 运行测试
uv run pytest tests/ -v
# 代码格式化
uv run ruff format czsc/ tests/
uv run ruff check czsc/ tests/关键环境变量
| 变量 | 说明 | 默认值 |
|---|---|---|
CZSC_MIN_BI_LEN |
最小笔长度 | 6 |
CZSC_MAX_BI_NUM |
最大笔数量 | 50 |
CZSC_VERBOSE |
是否输出详细日志 | False |
使用前必看
- 1.0.X 版本核心算法已迁移到 Rust,与 0.9.X 版本 不兼容;旧代码需按新 API 迁移;
- 免责声明:项目开源仅用于技术交流!
- 如果你发现了项目中的 Bug,可以先读一下《如何有效地报告 Bug》,然后在 issues 中报告 Bug
缠论精华
学了本ID的理论,去再看其他的理论,就可以更清楚地看到其缺陷与毛病,因此,广泛地去看不同的理论,不仅不影响本ID理论的学习,更能明白本ID理论之所以与其他理论不同的根本之处。
为什么要去了解其他理论,就是这些理论操作者的行为模式,将构成以后我们猎杀的对象,他们操作模式的缺陷,就是以后猎杀他们的最好武器,这就如同学独孤九剑,必须学会发现所有派别招数的缺陷,这也是本ID理论学习中一个极为关键的步骤。
真正的预测,就是不测而测。所有预测的基础,就是分类,把所有可能的情况进行完全分类。有人可能说,分类以后,把不可能的排除,最后一个结果就是精确的。
这是脑子锈了的想法,任何的排除,等价于一次预测,每排除一个分类,按概率的乘法原则,就使得最后的所谓精确变得越不精确,最后还是逃不掉概率的套子。
对于预测分类的唯一正确原则就是不进行任何排除,而是要严格分清每种情况的边界条件。任何的分类,其实都等价于一个分段函数,就是要把这分段函数的边界条件确定清楚。
边界条件分段后,就要确定一旦发生哪种情况就如何操作,也就是把操作也同样给分段化了。然后,把所有情况交给市场本身,让市场自己去当下选择。
所有的操作,其实都是根据不同分段边界的一个结果,只是每个人的分段边界不同而已。因此,问题不是去预测什么,而是确定分段边界。
原文整理
资料分享
- 链接:https://pan.baidu.com/s/1RXkP3188F0qu8Yk6CjbxRQ
- 提取码:vhue
Overview (`crates/czsc-core/README.md`)
czsc-core
💡 大多数用户应优先使用 facade crate
czsc:cargo add czsc一行拿到全部公共 API,本 crate 中的核心类型已在 facade
顶层 re-export。仅当你只想要缠论核心算法、不需要工具/信号/trader 时
才单独依赖本 crate。
缠论(CZSC,缠中说禅技术分析理论)核心数据结构与算法的 Rust 实现。
提供分型(FX)、笔(BI)、中枢(ZS)、CZSC 分析器等核心类型,以及 K 线
包含关系处理(remove_include)、笔识别(check_bi)等基础算法。
用法
[dependencies]
czsc-core = "1.0"use czsc_core::objects::{RawBar, Freq};
use czsc_core::analyze::CZSC;
let bars: Vec<RawBar> = /* ... */;
let czsc = CZSC::new(bars, 50, 6);
println!("最新一笔方向: {:?}", czsc.bi_list.last().map(|b| b.direction));特性
python:启用 PyO3 binding,供czsc-pythoncrate 聚合到czsc._native扩展。
项目主页
- 仓库:https://github.com/waditu/czsc
- Python 包:
czscon PyPI
Overview (`crates/czsc-derive/README.md`)
czsc-derive
⚙️ 本 crate 是 czsc 框架的内部 proc-macro,普通用户不需要直接依赖。
想用 czsc 量化分析框架请cargo add czsc(facade crate)。
本 crate 之所以独立发布是因为 cargo 强制 proc-macro 必须为单独的 crate,
仅供czsc-core/czsc-utils/czsc-trader等内部使用,也可被
其它需要 anyhow ↔ enum 自动桥接的 Rust 项目复用。
CZSC 工作区的过程式派生宏(procedural derive macros)。
当前提供 #[derive(CZSCErrorDerive)]:为枚举错误类型自动实现
From<anyhow::Error>—— 把任意anyhow::Error装进枚举的#[from]变体serde::Serialize—— 让错误结构跨 FFI / API 边界可序列化为字符串
用法
[dependencies]
czsc-derive = "1.0"
anyhow = "1"
thiserror = "2"
serde = { version = "1", features = ["derive"] }use czsc_derive::CZSCErrorDerive;
#[derive(CZSCErrorDerive, thiserror::Error, Debug)]
pub enum MyError {
#[error("invalid bar: {0}")]
InvalidBar(String),
#[from]
#[error("other: {0}")]
Other(anyhow::Error),
}历史
本 crate 在 1.0.0 之前曾名为 error-macros。重命名为 czsc-derive 是为了
避免占用 crates.io 上「error-macros」这一过于通用的命名空间。API 完全保持
兼容,仅 use 路径与 Cargo.toml 依赖名变化。
项目主页
Overview (`crates/czsc-signal-macros/README.md`)
czsc-signal-macros
⚙️ 本 crate 是 czsc 框架的内部 proc-macro,普通用户不需要直接依赖。
想用 czsc 量化分析框架请cargo add czsc(facade crate)。
本 crate 之所以独立发布是因为 cargo 强制 proc-macro 必须为单独的 crate,
由czsc-signals内部使用;
仅当你想为自己的 Rust 项目集成 czsc 风格的 inventory 信号注册系统时才
需要直接依赖。
czsc-signals 配套的过程宏 crate,
为缠论信号函数提供编译期签名校验与自动注册。
提供的宏
#[signal(category = "kline", template = "{freq}_{name}_{ver}")]
—— 标记一个函数为信号入口,校验签名并把元数据写入inventory#[signal_module(category = "kline")]
—— 标记一个mod为信号模块,统一应用类别默认值
用法
name 必须与函数名的版本后缀一致(如 foo_v230101 对应foo_V230101);template 描述实际输出的 k1_k2_k3,k3 末尾完整连续
ASCII 数字串必须与注册版本完全相等,不能仅包含相同尾部。保留模板既有标记形式(如 V230101
或 BS辅助221211),实际输出必须与模板一致。
[dependencies]
czsc-signal-macros = "1.0"
czsc-signals = "1.0"
inventory = "0.3"use czsc_signal_macros::{signal, signal_module};
#[signal_module(category = "kline")]
pub mod my_signals {
use super::*;
#[signal(template = "{freq}_我的信号_V250101")]
pub fn sig_v250101(/* ... */) -> /* ... */ { /* ... */ }
}项目主页
Overview (`crates/czsc-signals/README.md`)
czsc-signals
💡 大多数用户应优先使用 facade crate
czsc:cargo add czsc一行拿到全部公共 API,本 crate 已在czsc::signals
命名空间中 re-export。仅当你只想接入 czsc 的信号函数库到自己的
框架(不用 czsc-trader 状态机)时才单独依赖本 crate。
缠论信号函数库 —— 30+ 量化信号函数,按类别分组:
bar:基础 K 线形态信号cxt:缠论上下文信号(笔、中枢相关)tas:技术分析叠加信号(MACD、RSI 等)vol:成交量信号pressure/obv/cvolp:资金、压力、量价信号
每个信号通过 #[signal(...)]
属性宏在编译期自动注册到 inventory,运行期可按字符串配置(如"日线_D1#10_看多V240520")解析、调用。
用法
[dependencies]
czsc-signals = "1.0"// 通过 inventory 拿到注册的信号函数
let funcs = czsc_signals::all_signals();
println!("registered: {}", funcs.len());项目主页
- 仓库:https://github.com/waditu/czsc
- Python 入口:
czsc._native.signals
Overview (`crates/czsc-ta/README.md`)
czsc-ta
💡 如果你需要的是完整的缠论框架,请优先使用 facade crate
czsc:cargo add czsc一行拿到
全部 API(含本 crate 的算子)。本 crate 是少数适合单独依赖的场景——
当你只想要纯算子库(EMA/SMA/rolling_rank/...)、不需要任何缠论
业务代码时,可以零额外依赖地cargo add czsc-ta。
技术分析算子库 —— 缠论分析与量化策略常用的一组高性能数值算子。
包含 EMA、SMA、rolling_rank、ultimate_smoother 等纯 Rust 实现,附带可选的
numpy 互操作层(rust-numpy feature)以便供 PyO3 binding 直接零拷贝调用。
用法
[dependencies]
czsc-ta = "1.0"use czsc_ta::pure::ema::ema;
let prices = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let smoothed = ema(&prices, 3);特性
python:导出 PyO3 module。rust-numpy:在python之上加 numpy 数组互操作(零拷贝 in/out)。
项目主页
Overview (`crates/czsc-trader/README.md`)
czsc-trader
💡 大多数用户应优先使用 facade crate
czsc:cargo add czsc一行拿到全部公共 API,CzscTrader/CzscSignals/SignalConfig等核心类型已在 facade 顶层 re-export。仅当你已经
单独依赖 czsc-core/signals 并自己组装 trader 状态机时才直接依赖本
crate。
缠论多策略交易引擎、信号编译与参数优化。
核心组件:
engine_v2—— 事件驱动式 v2 执行引擎signals—— 信号字符串 → SignalConfig 编译,与czsc-signals配合trader——CzscTrader/CzscSignals状态机optimize—— 持仓策略参数网格搜索
权重回测(WeightBacktest)按 czsc 设计文档由外部 wbt
crate 提供,czsc-trader 只负责生成信号与持仓权重序列。
用法
[dependencies]
czsc-trader = "1.0"项目主页
- 仓库:https://github.com/waditu/czsc
- Python 入口:
czsc.CzscTrader/czsc.CzscSignals
Overview (`crates/czsc-utils/README.md`)
czsc-utils
💡 大多数用户应优先使用 facade crate
czsc:cargo add czsc一行拿到全部公共 API,本 crate 暴露的BarGenerator/is_trading_time/freq_data已在 facade 中 re-export。仅当你只用 K
线合成或交易日历工具、不需要其它 czsc 功能时才单独依赖本 crate。
缠论分析的 K 线合成与交易日历工具。
提供:
BarGenerator—— 多周期 K 线合成器freq_data—— 频率周期与对齐时间计算is_trading_time—— 国内 A 股 / 期货市场交易时段判断
用法
[dependencies]
czsc-utils = "1.0"use czsc_utils::is_trading_time;
use chrono::NaiveDateTime;
let dt: NaiveDateTime = "2024-01-02 10:30:00".parse().unwrap();
assert!(is_trading_time("000001.SZ", dt));特性
python:启用 PyO3 binding。
项目主页
Overview (`crates/czsc/README.md`)
czsc
🚀 这是 czsc 量化分析框架的官方推荐入口(facade crate)。
Rust 用户只需cargo add czsc即可获得全部公共 API。
CZSC(缠中说禅)量化分析框架的 Rust 入口。
本 crate 是一个 facade:本身不实现任何功能,只把 czsc Rust workspace 中
的 5 个业务 crate(czsc-core / czsc-utils / czsc-ta /czsc-signals / czsc-trader)汇总到一个 crate 名下,让纯 Rust 用户
不用一行一行加 5 个依赖。
想用 Python 接口?请直接
pip install czsc,那是基于maturin编译的
CPython 扩展,与本 facade 同源但接口面不同。
用法
[dependencies]
czsc = "1.0"use czsc::{CZSC, RawBar, Freq, BarGenerator, is_trading_time};
use czsc::analyze::CZSC as Analyzer;
let bars: Vec<RawBar> = /* ... */;
let analyzer = Analyzer::new(bars, 50);
println!("最新一笔方向: {:?}", analyzer.bi_list.last().map(|b| b.direction));也可以按子模块组织 import:
use czsc::objects::bi::BI;
use czsc::ta::ema;
use czsc::signals::registry::TRADER_SIGNAL_REGISTRY;
use czsc::trader::{CzscSignals, SignalConfig};命名空间速查
| 路径 | 等价于 |
|---|---|
czsc::analyze |
czsc_core::analyze |
czsc::objects::* |
czsc_core::objects::* |
czsc::error_chain |
czsc_core::error_chain |
czsc::ta |
czsc_ta::pure |
czsc::bar_generator |
czsc_utils::bar_generator |
czsc::freq_data |
czsc_utils::freq_data |
czsc::trading_time |
czsc_utils::trading_time |
czsc::signals |
czsc_signals |
czsc::trader |
czsc_trader 的对外公共面 |
顶层 czsc::* 还直接挂了最常用的核心类型:CZSC / RawBar / NewBar /Freq / FX / BI / ZS / Mark / Direction / Operate / Event /Position / Market / BarGenerator / CzscSignals / SignalConfig /is_trading_time。
Features
- (default) —— 纯 Rust API
python—— 透传给czsc-core/czsc-utils/czsc-ta,给嵌入 PyO3
的下游使用。一般用户不需要
工作区其它 crate
| crate | 说明 |
|---|---|
czsc-core |
缠论核心数据结构与算法 |
czsc-utils |
BarGenerator / freq_data / trading_time |
czsc-ta |
技术分析算子 |
czsc-signals |
30+ 量化信号函数 |
czsc-trader |
交易引擎与信号编译 |
czsc-derive |
错误派生宏(内部用) |
czsc-signal-macros |
信号注册宏(内部用) |
项目主页
Overview (`scripts/auto-czsc-quant/README.md`)
auto-czsc-quant
auto-czsc-quant 是一个脚本级实验编排器,用来跑通“大模型改 Position 配置 + CZSC 自动评分”的闭环。
当前实现不改 czsc 核心包;它可以读取人工候选 JSONL,也可以调用 .env 中配置的 Anthropic 兼容 LLM 生成完整 position 候选,然后用 mock、Tushare 或标准 feather 行情完成校验、回测、评分和实验记录。
快速运行
从仓库根目录执行:
PYTHONPATH=scripts/auto-czsc-quant uv run python -m auto_quant.cli run \
--config scripts/auto-czsc-quant/configs/example.json运行后会生成:
scripts/auto-czsc-quant/results/mock_position_demo/<run_id>/
config.json
accepted.jsonl
rejected.jsonl
leaderboard.csv
journal.md
report.html
best_positions/
curves/ # 每个候选的组合日收益序列,供报告叠加收益曲线对比report.html 是结构化优化报告,包含运行配置、top-k 策略累计收益曲线对比、最佳对比、数据加载、执行记录、leaderboard、候选详情、拒绝原因和产物索引。
优化语义
优化只允许两种合法操作(可重复执行上百、上千次,逐步累积变好):
- 入场优化:修改
opens中某个 event 的signals_all/signals_any/signals_not,换用不同的完全分类 signal,改进入场点。 - 出场优化:在
exits中新增 event,让策略提前正确止盈/止损,降低收益回撤。
每次迭代必须真正改变 opens/exits 的 event 信号,否则候选会被校验拒绝:
- 信号必须真实存在:候选里每个信号都要能被
czsc.derive_signals_config解析(即在 czsc 信号注册表中),臆造信号一律拒绝。 - 必须与 baseline 不同:opens/exits 信号签名与 baseline 完全相同的候选(无效克隆)会被拒绝;同一批里签名重复的候选也会被拒。
LLM 优化(candidate_mode=llm 或 /goal 提示)会把一份「信号目录」注入 prompt:从 czsc 注册表 + signal-functions skill 文档中提取经校验可直接使用的开多/平多方向信号字符串(参考 .claude/skills/signal-functions),让模型每轮都从真实信号池里选。
interval / timeout / stop_loss / T0 不参与优化:无论候选提交什么值,都会被 baseline(若配置 baseline_position_path)或安全默认值强制覆盖。/goal 下一轮提示会把上一轮 best 的 Position 作为新 baseline 喂回去,让优化持续迭代。
候选协议
候选文件是 JSONL,一行一个完整候选:
{"id":"trial_001","hypothesis":"入场优化:在开多 event 加入顶分型过滤","position":{...}}约束:
id必须唯一。hypothesis必须非空。position必须能被czsc.Position.load加载。position.opens不能为空。- 候选数量不能超过配置中的
max_candidates。 interval/timeout/stop_loss/T0即便填写也会被系统锁定覆盖。
数据源
data_source 支持三种模式:
mock:使用czsc.mock.generate_symbol_kines生成确定性行情,适合测试。tushare:直接通过czsc.connectors.ts_connector.get_raw_bars拉取真实行情;需要在环境变量或仓库根.env配置TUSHARE_TOKEN。feather:读取用户传入的标准行情 feather/ipc 文件;必需列为dt,symbol,open,close,high,low,vol,amount。
Tushare + 真实 LLM 示例:
PYTHONPATH=scripts/auto-czsc-quant uv run python -m auto_quant.cli run \
--config scripts/auto-czsc-quant/configs/example.tushare-llm.json.env 中默认读取:
TUSHARE_TOKEN=...
ANTHROPIC_BASE_URL=...
ANTHROPIC_API_KEY=...
ANTHROPIC_MODEL=...CLI
PYTHONPATH=scripts/auto-czsc-quant uv run python -m auto_quant.cli run --config <config.json>
PYTHONPATH=scripts/auto-czsc-quant uv run python -m auto_quant.cli prompt --run-dir <results/.../run_id>prompt 会读取上一轮 leaderboard.csv 和 journal.md,输出一个适合交给 /goal 的窄目标:只生成下一轮候选 JSONL,不修改源码。
Overview (`tests/compat/baselines/README.md`)
PR-1 基线快照
由 PR-1(盘点冻结)记录的"清理前"状态。
| 文件 | 说明 |
|---|---|
streamlit_imports.txt |
czsc/ tests/ docs/ 下所有 import streamlit / from streamlit 行 |
svc_refs.txt |
同上范围下所有 from czsc.svc / czsc.svc.* 引用行 |
ta_refs.txt |
同上范围下所有 from czsc.ta / czsc.ta. / from .ta 引用行 |
后续 PR 在 review 阶段用以下命令对比,每个 PR 应当只减不增:
git grep -nE "import streamlit|from streamlit" -- 'czsc/**/*.py' 'tests/**/*.py' 'docs/**/*.py' | diff - tests/compat/baselines/streamlit_imports.txt最终 PR-5 完成时,三份基线对应的现状都应为空。
Examples (`docs/examples.md`)
CZSC 核心功能案例库
目的:让用户在 30 分钟内对 czsc 库的核心能力建立完整的认知,并能围绕"缠论分析 → 信号 → 事件 → 策略 → 回测 → 可视化"这条主线动手实践。
适用版本:czsc ≥ 1.0(Rust + PyO3 架构,Python 端不再保留缠论算法回退)。
每个案例都是一个独立的 Python 脚本,位于 docs/examples/。
除明确标注 Tushare 的真实数据案例外,所有脚本仅依赖 czsc 顶层包(czsc.mock 提供模拟 K 线 / 权重数据,无需外部行情源),可以直接:
# 普通脚本
uv run python docs/examples/01_quick_start.py
# HTML 可视化脚本(生成自包含 HTML,浏览器直接打开)
uv run python docs/examples/13_lightweight_charts_html.py统一产物目录:03/07/08/09 这类有落盘产物的脚本,全部把输出写到
docs/examples/_output/(HTML 报告、parquet、Position JSON 等)。
该目录已在仓库.gitignore中忽略,无需手工清理;要重置只需rm -rf docs/examples/_output。
0. 系统全貌
┌──────────────┐ ┌─────────────────┐ ┌─────────────┐
│ RawBar 列表 │ ─► │ CZSC(缠论分析) │ ─► │ 分型/笔/中枢 │
└──────────────┘ └─────────────────┘ └─────────────┘
│ │
│ BarGenerator │ CzscSignals.update_signals(bar)
│(多周期合成) │
▼ ▼
┌──────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 多周期 bg │ ─► │ signals_config │ ─► │ 信号字典 cs.s │
└──────────────┘ └─────────────────┘ └─────────────────┘
│
▼ Event(signals_all/any/not)
┌─────────────────┐
│ Position(仓位)│
└─────────────────┘
│
CzscTrader.update(bar) / CzscStrategyBase.backtest
▼
┌─────────────────────────────────────┐
│ pairs / holds / signals (Arrow) │
└─────────────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
WeightBacktest plot_*绘图
(时序/截面) (离线 HTML)1. 案例索引
第一组:缠论核心(必读)
| # | 文件 | 核心 API | 你将学到 |
|---|---|---|---|
| 01 | 01_quick_start.py |
czsc.mock.generate_symbol_kines · format_standard_kline · CZSC |
数据准备、构造分析对象、增量 update 推进 |
| 02 | 02_chan_structures.py |
FX · BI · ZS · Mark · Direction |
分型/笔/中枢的属性、力度(power/SNR/slope/angle)、未完成笔(ubi) |
第二组:多级别与信号
| # | 文件 | 核心 API | 你将学到 |
|---|---|---|---|
| 04 | 04_bar_generator.py |
BarGenerator · freqs_sorted |
用低周期 K 线实时合成高周期 K 线,构造多级别 CZSC |
| 05 | 05_signals.py |
CzscSignals · generate_czsc_signals · get_signals_config |
三种信号生成方式 + 反推信号配置/涉及周期 |
第三组:事件驱动策略
| # | 文件 | 核心 API | 你将学到 |
|---|---|---|---|
| 06 | 06_event_position.py |
Event · Position · Operate · Event.is_match |
信号 → 事件(all/any/not 逻辑组合) → 持仓 |
| 07 | 07_strategy_backtest.py |
CzscStrategyBase · tactic.backtest · tactic.replay · tactic.save_positions |
完整的策略类定义 + 内存回测 + 落盘回放 + 持仓序列化(产物:_output/07_strategy_backtest/) |
第四组:回测与离线可视化
| # | 文件 | 核心 API | 你将学到 |
|---|---|---|---|
| 08 | 08_weight_backtest.py |
WeightBacktest · daily_performance · top_drawdowns |
时序权重回测的完整链路 + 核心绩效指标 |
| 13 | 13_event_weight_backtest.py |
CzscStrategyBase.backtest · wbt.generate_backtest_report |
从 Event 出发的策略回测 + 一键生成 HTML 报告(single / multi-event) |
第五组:lightweight-charts HTML(自包含交互看图)
| # | 文件 | 关键 API | 你将看到 |
|---|---|---|---|
| 13 | 13_lightweight_charts_html.py |
plot_czsc · plot_czsc_trader |
缠论 K 线 + 多周期联立,自包含 HTML(无需服务端) |
| 15 | 15_lightweight_signals_html.py |
plot_czsc_signals |
信号叠加版本,含 signal timeline + tooltip |
第六组:真实数据研究(需 `TUSHARE_TOKEN`)
| # | 文件 | 核心 API | 你将看到 |
|---|---|---|---|
| 14 | 14_tushare_event_backtest.py |
get_raw_bars · CzscStrategyBase · WeightBacktest |
单标的 30 分钟 Event 策略回测与 HTML 报告 |
| 18 | 18_tushare_daily_event_universe.py |
generate_czsc_signals · adjust_holding_weights · WeightBacktest |
日线股票池事件选股与 HTML 报告 |
运行真实数据案例前,请设置 TUSHARE_TOKEN;缺少令牌时脚本会明确退出,不影响本地 mock 案例验证。
第七组:性能基准
| # | 文件 | 核心 API | 你将看到 |
|---|---|---|---|
| 17 | 17_perf_benchmark.py |
CZSC · CzscTrader |
20 年 5 分钟 K 线下 CZSC / CzscTrader 两条路径的吞吐量基准(纯文本输出) |
2. 模块关联速查
| 想做的事 | 应使用的模块 / 函数 | 案例编号 |
|---|---|---|
| 拿到模拟 K 线 / 权重数据 | czsc.mock.generate_symbol_kines / generate_klines_with_weights |
01, 08 |
| 转 DataFrame → RawBar | czsc.format_standard_kline |
01 |
| 跑缠论分析 | czsc.CZSC(bars) |
01, 02 |
| 多周期 K 线合成 | czsc.BarGenerator |
04, 11 |
| 单次批量计算信号 | czsc.generate_czsc_signals |
05 |
| 实盘流式信号计算 | czsc.CzscSignals + update_signals(bar) |
05, 11 |
| 信号字符串 → 配置 | czsc.get_signals_config / get_signals_freqs |
05, 06 |
| 事件逻辑组合 | czsc.Event(signals_all/any/not) |
06 |
| 完整持仓策略 | czsc.Position / czsc.CzscStrategyBase |
06, 07 |
| 多级别交易决策 | czsc.CzscTrader |
11 |
| 内存回测 | tactic.backtest(bars) 或 czsc.run_research |
07 |
| 落盘回放 | tactic.replay(bars, res_path) 或 czsc.run_replay |
07 |
| 持仓序列化 | Position.dump / tactic.save_positions / CzscJsonStrategy |
07 |
| 权重回测 | czsc.WeightBacktest(来自 wbt) |
08, 12 |
| 绩效指标 | czsc.daily_performance / top_drawdowns |
08 |
| 后验时段标记 | czsc.mark_cta_periods / mark_volatility |
— |
| 离线 K 线绘图 | czsc.utils.plotting.lightweight.plot_czsc{,_trader,_signals} |
13, 15 |
| 回测 HTML 报告 | wbt.generate_backtest_report |
13 |
3. 典型工作流
3.1 "策略研究 → 回测 → 可视化" 完整流水线
[01] 准备数据
↓
[02] 看 CZSC 缠论结构(确认笔/分型识别正常)
↓
[04] 决定使用哪些周期,构造 BarGenerator
↓
[05] 设计信号配置(哪些信号 × 哪些周期)
↓
[06] 把信号组装成 Event;多个 Event 组装成 Position
↓
[07] 用 CzscStrategyBase 子类化 + 跑 backtest/replay
↓
[08] 把 trader / replay 输出的权重做 WeightBacktest 精细回测
↓
[09] 配合 EDA 工具(如 mark_cta_periods)做切片分析
↓
[13/15] 生成自包含 HTML 报告分享给协作者3.2 "看盘 / Demo" 工作流
[01] 跑通 mock 数据 → CZSC
↓
[03] 离线生成一份 HTML 看图(plotly)
↓
[13/15] lightweight-charts 自包含 HTML,浏览器直接打开4. 必须知道的几件事
4.1 数据格式约定
- K 线 DataFrame 必须包含列:
dt / symbol / open / close / high / low / vol / amount
(czsc.mock.generate_symbol_kines返回的就是该 schema)。 - 权重 DataFrame 必须包含列:
dt / symbol / weight / price,weight ∈ [-1, 1]
(>0 多头,<0 空头,0 空仓);用于WeightBacktest。 - 信号字符串 格式:
"{freq}_{key2}_{key3}_{v1}_{v2}_{v3}_{score}"
(前 3 段是 key,后 4 段是 value),例:"30分钟_D1_表里关系V230101_向上_任意_任意_0"。
4.2 信号函数命名
- Rust 端注册的信号函数名是简短形式;
#[signal]宏自动注册,不再使用V<yyMMdd>版本后缀,
例如:cxt_bi_status_V230101、bar_zdt_V230331、tas_ma_base_V221101。 - 完整列表:
czsc._native.signals.bar.list_signal_names()
(bar / cxt / tas / vol / pressure / obv / cvolp七个子模块入口都返回全集)。 - 每个信号的 key 模板:
czsc._native.signals.bar.get_signal_template(name)。
4.3 RawBar → DataFrame
pd.DataFrame(c.bars_raw)在新版 RawBar 上不能正确识别字段;
请使用c.bars_raw_df(CZSC 自带属性)或pd.DataFrame([b.to_dict() for b in bars])。
4.4 Position.operates 的时间字段
Position.operates是 list of dict,其中dt是 unix 时间戳(秒),
画图时需要pd.to_datetime(value, unit="s")转成 datetime。
4.5 环境变量(`czsc.envs`)
| 变量 | 默认 | 说明 |
|---|---|---|
CZSC_VERBOSE |
False | 是否打印详细日志 |
CZSC_MIN_BI_LEN |
6 | 笔最小长度 |
CZSC_MAX_BI_NUM |
50 | 单个 CZSC 实例保留的最大笔数 |
4.6 缓存
- 默认缓存目录:
czsc.home_path - 查看大小:
czsc.get_dir_size(czsc.home_path) - 清空:
czsc.empty_cache_path()
5. 可视化兼容性提示
可视化统一使用
czsc.utils.plotting.*(plotly + HTML)或czsc.utils.plotting.lightweight.*(lightweight-charts)。
6. 进一步阅读
- 顶层 API 列表:
czsc/__init__.py的__all__ - 类型 stub:
czsc/_native/__init__.pyi(Rust 扩展的完整类型声明) - 信号开发规范:飞书文档 - 信号函数编写规范
- WeightBacktest 工具集:见 czsc-weight-backtest skill 与 wbt 包文档
- CLAUDE.md:项目整体设计与最佳实践
Public Api (`docs/public_api.md`)
CZSC 公开 API 参考手册
本文档列出
czsc包通过__all__暴露给用户的所有公开 API,按功能分类。
每个 API 标注:用途说明、真正的功能实现位置、依赖的内部函数/模块。生成日期:2026-05-24 | 基于
czsc/__init__.py中__all__列表
目录
- 缠论核心类型(Rust 实现)
- 缠论核心算法函数(Rust 实现)
- 交易器与信号管理
- 策略门面
- 研究 / 回测 / 优化入口
- 数据格式转换
- 技术指标(TA)算子
- 探索性数据分析(EDA)
- 回测与绩效(来自 wbt)
- 缓存与数据管理
- IO 工具
- 交易工具
- 通用工具函数
- K 线质量检查
- 日志与告警工具
- 可视化
- 飞书集成(fsa)
- 数据连接器(connectors)
- 模拟数据(mock)
- 环境变量(envs)
- 包元信息
1. 缠论核心类型(Rust 实现)
所有核心类型由 Rust crate czsc-core 实现,通过 czsc-python(PyO3)暴露为 czsc._native.*,
顶层 czsc 命名空间做 re-export。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
CZSC |
缠论分析主对象,管理原始 K 线、分型、笔列表 | crates/czsc-core/src/czsc.rs → czsc._native.CZSC |
RawBar, NewBar, FX, BI, check_fx, check_bi, remove_include |
RawBar |
原始 K 线数据结构 | crates/czsc-core/src/bar.rs → czsc._native.RawBar |
Freq |
NewBar |
去包含关系后的 K 线数据结构 | crates/czsc-core/src/bar.rs → czsc._native.NewBar |
RawBar |
FX |
分型(顶分型/底分型)数据结构 | crates/czsc-core/src/fx.rs → czsc._native.FX |
NewBar, Mark |
BI |
笔数据结构,包含力度、R²、斜率等统计量 | crates/czsc-core/src/bi.rs → czsc._native.BI |
FX, NewBar, Direction, FakeBI |
FakeBI |
笔内部分型连接得到的近似次级别笔 | crates/czsc-core/src/bi.rs → czsc._native.FakeBI |
FX |
ZS |
中枢数据结构 | crates/czsc-core/src/zs.rs → czsc._native.ZS |
BI, Direction |
Freq |
K 线周期枚举(F1/F5/F15/F30/F60/D/W/M 等) | crates/czsc-core/src/enums.rs → czsc._native.Freq |
无 |
Mark |
分型标记枚举(顶/底/无) | crates/czsc-core/src/enums.rs → czsc._native.Mark |
无 |
Direction |
方向枚举(向上/向下/无) | crates/czsc-core/src/enums.rs → czsc._native.Direction |
无 |
Operate |
操作枚举(开多/开空/平多/平空等) | crates/czsc-core/src/enums.rs → czsc._native.Operate |
无 |
Signal |
信号数据结构 | crates/czsc-core/src/signal.rs → czsc._native.Signal |
无 |
Event |
事件数据结构(信号的逻辑组合:AND/OR/NOT) | crates/czsc-trader/src/event.rs → czsc._native.Event |
Signal, Operate |
Position |
持仓/仓位数据结构(开仓事件+平仓事件+风控) | crates/czsc-trader/src/position.rs → czsc._native.Position |
Event, Operate |
BarGenerator |
多周期 K 线合成器(基础周期→多个大级别) | crates/czsc-trader/src/bar_generator.rs → czsc._native.BarGenerator |
RawBar, Freq, freq_end_time, is_trading_time |
ParsedSignalDoc |
信号文档解析结果 | crates/czsc-signals/ → czsc._native.ParsedSignalDoc |
无 |
2. 缠论核心算法函数(Rust 实现)
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
check_fx |
检查三根无包含 K 线是否构成分型 | crates/czsc-core/src/analyze.rs → czsc._native.check_fx |
NewBar, FX, Mark |
check_fxs |
批量检查分型序列 | crates/czsc-core/src/analyze.rs → czsc._native.check_fxs |
check_fx, FX |
check_bi |
检查两个分型之间是否构成笔 | crates/czsc-core/src/analyze.rs → czsc._native.check_bi |
FX, BI, NewBar |
remove_include |
去除 K 线包含关系 | crates/czsc-core/src/analyze.rs → czsc._native.remove_include |
RawBar, NewBar |
freq_end_time |
计算指定周期的 K 线结束时间 | crates/czsc-core/src/utils.rs → czsc._native.freq_end_time |
Freq |
is_trading_time |
判断给定时间是否为交易时间 | crates/czsc-core/src/utils.rs → czsc._native.is_trading_time |
Freq |
parse_signal_doc |
解析信号函数的文档字符串 | crates/czsc-signals/ → czsc._native.parse_signal_doc |
ParsedSignalDoc |
3. 交易器与信号管理
统一入口:czsc.traders(facade 模块),实际实现在 czsc._native(Rust)。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
CzscSignals |
多周期联立信号分析器(不含仓位管理) | crates/czsc-trader/src/signals.rs → czsc._native.CzscSignals |
BarGenerator, CZSC, 信号函数注册表 |
CzscTrader |
多周期联立交易器(含仓位管理+信号) | crates/czsc-trader/src/trader.rs → czsc._native.CzscTrader |
CzscSignals, Position, Event |
generate_czsc_signals |
在 K 线序列上批量生成信号,返回 DataFrame | crates/czsc-trader/ → czsc._native.generate_czsc_signals |
BarGenerator, CzscTrader, 信号函数注册表 |
get_signals_config |
将信号序列解析为信号配置列表(运行时格式) | crates/czsc-trader/ → czsc._native.get_signals_config |
derive_signals_config |
get_signals_freqs |
从信号配置中提取涉及的周期列表 | crates/czsc-trader/ → czsc._native.get_signals_freqs |
derive_signals_freqs |
derive_signals_config |
从信号 key 列表派生信号配置 | crates/czsc-trader/ → czsc._native.derive_signals_config |
信号函数注册表 |
derive_signals_freqs |
从信号配置列表派生周期列表 | crates/czsc-trader/ → czsc._native.derive_signals_freqs |
无 |
get_unique_signals |
从 Position 列表中提取去重的唯一信号 key | crates/czsc-trader/ → czsc._native.get_unique_signals |
Position |
4. 策略门面
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
CzscStrategyBase |
策略开发抽象基类;子类实现 positions 属性即可获得回测/回放/序列化能力 |
czsc/strategies.py:56 |
czsc._native.{derive_signals_config, derive_signals_freqs, strategy_unique_signals, strategy_save_position, strategy_load_position}, czsc._runtime_adapters.{bars_to_dataframe, sort_freqs}, czsc.research.{run_research, run_replay} |
CzscJsonStrategy |
从 JSON 文件加载持仓定义的策略包装器(继承 CzscStrategyBase) |
czsc/strategies.py:284 |
CzscStrategyBase.load_positions |
CzscStrategyBase 关键方法/属性
| 方法/属性 | 用途 | 内部依赖 |
|---|---|---|
positions (抽象) |
子类必须实现,返回 list[Position] |
— |
unique_signals |
汇总所有 Position 的信号 key(去重) | czsc._native.strategy_unique_signals |
signals_config |
由 unique_signals 派生的信号配置列表 |
czsc._native.derive_signals_config |
freqs |
策略涉及的所有周期(排序去重) | czsc._native.derive_signals_freqs, czsc._runtime_adapters.sort_freqs |
base_freq |
策略基础周期(最高频) | freqs |
backtest(bars, **kwargs) |
内存模式回测 | czsc.research.run_research |
replay(bars, res_path, **kwargs) |
回放并落盘 | czsc.research.run_replay |
save_positions(path) |
持仓序列化为 JSON(带 sha256 校验) | czsc._native.strategy_save_position |
load_positions(files, check) |
从 JSON 加载 Position 列表 | czsc._native.strategy_load_position |
5. 研究 / 回测 / 优化入口
统一入口:czsc.research(Python 薄封装)→ Rust 后端。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
run_research |
内存模式策略研究,返回 Arrow 格式结果 | czsc/research.py:102 |
czsc._native.run_research, czsc._utils._df_convert.pandas_to_arrow_bytes, czsc.models.ResearchResult |
run_replay |
单标的回放,可选落盘 parquet | czsc/research.py:150 |
czsc._native.run_replay, czsc.models.ReplayResult |
run_optimize_batch |
批量参数优化任务 | czsc/research.py:192 |
czsc._native.run_optimize_batch, czsc._runtime_adapters.normalize_candidate_events, czsc.models.OptimizeResult |
build_open_optim_positions |
构造开仓优化候选仓位(不执行回测) | czsc/research.py:242 |
czsc._native.build_open_optim_positions |
build_exit_optim_positions |
构造平仓优化候选仓位(不执行回测) | czsc/research.py:264 |
czsc._native.build_exit_optim_positions, czsc._runtime_adapters.normalize_candidate_events |
优化框架辅助类(`czsc.utils.optimize`)
通过 czsc.utils 暴露,非顶层 __all__,但属于常用公开 API:
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
OpensOptimize |
开仓参数批量优化外观类 | czsc/utils/optimize.py:281 |
run_optimize_batch, CzscOpenOptimStrategy, czsc._runtime_adapters.bars_to_dataframe |
ExitsOptimize |
平仓参数批量优化外观类 | czsc/utils/optimize.py:422 |
run_optimize_batch, CzscExitOptimStrategy, czsc._runtime_adapters.normalize_candidate_events |
CzscOpenOptimStrategy |
开仓优化策略(按候选信号展开变体仓位) | czsc/utils/optimize.py:150 |
CzscStrategyBase, Position |
CzscExitOptimStrategy |
平仓优化策略(按候选事件展开变体仓位) | czsc/utils/optimize.py:204 |
CzscStrategyBase, Position, czsc._runtime_adapters.normalize_candidate_event |
6. 数据格式转换
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
format_standard_kline |
将标准 K 线 DataFrame 转换为 list[RawBar] |
czsc/_format_standard_kline.py:57 |
czsc._native.{RawBar, Freq} |
resample_bars |
将基础周期 K 线(DataFrame 或 list[RawBar])重采样到目标周期 |
czsc/_resample_bars.py:61 |
czsc._native.{resample_bars, RawBar, Freq} |
7. 技术指标(TA)算子
Rust 实现,位于 crates/czsc-ta/,通过 czsc._native.ta.* 暴露,顶层有别名。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
ema |
指数移动平均 | crates/czsc-ta/ → czsc._native.ema |
无 |
sma |
简单移动平均 | crates/czsc-ta/ → czsc._native.sma |
无 |
rolling_rank |
滚动排名 | crates/czsc-ta/ → czsc._native.rolling_rank |
无 |
boll_positions |
布林带位置指标 | crates/czsc-ta/ → czsc._native.boll_positions |
无 |
ultimate_smoother |
终极平滑器 | crates/czsc-ta/ → czsc._native.ultimate_smoother |
无 |
8. 探索性数据分析(EDA)
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
monotonicity |
单调性分析(等价于 scipy.stats.spearmanr) | crates/czsc-core/ → czsc._native.monotonicity |
无(Rust 实现) |
mark_cta_periods |
【后验】标记 CTA 最容易/最难赚钱的时间段 | czsc/utils/mark_cta_periods.py:22 |
czsc.{CZSC, Freq, format_standard_kline} |
mark_volatility |
【后验】标记时序/截面波动率最大/最小的时间段 | czsc/utils/mark_volatility.py:14 |
无(纯 pandas/numpy) |
9. 回测与绩效(来自 wbt)
这三个 API 来自硬依赖 wbt 包,在 czsc 顶层直接 re-export。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
WeightBacktest |
基于权重序列的回测引擎 | wbt.WeightBacktest |
wbt 内部 |
daily_performance |
日线绩效指标计算 | wbt.daily_performance |
wbt 内部 |
top_drawdowns |
最大回撤区间识别 | wbt.top_drawdowns |
wbt 内部 |
10. 缓存与数据管理
定义于 czsc/utils/data/,通过 czsc.utils → czsc 顶层暴露。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
home_path |
缓存根目录(~/.czsc,可通过 CZSC_HOME 环境变量覆盖) |
czsc/utils/data/cache.py:25 |
无 |
get_dir_size |
获取目录大小(单位:Bytes) | czsc/utils/data/cache.py:29 |
无 |
empty_cache_path |
清空缓存目录 | czsc/utils/data/cache.py:41 |
home_path |
DiskCache |
磁盘缓存类(支持 pkl/json/csv/parquet 等多种格式) | czsc/utils/data/cache.py:47 |
home_path |
disk_cache |
磁盘缓存装饰器 | czsc/utils/data/cache.py:175 |
DiskCache |
clear_cache |
清空指定缓存文件夹 | czsc/utils/data/cache.py:230 |
无 |
clear_expired_cache |
清除过期缓存文件 | czsc/utils/data/cache.py:214 |
无 |
DataClient |
统一数据接口客户端(兼容 Tushare,支持本地缓存) | czsc/utils/data/client.py:48 |
get_url_token, get_dir_size |
set_url_token |
设置数据接口凭证码 | czsc/utils/data/client.py:13 |
无 |
get_url_token |
获取数据接口凭证码 | czsc/utils/data/client.py:29 |
无 |
11. IO 工具
定义于 czsc/utils/io.py,通过 czsc.utils → czsc 顶层暴露。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
dill_dump |
使用 dill 序列化对象到文件 | czsc/utils/io.py:7 |
dill |
dill_load |
从文件反序列化 dill 对象 | czsc/utils/io.py:13 |
dill |
save_json |
将 dict 保存为 JSON 文件(UTF-8) | czsc/utils/io.py:30 |
无 |
read_json |
从 JSON 文件读取 dict | czsc/utils/io.py:35 |
无 |
12. 交易工具
定义于 czsc/utils/trade.py,通过 czsc.utils → czsc 顶层暴露。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
risk_free_returns |
创建无风险收益率序列 | czsc/utils/trade.py:17 |
_get_trade_dates(内部函数) |
update_nxb |
计算并添加后 N 根 bar 的累计收益列 | czsc/utils/trade.py:33 |
无(纯 pandas) |
update_bbars |
计算并添加前 N 根 bar 的累计收益列 | czsc/utils/trade.py:67 |
无(纯 pandas) |
update_tbars |
计算带 Event 方向信息的未来收益 | czsc/utils/trade.py:89 |
无(纯 pandas) |
resample_to_daily |
将非日线数据转换为日线数据 | czsc/utils/trade.py:110 |
_get_trade_dates(内部函数) |
adjust_holding_weights |
按固定持仓周期调整权重 | czsc/utils/trade.py:161 |
无(纯 pandas) |
13. 通用工具函数
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
import_by_name |
通过字符串导入模块/类/函数 | czsc/utils/__init__.py:175 |
无 |
freqs_sorted |
K 线周期列表排序并去重 | czsc/utils/__init__.py:198 |
czsc._runtime_adapters.sort_freqs |
print_df_sample |
以 reST 表格形式打印 DataFrame 前 N 行 | czsc/utils/__init__.py:211 |
tabulate |
to_arrow |
将 DataFrame 转换为 Arrow IPC 字节串 | czsc/utils/__init__.py:222 |
pyarrow |
get_py_namespace |
获取 Python 脚本文件中的 namespace | czsc/utils/__init__.py:128 |
无 |
code_namespace |
获取 Python 代码字符串中的 namespace | czsc/utils/__init__.py:156 |
无 |
cross_sectional_ic |
计算截面相关性(IC / ICIR) | czsc/utils/analysis/corr.py:20 |
tqdm, pandas |
index_composition |
按收益率加权合成指数 K 线 | czsc/utils/index_composition.py:11 |
无(纯 pandas) |
14. K 线质量检查
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
check_kline_quality |
综合检查多 symbol K 线数据质量(缺失值/类型/顺序/价格/成交量等) | czsc/utils/kline_quality.py:267 |
check_missing_values, check_data_types, check_datetime_order, check_price_reasonableness, check_volume_amount, check_symbol_consistency, check_duplicate_records, check_extreme_values(均为同文件内部函数) |
15. 日志与告警工具
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
log_strategy_info |
打印策略数据的详细信息(品种、时间范围、权重统计) | czsc/utils/log.py:5 |
loguru |
capture_warnings |
上下文管理器,捕获 warning 信息 | czsc/utils/warning_capture.py:14 |
无 |
execute_with_warning_capture |
执行函数并捕获 warning 信息 | czsc/utils/warning_capture.py:66 |
capture_warnings |
16. 可视化
lightweight-charts HTML 输出
定义于 czsc/utils/plotting/lightweight/,按需导入。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
plot_czsc |
单周期 CZSC 对象 → lightweight-charts HTML(三 sub-pane:K 线+成交量+MACD) | czsc/utils/plotting/lightweight/__init__.py:45 |
czsc._native.CZSC, _data.build_from_czsc, _html_renderer.render, _theme |
plot_czsc_trader |
多周期 CzscTrader/CzscSignals → 多 pane HTML | czsc/utils/plotting/lightweight/__init__.py:87 |
_data.build_from_trader, _html_renderer.render, _theme |
plot_czsc_signals |
K 线+信号函数历史触发点叠加 → HTML | czsc/utils/plotting/lightweight/__init__.py:127 |
generate_czsc_signals, get_signals_freqs, BarGenerator, CzscTrader, _signals.build_signal_overlays |
使用方式:
from czsc.utils.plotting.lightweight import plot_czsc, plot_czsc_trader, plot_czsc_signals17. 飞书集成(fsa)
定义于 czsc/fsa/,通过 czsc.fsa 子包暴露。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
FeishuApiBase |
飞书 API 基类(封装鉴权) | czsc/fsa/base.py |
requests |
request |
飞书 API 通用请求函数 | czsc/fsa/base.py |
requests |
SpreadSheets |
飞书电子表格操作类 | czsc/fsa/spreed_sheets.py |
FeishuApiBase |
SingleSheet |
单个工作表操作类 | czsc/fsa/spreed_sheets.py |
FeishuApiBase |
IM |
飞书即时消息操作类 | czsc/fsa/im.py |
FeishuApiBase |
BiTable |
飞书多维表格操作类 | czsc/fsa/bi_table.py |
FeishuApiBase |
push_text |
推送文本消息到飞书群聊 | czsc/fsa/__init__.py:40 |
requests |
push_card |
推送卡片消息到飞书群聊 | czsc/fsa/__init__.py:61 |
requests |
push_message |
使用飞书 APP 批量推送消息 | czsc/fsa/__init__.py:131 |
IM |
read_feishu_sheet |
读取飞书电子表格 | czsc/fsa/__init__.py:95 |
SpreadSheets |
update_spreadsheet |
更新飞书电子表格 | czsc/fsa/__init__.py:168 |
SpreadSheets |
get_feishu_members_by_mobiles |
根据手机号获取飞书用户 ID | czsc/fsa/__init__.py:115 |
IM |
push_strategy_latest |
推送最新策略结果到飞书 | czsc/fsa/push_strategy_latest.py |
IM, SpreadSheets |
18. 数据连接器(connectors)
定义于 czsc/connectors/,通过 czsc.connectors 子包暴露。
| 模块 | 用途 | 文件位置 |
|---|---|---|
tq_connector |
天勤量化数据源连接器 | czsc/connectors/tq_connector.py |
ts_connector |
Tushare 数据源连接器 | czsc/connectors/ts_connector.py |
ccxt_connector |
数字货币(CCXT)数据源连接器 | czsc/connectors/ccxt_connector.py |
local_data |
投研数据本地缓存读取入口 | czsc/connectors/local_data.py |
使用方式:
from czsc.connectors import ts_connector
# 或
from czsc.connectors.ts_connector import get_raw_bars19. 模拟数据(mock)
定义于 czsc/mock.py,转发到 wbt.mock。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
generate_symbol_kines |
生成单标的、单周期随机 K 线 DataFrame | czsc/mock.py:33 → 转发 wbt.mock.mock_symbol_kline |
wbt.mock |
generate_klines_with_weights |
生成带权重列的多标的 K 线(用于 WeightBacktest 打样) | czsc/mock.py:61 → 转发 wbt.mock.mock_weights |
wbt.mock |
使用方式:
from czsc.mock import generate_symbol_kines
df = generate_symbol_kines("000001", "30分钟", "20240101", "20240601")20. 环境变量(envs)
定义于 czsc/envs.py,通过 czsc.envs 子模块暴露。
| API | 用途 | 实现位置 | 内部依赖 |
|---|---|---|---|
get_verbose |
获取是否启用详细日志(CZSC_VERBOSE) |
czsc/envs.py:35 |
无 |
get_min_bi_len |
获取笔最小长度(CZSC_MIN_BI_LEN,默认 6) |
czsc/envs.py:40 |
无 |
get_max_bi_num |
获取最大笔数量(CZSC_MAX_BI_NUM,默认 50) |
czsc/envs.py:46 |
无 |
21. 包元信息
| API | 用途 | 实现位置 |
|---|---|---|
__version__ |
包版本号(唯一来源:Cargo.toml [workspace.package].version) |
czsc/__init__.py:140 |
__author__ |
作者 | czsc/__init__.py:144 |
__email__ |
邮箱 | czsc/__init__.py:145 |
__date__ |
发布日期 | czsc/__init__.py:146 |
welcome |
打印版本号、随机格言、缓存提示 | czsc/__init__.py:258 |
附录 A:预加载子包一览
通过 czsc/__init__.py 顶层 import 的子包,用户可直接 czsc.xxx 访问:
| 子包 | 职责 |
|---|---|
czsc.connectors |
数据源连接器(天勤/Tushare/CCXT/本地缓存) |
czsc.envs |
环境变量读取(verbose/min_bi_len/max_bi_num) |
czsc.traders |
交易器与信号管理 facade |
czsc.utils |
通用工具(缓存/IO/分析/交易/绘图/优化) |
czsc.fsa |
飞书 API 集成 |
czsc.aphorism |
缠中说禅格言(czsc.aphorism.print_one()) |
czsc.mock |
模拟数据生成 |
附录 B:信号函数(`czsc._native.signals`)
信号函数由 Rust 实现,通过 PyO3 暴露为 czsc._native.signals.*。Python 端有 7 个子模块:
| 子模块 | 说明 |
|---|---|
czsc._native.signals.bar |
基于 K 线形态的信号 |
czsc._native.signals.cvolp |
量价关系信号 |
czsc._native.signals.cxt |
基于缠论结构(分型/笔/中枢)的信号 |
czsc._native.signals.obv |
OBV 指标信号 |
czsc._native.signals.pressure |
压力/支撑信号 |
czsc._native.signals.tas |
技术分析指标信号(MACD/KDJ/RSI/BOLL 等) |
czsc._native.signals.vol |
成交量信号 |
底层 Rust 源文件位于 crates/czsc-signals/src/(22 个 .rs 文件),信号通过 #[signal] 宏自动注册到 SIGNAL_REGISTRY。
使用方式:信号函数一般不直接调用,而是通过 signals_config 配置交给 generate_czsc_signals / CzscTrader / CzscSignals 使用。查询可用信号请参考 signal-functions skill 或执行 ls crates/czsc-signals/src/。
附录 C:数据模型(`czsc.models`)
定义于 czsc/models.py,用于策略研究流程的返回值容器。
| 类型 | 用途 | 实现位置 |
|---|---|---|
StrategyConfig |
策略配置 TypedDict(类型标注用) | czsc/models.py:22 |
ResearchResult |
研究/回测结果容器(含 Arrow 字节流,提供 signals_df() / pairs_df() / holds_df() 方法) |
czsc/models.py:52 |
ReplayResult |
单标的回放结果容器(结构同 ResearchResult) |
czsc/models.py:96 |
OptimizeResult |
参数优化结果容器(含 message 字段) |
czsc/models.py:109 |
Release Checklist (`docs/release_checklist.md`)
版本发布前的检查清单
适用范围:czsc(Python wheel via PyPI + Rust crates via crates.io,共用 git tag
v<X.Y.Z>)。
目的:在 push tag 之前堵住 CI 覆盖不到的质量风险与隐藏缺陷。
CI 已强制项(版本一致性、stub 漂移、ruff/cargo fmt、clippy-D warnings、py3.10–3.13 pytest)不在本清单重复。
1. 代码冻结前(提交期)
- 所有面向用户的 PR 已合并、关闭或显式延期;
master当前 commit 即是要发布的内容 - CHANGELOG 已更新:列出本次 release 的 breaking changes、新增 API、修复项;Rust 端如有签名变更必须显式标出
- 公开 API 改名/删除已有迁移说明:放在
docs/migration/或 CHANGELOG,避免下游升级炸裂
代码 vs Markdown 文档一致性(硬性检查)
**原则:以代码为准。**任何不一致都改文档,不要反向去改代码迁就文档措辞。
检查范围与方法:
-
README.md:安装命令、最小示例、API 调用片段、特性列表都能跑通;版本徽章/截图同步 -
CLAUDE.md:模块路径、目录结构描述与环境变量名均与当前代码一致 -
docs/examples/*.py:每个示例真实可运行(至少python -cimport 不报错);删除的示例同步从docs/examples.md索引里删掉 -
docs/migration/:历史迁移说明里引用的旧 API/路径,与当前实际"已删除/已改名"的事实对齐 - 公开 docstring:
czsc/顶层导出对象、crates/*/src/lib.rs公开 item 的 docstring 中的代码示例可执行 - API 文档(飞书 / readthedocs)链接的有效性:本次若有大规模重构,外链文档需手动确认未误导
快速定位漂移点的命令:
# 1) 找 md 里引用的 czsc.xxx 符号,对照实际是否存在
rg -n 'czsc\.[a-zA-Z_][a-zA-Z0-9_.]*' README.md CLAUDE.md docs/ \
| awk -F'czsc\\.' '{print $2}' | awk '{print $1}' | sort -u \
> /tmp/md_refs.txt
uv run python -c "
import czsc, sys
refs = [l.strip().rstrip('.,)`\"') for l in open('/tmp/md_refs.txt') if l.strip()]
for r in refs:
root = r.split('.')[0].rstrip('()')
if root and not hasattr(czsc, root):
print('MISSING in czsc:', r)
"
# 2) 确认面向用户文档没有引用已删除的模块路径
rg -n 'czsc\.svc|czsc\.signals\.|czsc\.core\b' README.md docs/ \
&& echo "::error:: 上述命中均为已删除模块路径,需从文档中清理"
# 3) 校验案例索引与 docs/examples/ 实际文件一致
python - <<'PY'
from pathlib import Path
import re
files = {p.name for p in Path("docs/examples").glob("*.py")}
listed = set(re.findall(r"\]\(\./examples/([^)]*\.py)\)", Path("docs/examples.md").read_text()))
for name in sorted(files ^ listed):
print("MISSING example index entry:", name)
PY- 以上三段命令均无输出(或所有命中都已修正)
-
docs/examples/与docs/examples.md索引一致:文件删除/新增都同步索引
2. 版本号(单一版本源 = `Cargo.toml [workspace.package].version`)
-
Cargo.toml [workspace.package].version已 bump 到目标版本 - 未硬编码
pyproject.toml的version,仍为dynamic = ["version"](硬编码会被crates/czsc-python/build.rs编译期拒绝) - 计划打的 git tag 是
v<Cargo.toml version>(CI 会做三方校验:tag == Cargo == wheel filename)
语义化版本(SemVer)规则
本次发布的明确例外(2026-10-15):维护者指定 1.0.2,并保留 #368 的 34 项破坏性信号键迁移,
不提供旧键别名或双协议。CHANGELOG 与 docs/signal_contracts.md 必须保留迁移警告,不能将本次
称为兼容升级;该例外不修改今后的一般版本规则,也不豁免任何功能、安全、产物或上传授权门禁。
格式:MAJOR.MINOR.PATCH(如 1.2.3),三段含义对应"兼容性合同":
| 段位 | 何时 +1 | 典型场景 | 同时清零 |
|---|---|---|---|
| MAJOR | 破坏向后兼容 | 删除/重命名公开 API、改函数签名、改字段语义、调整信号默认参数、Rust 类型签名变更 | MINOR、PATCH → 0 |
| MINOR | 向后兼容地新增 | 新信号函数、新公开 API、新可选参数、新示例 | PATCH → 0 |
| PATCH | 向后兼容地修 bug | 算法修正、性能优化、文档/类型提示修正、内部重构(不影响外部行为) | — |
判定决策树:
- 用户升级后会报错或行为变化吗?→ MAJOR
- 用户能用上新东西且老代码不动也跑得通吗?→ MINOR
- 只是修了已有功能的 bug,外部接口与行为不变?→ PATCH
补充约束:
- 任意 breaking change(即使再小)必须 bump MAJOR,且在 CHANGELOG 顶部用
### Breaking Changes单独成段 - 不允许"小版本里夹 breaking"——这是下游升级最容易翻车的反模式
- 预发布用
1.2.0-rc.1/1.2.0-beta.1(PyPI 与 crates.io 都支持),tag 同步打v1.2.0-rc.1 -
0.x.y阶段(如果未来有):MINOR 段位的语义可视作 MAJOR——即0.3.0 → 0.4.0允许带 breaking;本项目当前已稳定在1.x,不适用 - Rust crates 与 Python wheel 共享同一版本号(已由 CI 强校验),不允许某一侧单独 bump
3. 本地 Rust 验证(CI 覆盖盲点)
CI 因 pyo3/extension-module 限制,只对 5 个纯 Rust crate 跑 cargo test。czsc-signals / czsc-trader / czsc-python 的 Rust 测试只能本地跑:
-
cargo build --workspace --release通过 -
cargo test -p czsc-derive -p czsc-core -p czsc-utils -p czsc-ta -p czsc-signal-macros通过(与 CI 同步) -
cargo clippy --workspace --all-targets -- -D warnings通过 -
cargo fmt --all -- --check通过 - stub 已重新生成并 commit:bash
PYO3_PYTHON=$(uv run python -c 'import sys; print(sys.executable)') \ cargo run --bin stub_gen -p czsc-python --no-default-features --features stub-gen git diff --exit-code czsc/_native/__init__.pyi # 必须无差异
4. 本地 Python 验证
-
uv sync --extra all(仅在依赖变更后跑一次)+uv run maturin develop --release构建本地扩展 - 全量测试含 slow 用例(CI 默认跳过
@pytest.mark.slow,发布前必须显式跑):bashuv run --no-sync pytest --run-slow -
uv run --no-sync pytest --cov=czsc覆盖率无显著回退 -
uv run --no-sync ruff format --check czsc/ tests/&uv run --no-sync ruff check czsc/ tests/通过 - 冒烟 import 顺序无 side-effect:bash
uv run python -c "import czsc; print(czsc.__version__)" uv run python -c "from czsc import CZSC, RawBar, Freq, BarGenerator, CzscTrader; print('core OK')" uv run python -c "from czsc._native import signals; print(len(dir(signals)))" uv run python -c "from czsc._native import ta; print(sorted(dir(ta))[:5])" -
czsc.__version__输出 == 目标版本(验证importlib.metadata注入正确)
5. 本地 Wheel 验证(覆盖 CI smoke 盲区)
CI smoke 仅覆盖 Linux x86_64 / macOS x86_64 / macOS arm64 / Windows x64。Linux aarch64 与 musllinux 必须本地验证:
-
uv run maturin build --release --strip产物可被pip install --no-deps安装并import czsc成功 - 如本次有触及 PyO3 边界(参数 / 返回值序列化、Arrow IPC、GIL release):用 docker
--platform linux/arm64或 alpine 镜像本地拉一遍 wheel 安装 + import - Wheel 文件名版本符合
czsc-<X.Y.Z>-cp310-abi3-.whl(abi3 单 wheel 覆盖 3.10–3.13)
6. 发布动作(按顺序)
任何一步失败都先排查再前进,不要靠重发覆盖。tag 一旦发布到 PyPI 不可重用。
- TestPyPI 演练(强烈推荐,尤其是 breaking 版本):
- Actions →
Build & Publish Python Package→workflow_dispatch→publish_to_testpypi=true - 在干净虚拟环境
pip install -i https://test.pypi.org/simple/ czsc==<X.Y.Z>实测
- Actions →
- Rust crates dry-run(push tag 时自动跑,可提前 workflow_dispatch 验证):
- Actions →
Publish Rust crates→ 默认 dry-run;查看日志确认cargo publish --workspace --dry-run通过
- Actions →
- Push tag 触发 PyPI 真发:bash
git tag v<X.Y.Z> git push origin v<X.Y.Z>- CI 自动:build wheels → sdist → smoke-test → 版本三方校验 → PyPI 发布 → GitHub Release + sigstore 签名
- Rust crates 真发:tag push 不会自动真发,需手动:
- Actions →
Publish Rust crates→workflow_dispatch→do_publish=true - 若中途撞 crates.io rate-limit(新 crate ~1/10min),用
start_layer/end_layer断点续发
- Actions →
- PyPI 与 crates.io 任一发布失败 → 不要急着 yank,先在 Issue 记录、修复后 bump 一个 patch 版本重发(已上传不可覆盖)
7. 发布后验证
- PyPI 页面可见目标版本,wheel 平台齐全(6 平台 + sdist):https://pypi.org/project/czsc/
- crates.io 页面可见目标版本,全部 6 个 crate 都 OK(czsc-derive / czsc-signal-macros / czsc-core / czsc-ta / czsc-utils / czsc-signals / czsc-trader / czsc)
- GitHub Release 已创建,包含 wheel + sdist + sigstore 签名文件
- 干净环境冒烟:bash
pip install czsc==<X.Y.Z> python -c "import czsc; print(czsc.__version__)" -
cargo add czsc@=<X.Y.Z>在干净 Rust 项目里能cargo check真编通过(注意:仅"可解析"不够 —— 1.0.0-rc.8 发版时踩过坑,下游 cargo add 后解析成功但 cargo check 失败。完整命令:常见失败:(a) SemVer prerelease 解析:crates.io 同时有bashTMPD=$(mktemp -d); cd "$TMPD" cargo init --name release_smoke --quiet cargo add 'czsc@=<X.Y.Z>' --quiet cargo check 2>&1 | tail -20 # 必须 0 错误 cargo tree -i polars-core 2>&1 | head -10 # 必须单一版本,无 0.42/0.52 双版本冲突 cd - && rm -rf "$TMPD"1.0.0stable 与1.0.0-rc.*时,cargo 会优先选 stable,造成依赖图错位 —— 必须用=严格匹配 +cargo yank误发 stable;(b) pyo3 / pyo3-stub-gen / numpy 被无条件依赖 —— 纯 Rust 用户也被强制拉 Python 工具链,撞上游兼容性 bug,必须 feature-gate。详见 §8 雷达表) - 至少跑一个最小 CZSC + 信号配置的 demo(如
docs/examples/13_lightweight_charts_html.py)确认运行时无回归
8. 隐藏缺陷雷达(按历史踩坑频度排序)
| 风险 | 触发条件 | 防御 |
|---|---|---|
| Stub 漂移 | 改了 #[gen_stub_*] 但忘了重跑 stub_gen |
§3 最后一项 + CI stub-drift job |
| 版本三方不一致 | 只 bump 了 Cargo 没 push tag、或反之 | §2 + CI verify_version_consistency |
pyproject.toml 误改 version |
手抖去掉 dynamic |
build.rs 编译期校验 |
| Slow 测试漏跑 | 平时 CI 不跑 --run-slow |
§4 强制本地跑 |
czsc-signals / czsc-trader Rust 端回归 |
CI 不能 cargo test 这两个 crate |
§3 + Python pytest 端到端覆盖 |
| ARM/musllinux wheel 加载失败 | CI smoke 不覆盖 | §5 本地 docker 验证 |
| Rust crates 漏发 | tag push 只触发 dry-run,真发要手动 dispatch | §6 step 4 强提醒 |
| crates.io rate-limit | 新 crate 创建 ~1/10min | start_layer/end_layer 断点续发 |
| PyPI 已存在版本无法覆盖 | 发布失败后误以为可重发 | §6 step 5:bump patch 重发,不要 yank |
| 下游升级炸裂 | breaking change 没写迁移说明 | §1 CHANGELOG + docs/migration/ |
| SemVer prerelease 被 stable 抢解析 | crates.io 同时有 1.0.0 与 1.0.0-rc.*,下游 cargo add czsc 会解析到 stable 而非 rc,导致依赖图错位(rc.8 实战遇到) |
必须 workspace.dependencies 全用 version = "=<X.Y.Z>" 严格匹配;误发的 stable 必须 cargo yank;§7 cargo check ratchet 抓得到 |
| pyo3 / pyo3-stub-gen / numpy 无条件硬依赖 | 纯 Rust 下游被强制拉 Python 工具链,撞上游兼容性 bug 编不出(rc.8 实战遇到) | czsc-core / czsc-utils / czsc-signals / czsc-trader 把 pyo3 系列 dep 改 optional = true + [features] python = [...];§7 cargo check ratchet 抓得到 |
9. 一键自检脚本(可选)
把 §3 + §4 + §5 的本地命令串成一个脚本,在 push tag 前跑一遍:
set -e
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo build --workspace --release
cargo test -p czsc-derive -p czsc-core -p czsc-utils -p czsc-ta -p czsc-signal-macros
PYO3_PYTHON=$(uv run python -c 'import sys; print(sys.executable)') \
cargo run --bin stub_gen -p czsc-python --no-default-features --features stub-gen
git diff --exit-code czsc/_native/__init__.pyi
uv run maturin develop --release
uv run --no-sync ruff format --check czsc/ tests/
uv run --no-sync ruff check czsc/ tests/
uv run --no-sync pytest --run-slow
uv run python -c "import czsc; print('version:', czsc.__version__)"
# §7 cargo check ratchet(rc.8 教训)—— 模拟下游纯 Rust 用户拉发布产物
# 注意:本地 cargo check --workspace 会因 path = "..." 短路,看不到
# crates.io 上的版本解析问题;必须在干净项目里 cargo add 才能复现。
# 这一段建议在 PR push tag **之后**、cargo publish dispatch **之前**
# 跑一次(用 dry-run 产物或 TestPyPI/crates.io-staging 镜像)。
echo "✅ 本地预检全部通过"全绿之后再做 §6 的发布动作。
Release Security Review (`docs/release_security_review.md`)
1.0.2 发布安全处置记录
维护者已选择 1.0.2 并保留新信号键。本次只同步项目版本和内部精确依赖,不回退下列安全修复或
变更第三方解析图。下文是此前 2.0.0 候选阶段对同一依赖图的审核记录;历史证据不是新 1.0.2
产物验证或全库安全认证,新版本仍须重新构建、安装和运行检查。
本轮针对审核 head f7e5cc6 的 30 项 open Dependabot 告警逐项处置。
来源为只读 gh api --paginate 'repos/waditu/czsc/dependabot/alerts?state=open&per_page=100';漏洞条件与修复阈值来自各项 security_advisory / security_vulnerability。
共 1 critical、13 high、13 medium、3 low。该快照不是全库安全认证;GitHub 默认分支告警不会因草稿 PR 的锁文件更新自动关闭,本任务不 dismiss 告警。
每项影响与处理
“升级锁”只表示该 PR 解析图排除了本次审核涉及的漏洞版本,不表示已复现利用、用户旧环境已修复或所有平台产物已验证。版本分支由 Python marker 决定。
| 告警/级别 | GHSA | 影响及触发条件 | 版本与可达路径 | 处理 / 残余风险编号 |
|---|---|---|---|---|
| #93 low | GHSA-6g2r-675j-hx59 | 短自定义 xxh3 secret 可能越界 | xxhash-rust:0.8.15 → 0.8.16;native/Polars 传递依赖 | 升级至首个修复版本;R1 |
| #92 high | GHSA-8988-9cw3-xx77 | HTTPS 代理可能忽略 TLS 配置 | urllib3:2.7.0 → 2.8.0;运行时 requests,以及发布工具 | 升级锁并声明 urllib3>=2.8.0;R3 |
| #91 medium | GHSA-3jqq-pw4j-pqcj | 恶意语言包导致前端 XSS | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #90 medium | GHSA-3325-v43h-43rv | 扩展卸载参数注入与文件/URL 泄漏 | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #89 high | GHSA-6966-vjj6-99xv | 剪贴板单元格触发 XSS | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #88 high | GHSA-6966-vjj6-99xv | 剪贴板单元格触发 XSS | notebook:7.5.0 → 7.6.3;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #87 high | GHSA-c2m8-h5v5-343r | 静态文件符号链接越界读取 | tornado:6.5.7 → 6.5.10;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #86 medium | GHSA-3hv7-mjh2-fv65 | 无限查询参数阻塞事件循环 | tornado:6.5.7 → 6.5.10;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #85 high | GHSA-chx6-46f5-w4vp | 解压响应缺少大小限制导致 OOM | tornado:6.5.7 → 6.5.10;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #84 medium | GHSA-gh4c-6fx4-qh6g | Deflate 分块流可能无限循环 | urllib3:2.7.0 → 2.8.0;运行时 requests,以及发布工具 | 升级锁并声明 urllib3>=2.8.0;R3 |
| #83 high | GHSA-jwv3-5hgf-82ww | 重复自签中间证书导致指数路径搜索 | cryptography:48.0.1 → 50.0.2;Linux release/all extra:Twine→keyring→secretstorage | 升级锁;R4 |
| #82 medium | GHSA-m2h6-j472-rp4c | 通配 DNS 名可逃逸证书子树约束 | cryptography:48.0.1 → 50.0.2;Linux release/all extra:Twine→keyring→secretstorage | 升级锁;R4 |
| #81 medium | GHSA-5p39-cfhj-2xmp | 进程池 stderr 不排空导致永久阻塞 | anyio:4.10.0 → 4.14.2/4.15.1;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #80 critical | GHSA-82r6-8w77-94w6 | 非 ASCII 域名的旧 IDNA 编码可能接受攻击者证书 | anyio:4.10.0 → 4.14.2/4.15.1;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #79 medium | GHSA-gjv8-xp57-g29c | 恶意 CSS selector 正则二次复杂度 | soupsieve:2.8.4 → 2.10/3.0.3;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #78 medium | GHSA-j934-xhv5-fg8f | 合法 selector 尾部空白/注释触发 ReDoS | soupsieve:2.8.4 → 2.10/3.0.3;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #77 high | GHSA-mpf4-983q-p7j4 | 表单字段无限解析阻塞事件循环 | tornado:6.5.7 → 6.5.10;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #76 high | GHSA-6m44-fpc8-c3rq | 过多 Markdown emphasis 导致递归异常/DoS | mistune:3.3.0 → 3.3.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #75 medium | GHSA-8423-8fgw-73vq | multipart split 临时列表导致内存放大 | tornado:6.5.7 → 6.5.10;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #74 low | GHSA-wwv5-g3v4-889x | cookie 属性注入修复可被旧 kwargs 路径绕过 | tornado:6.5.7 → 6.5.10;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #73 high | GHSA-g6cj-pr64-35w5 | PKCS#7 解密错误/时序可形成 padding oracle | cryptography:48.0.1 → 50.0.2;Linux release/all extra:Twine→keyring→secretstorage | 升级锁;R4 |
| #72 high | GHSA-4w2j-m93h-cj5j | 乱序流重组无界内存导致远程耗尽 | quinn-proto:0.11.14 → 0.11.15;锁内,默认 normal tree 不活动 | 升级至首个修复版本;R2 |
| #71 low | GHSA-whvh-wf3x-g77j | 直接安装扩展可绕过允许/禁止列表 | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #70 high | GHSA-gx64-gj6p-pc4c | 恶意图片在新标签触发 XSS | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #69 high | GHSA-pppj-hq3g-57pj | 恶意 settings 文件触发 XSS | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #68 medium | GHSA-h5v5-8746-g7mm | PluginManager 锁规则可绕过 | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #67 medium | GHSA-89vp-jrxv-24w8 | 扩展 blocklist 包名规范化可绕过 | jupyterlab:4.5.9 → 4.6.4;Jupyter 开发依赖 / dev、all extra | 升级锁与必要兼容依赖;R4 |
| #66 medium | GHSA-h35f-9h28-mq5c | Unicode 规范化可绕过 sdist MANIFEST 排除 | setuptools:80.9.0 → 已移除;旧构建/开发传递依赖 | 移出锁图,maturin 构建不依赖它;R5 |
| #52 medium | GHSA-chgr-c6px-7xpp | Python closure 缺 Sync 约束可能跨线程竞态 | pyo3:0.28.3 → 0.29.3;native 实际依赖 | 升级 PyO3 及 NumPy/stub 兼容链;R1 |
| #51 high | GHSA-36hh-v3qg-5jq4 | list/tuple nth/nth_back 溢出导致越界读 | pyo3:0.28.3 → 0.29.3;native 实际依赖 | 升级 PyO3 及 NumPy/stub 兼容链;R1 |
残余风险和接受边界
- R1:PyO3 位于 native 路径,更新至 0.29.3;NumPy Rust binding 0.29.0、stub generator 0.23.1 是匹配升级,编译与回归必须重新执行。项目直接调用中未找到 Python list/tuple
nth/nth_back或PyCFunction::new_closure,不是第三方依赖不可达证明。xxhash 的短 secret 攻击未做压力/内存安全复现;以修复版本及产物重建处置,不靠未发现直接调用豁免。 - R2:
cargo tree --workspace -e normal -i quinn-proto --locked默认图不活动;未来开启 HTTP/3/相关 feature 可能改变可达性。因此仍升级到 0.11.15,不仅记录“当前没用”。未做远程 DoS 攻击复现。 - R3:urllib3 位于正常 requests 路径,新增 wheel 安装下限
urllib3>=2.8.0,不能只升级开发锁。依赖解析与安装检查不代表部署代理/TLS 配置正确,须部署方核查。 - R4:AnyIO critical 与其他 Jupyter 告警已按锁升级;JupyterLab 4.6.4 是 Notebook 7.6.3 的兼容要求,不是无关升级。cryptography 在 Linux 发布工具 extra,经 Twine/keyring/secretstorage 可达,不是正常 wheel native 库。没有运行恶意 notebook、域名、证书、HTTP/Markdown 压力复现。开发/发布环境应按该锁重建;用户自选旧版 extras 不会被 wheel 强制升级,须固定安全基线并限制不可信文档、扩展和输入。
- R5:setuptools 已从本次锁图移除;原生构建后端仍为 maturin。用户外部构建工具/系统环境中的 setuptools 不在本 PR 处置范围,尤其 macOS sdist 路径仍应核查其版本。
本 PR 没有将 CodeQL 成功当作依赖安全通过。tests/test_release_dependencies.py 为这次处置的版本阈值和 urllib3 安装下限加了防回退守卫;未来新告警仍需重新审查,不能复用本表作为永久豁免。
可执行追溯
python -m pytest tests/test_release_dependencies.py -q
cargo tree --workspace --all-features -e normal -i pyo3 --locked
cargo tree --workspace -e normal -i quinn-proto --locked
cargo tree --workspace -e normal -i xxhash-rust --locked
uv tree --universal --locked --invert --package anyio
uv tree --universal --locked --invert --package cryptography
uv tree --universal --locked --invert --package urllib3跨平台重建、平台漏洞行为验证、线上利用路径、上传凭据有效性仍不可由版本守卫证明;剩余发布门禁见 docs/release_validation.md。正式版/RC、发布范围和风险接受须维护者确认。
Release Validation (`docs/release_validation.md`)
1.0.2 发布审核补证
当前版本与验收边界(2026-10-15)
维护者明确选择 1.0.2,保留 #368 的 34 项输出键迁移及新键契约,不增加别名或双协议,
不再推进兼容改造或 2.0.0 发布。CHANGELOG 保留破坏性警告;该范围决定不是合并、tag 或上传授权。
版本源、八个内部精确约束和九个工作区锁包必须统一 1.0.2,Python 版本仍由 maturin 动态读取。uv.lock 的项目为 dynamic/editable,没有待替换的项目版本;第三方包的 2.0.0 不是项目版本。
REPO_ROOT="$PWD"
python -m pytest tests/test_release_dependencies.py .github/scripts/tests/test_release_wheel_smoke.py -q
uvx maturin build --release --locked --offline --interpreter "$PYTHON" --out target/release-1.0.2-dist
uvx maturin sdist --out target/release-1.0.2-dist
mkdir -p target/release-1.0.2-smoke-cwd
(cd target/release-1.0.2-smoke-cwd && "$PYTHON" "$REPO_ROOT/.github/scripts/smoke_release_wheel.py" \
--expected-version 1.0.2 --expected-machine arm64 --wheel-dir "$REPO_ROOT/target/release-1.0.2-dist")构建后必须在新 venv 完整安装实际 1.0.2 wheel,并在仓库外运行 smoke;上面的 $PYTHON 对安装和
检查命令应指向该环境,不能从源码目录导入旧 native 冒充新产物。检查版本/架构/来源/native 字节、
RawBar/CZSC、SMA、八个时间精度组合及实际新信号键/配置往返、旧键拒绝,不放宽原功能门禁。
原六平台 × Python 3.10–3.13 的 24 组合保持完整在线/no-cache 安装、pip check 和所有上传 needs;
只无上传演练 publish_to_testpypi=false,新 producer SHA/产物哈希及一次执行快照随任务评论交付,
没有结果的项目明确未验证,不轮询等待外部 CI。
历史 2.0.0 候选记录(以下至文末均非新版本通过证明)
下文保留此前源码、命令、版本、文件名、哈希及局部运行缺口的原貌,不改名复用历史产物。
其后独立复审已验收 eeeb731 / run 38615098709 的六平台与 24/24 组合,三个上传 job 均 skipped;
该结论只属于原 2.0.0 产物。未变化的算法/安全图证据可按边界保留,不能替代 1.0.2 的重建和运行。
本次验证对应 PR #369 的审核修复内容,不等于发布批准。版本源仍为 Cargo workspace;正式版/RC、范围和上传授权未确认。禁止把以下本地通过替代六平台产物、线上 registry 安装或上传权限证据。
修复与可复现回归
- 时间精度:输入/merge 键/输出统一
datetime64[ns],覆盖 s/ms/us/ns 和交易日/自然日。没有改笔/分型算法。 - 测试隔离:测试包导入不再写进程配置;默认值测试的环境清理通过 monkeypatch 恢复。没有调整收集顺序、刷新快照或为通过而设置笔长 6。
- 安全处置:30 项逐项见
release_security_review.md;版本阈值与普通安装 urllib3 下限有防回退守卫。 - 红灯证据:初始组合 11 failed / 6 passed,锁版本守卫 12 failed;环境恢复测试 1 failed、urllib3 安装下限测试 1 failed。
- 绿灯证据:首组修复 45 passed;原样核心/环境/安全组合两种顺序各 37 passed。顺序变换只用于证明隔离,不是修复手段。
PYTHON=.venv/bin/python
"$PYTHON" -m pytest tests/unit/test_core_parity.py -q
"$PYTHON" -m pytest tests/test_testing_configuration.py tests/test_release_dependencies.py \
tests/unit/test_core_parity.py tests/test_envs.py -q
"$PYTHON" -m pytest tests/test_envs.py tests/unit/test_core_parity.py \
tests/test_release_dependencies.py tests/test_testing_configuration.py -q
"$PYTHON" -m pytest tests/test_trade_utils.py tests/unit/test_resample_bars.py -q分别使用 pandas 2.3.2 与 pandas 3.0.6 的 Python 3.11.15 环境,不把降级 pandas 当作修复。两套完整回归均为 546 passed / 1 deselected(分别 18.00s / 27.38s),执行命令如下;数量有交叉,不累加:
"$PYTHON" -m pytest tests --run-slow --import-mode=importlib \
-k 'not test_wheel_install_in_clean_venv' -q唯一未执行项是在线 pip clean-install 测试;独立环境离线安装、native import、依赖检查另行取证,不把离线安装等同在线无约束 resolver 验证。pandas 2 环境检查 53 个包、pandas 3 环境检查 41 个包,均报告 All installed packages are compatible。完整新 wheel 的本地结果和 SHA256 以本轮交付日志为准。
构建、typing 和 Rust 产物
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --exclude czsc-python --locked
PYO3_PYTHON="$("$PYTHON" -c 'import sys; print(sys.executable)')" \
cargo test -p czsc-core --features python --test test_python_enum_extraction --locked
PYO3_PYTHON="$("$PYTHON" -c 'import sys; print(sys.executable)')" \
cargo run --locked --bin stub_gen -p czsc-python --no-default-features --features stub-gen
git diff --exit-code -- czsc/_native/__init__.pyi tests/unit/snapshots/
uvx maturin build --release --locked --interpreter "$PYTHON"
uvx maturin sdist --out dist
cargo package --workspace --exclude czsc-python --allow-dirty --locked --offline- 已验证 Rust 273 passed / 1 ignored,额外 Python enum 提取契约 2 passed;fmt/Clippy 通过。
- 新 stub generator 已执行,stub 与原提交字节一致。debug stub_gen 的 macOS 链接器报告
__eh_frame超过 compact unwind 16MB 限制;没有屏蔽告警,异常展开性能未测,不能将其称为 release 运行失败或宣称性能无影响。 - 八个可发布 crate 均实际
Packaged和Verifying成功;没有使用--no-verify,不只编译 workspace path 依赖。Cargo 在本地临时 registry 中解析本次=2.0.0依赖。 - 额外纯 Rust 消费者使用
czsc = "=2.0.0",无 path dependency、无[patch]、无 Python feature。将八个.crate与cargo vendor --locked --offline --versioned-dirs的第三方源放入隔离 source mirror,生成 archive/file SHA256 校验后,cargo run --offline成功,czsc::Freq::F1实际运行;normal tree 没有 pyo3/numpy。 - 此消费者验证的是本次归档在离线镜像里的行为,不是正式 crates.io 安装;2.0.0 尚未上传,不能制造线上验证证据。
Wheel 内容与独立环境验证
"$PYTHON" - <<'PY'
from pathlib import Path
from zipfile import ZipFile
from email import message_from_bytes
import hashlib
wheels = list(Path('dist').glob('czsc-*.whl'))
assert wheels, 'No release wheel found'
for wheel in wheels:
with ZipFile(wheel) as archive:
names = archive.namelist()
metadata = message_from_bytes(archive.read(next(name for name in names if name.endswith('/METADATA'))))
assert metadata['Version'] == '2.0.0'
assert 'urllib3>=2.8.0' in metadata.get_all('Requires-Dist', [])
payload = [name for name in names if name.startswith('czsc/') and name.endswith(('.py', '.pyi', 'py.typed'))]
assert all(archive.read(name) == Path(name).read_bytes() for name in payload)
assert any(name.startswith('czsc/_native') and name.endswith(('.so', '.pyd')) for name in names)
assert 'czsc/py.typed' in names and 'czsc/_native/__init__.pyi' in names
assert any(name.endswith('/LICENSE') for name in names)
print(wheel.name, len(payload), hashlib.sha256(wheel.read_bytes()).hexdigest())
PY普通安装与本地源测试是两种证据。先在全新 venv 安装完整依赖快照与新 wheel,在非仓库目录执行 native import 和 uv pip check,再执行对应环境的 pandas 回归;避免从仓库导入旧 .so 冒充新产物。
本轮重新构建的 czsc-2.0.0-cp310-abi3-macosx_11_0_arm64.whl SHA256 为 93b89636192d89a3dcb49fce6f4bdf6fa22ccddfa603f4d2fa278a8ed25d180f;97 个 Python/stub/typing payload 与修复源码逐字节一致。native 使用升级后的 PyO3/NumPy 重新编译,不复用审核前产物。wheel 在修复提交前的 worktree 构建,源码一致性已验证,但不能将其描述为 CI 的 clean-checkout 产物。
在线无约束 pip resolver 与所有 Python/平台组合尚不能由离线依赖快照证明。原 test_wheel_install_in_clean_venv 的在线安装项若未执行,必须明确列出,不得把 deselected 写成 passed。
未验证门禁(不得标绿)
| 项目 | 本次证据 / 剩余动作 |
|---|---|
| macOS arm64 / Python 3.11 | 本机 release wheel、metadata/源码/stub/native 与独立环境验证;详见本轮日志 |
| Linux x86_64、Linux arm64、musllinux、macOS Intel、Windows wheel | 本机未验证;需收集同一 head 的六平台 workflow 产物与真实运行环境 smoke |
| Python 3.10/3.12/3.13 | 本机未重新运行;PR CI 是开发构建回归,不能替代对应 release wheel 验证 |
| ARM/musllinux/macOS Intel import smoke | 已给现有 workflow 增加原生 ARM/Intel runner 与 Alpine 运行检查;配置不等于执行通过,本机 Docker daemon 不可用,结果仍须单独收集 |
| 官方 crates.io 下游解析/安装 | 未发布,尚不可验证;离线镜像验证不是线上证据 |
| PyPI Trusted Publisher / crates.io token | 仅知道 repo environment / secret 名存在,未验证有效性,不读取 secret 值 |
| TestPyPI/正式上传与发布后验收 | 无授权,未触发;正式版/RC 和范围须维护者确认 |
安全构建演练可以在同一新 head 的分支上使用现有 workflow 的 publish_to_testpypi=false;它仅构建与测试,上传 job 条件不成立,仍必须核对 run 输入/事件与结果:
gh workflow run python-publish.yml --repo waditu/czsc \
--ref agent/codex/SKZ-964-release-2.0.0 -f publish_to_testpypi=false不要改成 true 或推 v* tag 来获取证据:前者会上传 TestPyPI,后者会自动上传正式 PyPI。Rust 真正发布需要授权后在同一 tag dispatch do_publish=true。本任务不合并、不打 tag、不上传包。
补充 release wheel 运行检查
现有 python-publish.yml 的 smoke 已扩展至六个平台 × Python 3.10/3.11/3.12/3.13(24 个组合),不是重新运行全套算法回归:
- manylinux x86_64、aarch64 分别在 x86_64、
ubuntu-24.04-arm原生 runner 执行;macOS Intel 在macos-15-intel,arm64 在 Apple Silicon,Windows 在 x64 runner 执行。 - musllinux x86_64 在对应 Python 版本的 Alpine Docker 环境执行,不能用 glibc Linux 或交叉编译结果替代实际加载。
- 每个组合创建新 venv,从公开 PyPI 在线解析完整运行时依赖,安装同一 run 的 release wheel,执行
pip check;取消原最小依赖/no-deps smoke。依赖若缺少该平台发行包或编译环境,必须失败并记录,不能跳过依赖伪装普通安装成功。 - 在仓库之外执行
.github/scripts/smoke_release_wheel.py;校验版本、runner 架构、模块属于目标 venv、native 二进制与下载 wheel 字节一致,并实际运行 RawBar/CZSC、Rust SMA 和八种时间精度组合。 - 两个 smoke job 仅有 contents/actions read 权限,没有上传步骤或 OIDC write。原 PyPI/TestPyPI 上传条件不变,两个上传 job 新增 musl smoke 为依赖;演练必须继续显式
publish_to_testpypi=false。 - 新脚本与门禁回归使用独立安装环境执行:
python -m pytest .github/scripts/tests/test_release_wheel_smoke.py -q。原始红灯 5 failed,修复后 5 passed。此工具回归不混入先前 546 项算法回归计数。
当前配置的静态检查、本机在线安装与实际 CI wheel 执行日志随任务评论交付。远程 24 组合是否通过只能由新 head 的 job 执行结果证明;dispatch 成功或 queued 不算运行通过。未完成时只记录快照,不轮询等待外部 CI,也不申请发布批准。
Windows 编码与 musl 安装环境补证(2026-10-15)
已完成演练的实际结果
run 38607249078 对应bba2c0802931db51c43e523c19d2ee33f6ab4a7c,已结束且失败,不是仍在等待。
六个平台 wheel 和 sdist 构建成功;24 个运行组合为 16 success / 8 failure。
manylinux x86_64/aarch64、macOS Intel/arm64 的 Python 3.10–3.13 全部完成完整在线无缓存安装、pip check 和 wheel/native/功能检查。Windows 四组合在 Cargo 版本读取阶段失败;
musl 四组合在 wbt 源包的构建准备阶段失败,均尚未执行 native smoke。
PyPI、TestPyPI、GitHub Release 三个 job 均 skipped。该远程证据更新前文的本地未验证记录,
但不把历史产物或旧 SHA 的结果改写成修复 head 的结果。
最小修复与回归
- 两个 smoke 版本命令显式使用 UTF-8 读取 Cargo.toml;不设置全局 UTF-8/locale 环境绕开缺陷。
参数化回归直接执行 workflow 中的两个真实 Python 命令,在默认编码 cp1252 下先复现UnicodeDecodeError(2 failed),然后验证显式 UTF-8(2 passed)。 - musl 容器在创建新 venv 前安装
build-base libgcc libstdc++ rustup pkgconf openssl-dev,
用rustup-init -y --profile minimal --default-toolchain stable显式准备 musl Rust 工具链,
把$HOME/.cargo/bin加入 PATH,并打印 Alpine、APK、rustc 和 Cargo 版本。
不再依赖 Python 构建后端隐式安装 Rust;仅补 libgcc 不足以构建 wbt。 - 以
CARGO_BUILD_JOBS=2限制源码编译并行度,musl job 预算由 20 分钟改为 60 分钟,
为 Polars 及 wbt 的 release 编译预留资源。这不是运行成功证明,仍可能内存不足或超时。
不改变 wbt 的 LTO/profile,不更改 Python 依赖约束,不跳过完整安装,不删除平台或放宽上传 needs。 - 环境配置回归先因缺少 APK 前置安装失败(1 failed),补齐后通过;8 项工具回归包含
编码、环境顺序、原完整安装/上传约束和本地既有已安装 wheel 的正反例。
静态配置断言不能代替真正的 Alpine 编译、在线依赖解析及 native 加载。
runner 补齐与用户分发要求不同
核对公开 PyPI 的 wbt 0.9.2 文件清单及校验 SHA256 的 sdist:目前无 musllinux wheel,
sdist 使用 maturin、Rust edition 2024、PyO3 0.28 与 Polars 0.53,Cargo.lock 无 git source。
所以普通 Alpine 完整安装会构建 wbt 源包,需要可用的 Rust/C/C++ 工具链、头文件、链接器、
运行库、网络以及足够 CPU/内存/时间;CZSC 的 abi3 musllinux wheel 本身不提供这些依赖。
APK 的 rustup 包提供 rustup-init 并依赖 libgcc,可由
Alpine 包清单 核对。
容器日志中的实际 Alpine/工具链版本才是本次执行环境,不假定浮动标签永远不变。
不能把 PyArrow 一概归为源码构建阻塞:本次 PyPI 清单中,25.0.1 有 Python 3.10 的
musllinux_1_2_x86_64 wheel,26.0.0 有 3.11–3.13 对应 wheel;旧 3.11 失败日志也已选中
PyArrow 的 musllinux wheel。因此此修复不安装或绑定发行版 Arrow SDK。
若将来 resolver 选到只有 sdist 的 PyArrow,则需匹配的 Arrow C++ SDK、CMake 等,
参见 Arrow 源码构建说明;届时必须重新验证,
不能使用 distro Python 包或 no-deps 假装满足普通 pip 安装。
维护者需决定是否接受“musl 完整安装需要构建 wbt 源码”的用户要求。建议协调 wbt 上游补齐
musllinux wheel,以降低用户编译成本;该跨仓库构建/上传不属于本任务,也未获得授权。
本轮仅补 runner 基线,不调整依赖或支持范围。若完整安装仍失败,应继续记录实际缺口,
由维护者选择补充工具/资源、上游 wheel 分发或其他明确方案;不能自行删掉 musl 门禁。
证据复用与新 SHA 范围
本次不修改 CZSC native/Python 运行源码、依赖图、wheel 构建参数或功能 smoke 脚本。
既有算法、Rust、归档、安全审核和旧 head 的 16 个成功组合可作为历史证据复用,
但新 run 的 wheel 必须标明其自身 producer SHA,不能标记旧产物为新产物。
重点复核 Windows/musl × Python 3.10–3.13 八组合。由于共享版本命令也影响另外四平台,
沿用现有完整 24 组合发布 smoke 演练、重建同一 head 产物,不另造只跑子集却能发布的通道;
这会再次运行短 smoke,不重复已充分验证的全套算法测试。
新演练显式 publish_to_testpypi=false,只取一次快照,不等待/轮询外部 CI。
Windows 本机不可用,Docker daemon 不可用,未将本地编码/静态回归宣称为这八组合已通过。
最终复审需核对实际修复 head、八组合安装/native 结果、其余平台共享命令结果以及所有上传
仍 skipped。正式版/RC、最终发布范围、维护者审批、旧 extras 等残余风险接受和上传权限
继续独立待确认;无上传演练通过也不是发布授权。
Signal Contracts (`docs/signal_contracts.md`)
信号输出与注册模板契约
注册模板的版本标记是信号契约的一部分,不能以历史 Python 无后缀输出为理由删除。
本次恢复 34 个原始注册模板,并逐项修正实际 k3 与之完全一致;不是对所有输出
机械拼接函数名。bar_channel_V230508 使用 通道V230508,三法、分手线保留
完整八位版本。其他已一致的模板(包括 BS辅助221211)不改变标记形式。
宏同时校验函数名/注册名一致性,以及 k3 末尾完整连续 ASCII 数字串与注册版本
完全相等(不能用更长数字串包含相同尾部)。本次仅升级目标 k3,不改变 v1/v2/v3
业务分类值;普通十字线仍输出分类 十字线。
旧键迁移(破坏性变更)
本迁移纳入维护者于 2026-10-15 明确选择的 1.0.2,未撤回迁移、增加旧键别名或双协议。
版本号仍为 1.x 不代表旧策略可直接升级;以下迁移与历史数据重建要求保持有效。
函数注册名、调用参数和公开 API 不变。旧无版本键及通道旧版本键不提供静默别名,
反解析不再接受这些旧键,旧策略条件也不会匹配新输出。请更新 Event/Position/策略的signals_all、signals_any、signals_not、保存的信号字符串、过滤器及缓存;需要
历史回测对齐时重新生成历史信号和快照。以下占位符仍按原参数取值。
| 注册名 | 旧 k3 | 新 k3 |
|---|---|---|
bar_operate_span_V221111 |
时间区间 |
时间区间V221111 |
bar_mean_amount_V221112 |
{th1}至{th2}千万 |
{th1}至{th2}千万V221112 |
bar_zdf_V221203 |
{t1}至{t2} |
{t1}至{t2}V221203 |
bar_amount_acc_V230214 |
累计超{t}千万 |
累计超{t}千万V230214 |
bar_single_V230214 |
状态 |
状态V230214 |
bar_big_solid_V230215 |
MID |
MIDV230215 |
bar_section_momentum_V221112 |
阈值{th}BP |
阈值{th}BPV221112 |
bar_vol_bs1_V230224 |
BS1辅助 |
BS1辅助V230224 |
bar_fake_break_V230204 |
假突破 |
假突破V230204 |
bar_channel_V230508 |
通道V230507 |
通道V230508 |
byi_symmetry_zs_V221107 |
对称中枢 |
对称中枢V221107 |
coo_td_V221110 |
TD |
TDV221110 |
cxt_fx_power_V221107 |
分型强弱 |
分型强弱V221107 |
cxt_first_buy_V221126 |
BUY1 |
BUY1V221126 |
cxt_first_sell_V221126 |
SELL1 |
SELL1V221126 |
jcc_san_xing_xian_V221023 |
伞形线 |
伞形线V221023 |
jcc_ten_mo_V221028 |
吞没形态 |
吞没形态V221028 |
jcc_wu_yun_gai_ding_V221101 |
乌云盖顶 |
乌云盖顶V221101 |
jcc_ci_tou_V221101 |
刺透形态 |
刺透形态V221101 |
jcc_san_fa_V20221118 |
三法A |
三法AV20221118 |
jcc_san_fa_V20221115 |
三法 |
三法V20221115 |
jcc_xing_xian_V221118 |
星形线 |
星形线V221118 |
jcc_fen_shou_xian_V20221113 |
分手线 |
分手线V20221113 |
jcc_zhu_huo_xian_V221027 |
烛火线 |
烛火线V221027 |
jcc_yun_xian_V221118 |
孕线 |
孕线V221118 |
jcc_ping_tou_V221113 |
平头形态 |
平头形态V221113 |
jcc_two_crow_V221108 |
两只乌鸦 |
两只乌鸦V221108 |
jcc_three_crow_V221108 |
三只乌鸦 |
三只乌鸦V221108 |
jcc_szx_V221111 |
十字线 |
十字线V221111 |
jcc_san_szx_V221122 |
三星 |
三星V221122 |
jcc_fan_ji_xian_V221121 |
反击线 |
反击线V221121 |
jcc_shan_chun_V221121 |
山川形态 |
山川形态V221121 |
jcc_gap_yin_yang_V221121 |
并列阴阳 |
并列阴阳V221121 |
jcc_ta_xing_V221124 |
塔形 |
塔形V221124 |
参数与验证范围
反解析对整个模板做占位符匹配,不再要求 k3 与模板第三段字面相等。
模板参数不一定直接等于调用参数:时间区间的 t1/t2 还原为 HHMM 字符串,
涨跌幅的 t1/t2 合并为 span,伞形线的 th 还原其 100 倍编码。
逆变换失败的候选继续匹配其他模板,不能提前终止整个注册表搜索。
当前注册模板中,相邻占位符只有 bar_zdf_V221203 的 {di}{mode},
以及 tas_ma_base_V221101、tas_ma_base_V221203、tas_second_bs_V230303 的 {di}{ma_type}。共享解析器要求相邻参数中的di 为完整非负整数前缀,不能将多位数字的尾部放入 mode/ma_type。
非空数据测试覆盖 di=10/11/100、ZF/DF,以及三个 MA 信号的 SMA/EMA/WMA
参数精确还原和完整信号键/值往返;其中二买信号不代表其笔形态分支已覆盖。
回归测试覆盖这 34 个信号的默认/空 K 线键、模板实例化和键往返,
四个动态 k3 的默认/自定义参数还原,以及全量 descriptor 的名称、opcode
唯一性与注册表映射。它不代表所有指标计算分支均已得到验证。
分类子模块待决事项
docs/public_api.md 附录 B 当前只承诺 bar/cxt/tas/vol/pressure/obv/cvolp
七个 Python 子模块,与现有 dispatcher 创建的模块一致。byi/coo/jcc/xl/zdy
等其他分类是否应新增分类导入路径,尚未确认属于当前支持范围;本次不扩展
API,也不将分类模块缺口等同于信号未注册。
Superpowers Plans 2026 06 02 Czsc Cli (`docs/superpowers/plans/2026-06-02-czsc-cli.md`)
czsc CLI 模块实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 为 czsc 库新增一个用 typer 写的统一 CLI(入口 czsc),把信号目录、缠论分析、策略回测、研究、可视化、性能基准等能力暴露成子命令,人机两用、--json LLM 友好、schema 自发现。
Architecture: 新建 czsc/cli/ 子包,按命令组拆文件;_io.py 收口输入加载/输出渲染/错误边界/连接器分发。每个命令只做「解析参数 → 调 czsc.* 公共 API → 交 _io.py 渲染」,不写业务/算法逻辑(符合开发宪法)。pyproject.toml 注册 [project.scripts] czsc = "czsc.cli:app"。
Tech Stack: Python 3.10+,typer(+click),pandas,pytest(typer.testing.CliRunner),ruff(line-length 120),uv。
设计文档: 飞书 wiki https://s0cqcxuy3p.feishu.cn/wiki/IoGRwkHxwiiHP8kOWwJc1s85nSb
已验证的 API 契约(实测,实现时直接用)
czsc._native.list_all_signals() -> list[dict](246 项;key:name/param_template/category/namespace)czsc._native.list_signal_names(category=None) -> list[str]czsc._native.get_signal_template(name) -> str、czsc._native.get_signal_category(name) -> strczsc.Freq成员.value为中文(Freq.F30.value == "30分钟",Freq.D.value == "日线"…)czsc.format_standard_kline(df, freq) -> list[RawBar];df 必含dt,symbol,open,close,high,low,vol,amountczsc.mock.generate_symbol_kines(symbol, freq, sdt="20100101", edt="20250101", seed=42) -> df(列同上)CZSC(bars):c.fx_list(FX:symbol,dt,mark,fx,high,low,power_volume,power_str)、c.bi_list(BI:symbol,sdt,edt,direction,high,low,length,power,angle)。无zs_list,中枢不在 v1。czsc.check_kline_quality(df) -> dict[symbol -> dict[check -> {description, rows}]]czsc.get_signals_config(list[str]) -> list[dict]、czsc.get_signals_freqs(list[dict]) -> list[str]czsc.Position:to_json()->str、from_json(str)->Position、save(path)、load_from_file(path)->Position、dump()->dict、unique_signals->list[str]czsc.CzscStrategyBase是抽象类(positions抽象属性)→ CLI 内定义薄子类注入 positionsstrat.backtest(bars) -> ResearchResult;res.holds_df()列dt,pos,price,n1b,symbol,pos_nameczsc.WeightBacktest(w, fee_rate=2e-4).stats -> dict(25 项:年化收益/夏普比率/最大回撤/日胜率…);w列dt,symbol,weight,pricewbt.generate_backtest_report(df, output_path, title=...) -> str(df 列dt,symbol,weight,price)from czsc.utils.plotting.lightweight import plot_czsc, plot_czsc_trader, plot_czsc_signals;plot_czsc(c, output='html', path=None):path=None返回 HTML 字符串,传 path 写文件返回路径;plot_czsc_signals(bars, signals_config=[{'name': '...'}], output='html', path=None)- 连接器(懒加载,import 即可能抛错):
local_data.get_raw_bars(symbol, freq, sdt, edt) -> list[RawBar];ts_connector.get_raw_bars(symbol, freq, sdt, edt) -> list[RawBar];ccxt_connector.get_raw_bars(symbol=, period=, sdt=, edt=) -> DataFrame(列dt,open,high,low,close,vol,amount,无 symbol)
文件结构(先定边界)
| 文件 | 职责 |
|---|---|
czsc/cli/__init__.py |
组装 typer app,add_typer 各子命令组,注册顶层命令 |
czsc/cli/_io.py |
load_bars_df / emit / fail / error_boundary / resolve_bars_for_symbol / freq_to_cn |
czsc/cli/signals.py |
signals list / signals doc |
czsc/cli/analyze.py |
analyze |
czsc/cli/backtest.py |
backtest(_PositionsStrategy + 多 symbol + 连接器 + WeightBacktest + HTML) |
czsc/cli/research.py |
research run / research replay / research config |
czsc/cli/data.py |
data mock / data quality |
czsc/cli/plot.py |
plot czsc / plot trader / plot signals |
czsc/cli/bench.py |
bench |
czsc/cli/schema.py |
schema |
tests/cli/ |
各命令 CliRunner 测试 |
设计偏差说明(相对飞书 spec): --json 改为每条命令的选项(而非全局 callback),因为 typer 全局 callback 选项必须置于子命令前(czsc --json signals list),对 LLM 不友好;改为 czsc signals list --json 更顺手。error_boundary 统一捕获异常。
Task 0: 依赖与入口骨架
Files:
Modify:
pyproject.toml(dependencies 加typer;新增[project.scripts])Create:
czsc/cli/__init__.pyTest:
tests/cli/__init__.py、tests/cli/test_cli_skeleton.pyStep 1: 安装 typer 依赖
编辑 pyproject.toml,在 [project].dependencies 列表末尾("tenacity", 之后)加一行:
"typer>=0.12.0",在 [project.optional-dependencies] 之前(约 line 73 附近,dependencies 数组闭合 ] 之后)新增:
[project.scripts]
czsc = "czsc.cli:app"Run: uv sync --extra dev
Expected: 成功安装 typer。
- Step 2: 写失败的测试
Create tests/cli/__init__.py(空文件)。
Create tests/cli/test_cli_skeleton.py:
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def test_app_help_lists_subcommands():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
for name in ["signals", "analyze", "backtest", "research", "data", "plot", "bench", "schema"]:
assert name in result.output- Step 3: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_cli_skeleton.py -v
Expected: FAIL(ModuleNotFoundError: czsc.cli 或子命令未注册)。
- Step 4: 写最小实现
Create czsc/cli/__init__.py:
"""CZSC 命令行工具入口。
通过 `czsc <子命令>` 调用;所有命令支持 `--json` 输出(LLM 友好)。
"""
from __future__ import annotations
import typer
from czsc.cli import analyze, backtest, bench, data, plot, research, schema, signals
app = typer.Typer(
help="CZSC 缠论技术分析命令行工具",
no_args_is_help=True,
add_completion=False,
)
app.add_typer(signals.app, name="signals", help="信号目录与文档")
app.add_typer(research.app, name="research", help="策略研究 / 回放 / 配置解析")
app.add_typer(data.app, name="data", help="造数与质量校验")
app.add_typer(plot.app, name="plot", help="缠论 / 信号 HTML 可视化")
app.command("analyze", help="对一段 K 线跑缠论,输出分型 + 笔")(analyze.analyze)
app.command("backtest", help="传入 Position 对象 + 数据源,产出回测结果")(backtest.backtest)
app.command("bench", help="CZSC / CzscTrader 吞吐量基准")(bench.bench)
app.command("schema", help="吐出全部命令/参数 schema 供 LLM 自发现")(schema.schema)
if __name__ == "__main__":
app()此 task 依赖后续 task 创建的子模块。为先让骨架测试通过,先创建各子模块的最小占位(后续 task 替换为真实实现)。Create 以下占位文件,每个含一个 typer app 或函数:
czsc/cli/signals.py、czsc/cli/research.py、czsc/cli/data.py、czsc/cli/plot.py:
from __future__ import annotations
import typer
app = typer.Typer(no_args_is_help=True)czsc/cli/analyze.py:
from __future__ import annotations
def analyze() -> None: # 占位,Task 4 替换
...同理 czsc/cli/backtest.py(def backtest(): ...)、czsc/cli/bench.py(def bench(): ...)、czsc/cli/schema.py(def schema(): ...)。
- Step 5: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_cli_skeleton.py -v
Expected: PASS。
- Step 6: Commit
git add pyproject.toml czsc/cli/ tests/cli/
git commit -m "feat(cli): typer 入口骨架 + 子命令占位"Task 1: `_io.py` 共享层
Files:
Create:
czsc/cli/_io.pyTest:
tests/cli/test_io.pyStep 1: 写失败的测试
Create tests/cli/test_io.py:
import json
import pytest
import typer
from czsc.cli import _io
from czsc.mock import generate_symbol_kines
def test_load_bars_df_csv(tmp_path):
df = generate_symbol_kines("000001", "30分钟", "20240101", "20240105")
p = tmp_path / "k.csv"
df.to_csv(p, index=False)
out = _io.load_bars_df(str(p))
for col in ["dt", "symbol", "open", "close", "high", "low", "vol", "amount"]:
assert col in out.columns
def test_load_bars_df_missing_columns(tmp_path):
p = tmp_path / "bad.csv"
p.write_text("a,b\n1,2\n")
with pytest.raises(ValueError, match="缺少必需列"):
_io.load_bars_df(str(p))
def test_emit_json(capsys):
_io.emit({"k": 1}, json_out=True, human=lambda d: None)
out = capsys.readouterr().out
assert json.loads(out) == {"k": 1}
def test_fail_json_raises_exit(capsys):
with pytest.raises(typer.Exit):
_io.fail("boom", json_out=True, err_type="ValueError")
err = capsys.readouterr().err
assert json.loads(err)["error"]["message"] == "boom"
def test_freq_to_cn():
assert _io.freq_to_cn("F30") == "30分钟"
assert _io.freq_to_cn("30分钟") == "30分钟"
assert _io.freq_to_cn("D") == "日线"- Step 2: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_io.py -v
Expected: FAIL(czsc.cli._io 不存在)。
- Step 3: 写实现
Create czsc/cli/_io.py:
/* Detailed source-code truncated for AI context efficiency. */- Step 4: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_io.py -v
Expected: PASS。
- Step 5: Commit
git add czsc/cli/_io.py tests/cli/test_io.py
git commit -m "feat(cli): _io 共享层(加载/渲染/错误边界/连接器分发)"Task 2: `data` 命令组(mock / quality)
Files:
Modify:
czsc/cli/data.pyTest:
tests/cli/test_data.pyStep 1: 写失败的测试
Create tests/cli/test_data.py:
import json
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def test_data_mock_writes_csv(tmp_path):
out = tmp_path / "k.csv"
r = runner.invoke(app, ["data", "mock", "--symbol", "000001", "--freq", "30分钟",
"--sdt", "20240101", "--edt", "20240201", "-o", str(out)])
assert r.exit_code == 0, r.output
assert out.exists()
import pandas as pd
df = pd.read_csv(out)
assert {"dt", "symbol", "open", "close", "high", "low", "vol", "amount"} <= set(df.columns)
def test_data_quality_json(tmp_path):
csv = tmp_path / "k.csv"
from czsc.mock import generate_symbol_kines
generate_symbol_kines("000001", "30分钟", "20240101", "20240201").to_csv(csv, index=False)
r = runner.invoke(app, ["data", "quality", str(csv), "--json"])
assert r.exit_code == 0, r.output
data = json.loads(r.stdout)
assert "000001" in data- Step 2: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_data.py -v
Expected: FAIL(占位 app 无 mock/quality 命令)。
- Step 3: 写实现
Replace czsc/cli/data.py:
/* Detailed source-code truncated for AI context efficiency. */- Step 4: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_data.py -v
Expected: PASS。
- Step 5: Commit
git add czsc/cli/data.py tests/cli/test_data.py
git commit -m "feat(cli): data mock / quality"Task 3: `signals` 命令组(list / doc)
Files:
Modify:
czsc/cli/signals.pyTest:
tests/cli/test_signals.pyStep 1: 写失败的测试
Create tests/cli/test_signals.py:
import json
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def test_signals_list_json_has_entries():
r = runner.invoke(app, ["signals", "list", "--json"])
assert r.exit_code == 0, r.output
data = json.loads(r.stdout)
assert isinstance(data, list) and len(data) > 100
assert {"name", "param_template", "category", "namespace"} <= set(data[0])
def test_signals_doc_known_name():
# 先取一个真实名字
listing = json.loads(runner.invoke(app, ["signals", "list", "--json"]).stdout)
name = listing[0]["name"]
r = runner.invoke(app, ["signals", "doc", name, "--json"])
assert r.exit_code == 0, r.output
doc = json.loads(r.stdout)
assert doc["name"] == name
assert "param_template" in doc
def test_signals_doc_unknown_name_fails():
r = runner.invoke(app, ["signals", "doc", "不存在的信号xyz", "--json"])
assert r.exit_code != 0
assert json.loads(r.stderr)["error"]["message"]- Step 2: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_signals.py -v
Expected: FAIL。
- Step 3: 写实现
Replace czsc/cli/signals.py:
/* Detailed source-code truncated for AI context efficiency. */- Step 4: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_signals.py -v
Expected: PASS。
- Step 5: Commit
git add czsc/cli/signals.py tests/cli/test_signals.py
git commit -m "feat(cli): signals list / doc"Task 4: `analyze` 命令
Files:
Modify:
czsc/cli/analyze.pyTest:
tests/cli/test_analyze.pyStep 1: 写失败的测试
Create tests/cli/test_analyze.py:
import json
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def _mock_csv(tmp_path):
from czsc.mock import generate_symbol_kines
p = tmp_path / "k.csv"
generate_symbol_kines("000001", "30分钟", "20230101", "20240101").to_csv(p, index=False)
return p
def test_analyze_json_has_fx_and_bi(tmp_path):
p = _mock_csv(tmp_path)
r = runner.invoke(app, ["analyze", str(p), "--freq", "30分钟", "--json"])
assert r.exit_code == 0, r.output
data = json.loads(r.stdout)
assert data["symbol"] == "000001"
assert len(data["fx_list"]) > 0
assert len(data["bi_list"]) > 0
assert {"dt", "mark", "fx"} <= set(data["fx_list"][0])
assert {"sdt", "edt", "direction", "high", "low"} <= set(data["bi_list"][0])- Step 2: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_analyze.py -v
Expected: FAIL。
- Step 3: 写实现
Replace czsc/cli/analyze.py:
/* Detailed source-code truncated for AI context efficiency. */- Step 4: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_analyze.py -v
Expected: PASS。
- Step 5: Commit
git add czsc/cli/analyze.py tests/cli/test_analyze.py
git commit -m "feat(cli): analyze(分型+笔)"Task 5: `backtest` 命令(核心)
Files:
Modify:
czsc/cli/backtest.pyTest:
tests/cli/test_backtest.py、tests/cli/conftest.py(Position fixture)Step 1: 写 Position fixture(测试基础设施)
Create tests/cli/conftest.py:
import pytest
from czsc import Event, Position
@pytest.fixture
def position_file(tmp_path):
"""构造一个 unique_signals 非空的真实 Position,落盘为 JSON 文件,返回路径。"""
oe = Event.load({"operate": "开多", "signals_all": ["30分钟_D1_表里关系V230101_向上_任意_任意_0"]})
xe = Event.load({"operate": "平多", "signals_all": ["30分钟_D1_表里关系V230101_向下_任意_任意_0"]})
pos = Position(symbol="000001", name="表里多头", opens=[oe], exits=[xe],
interval=0, timeout=20, stop_loss=300)
p = tmp_path / "pos.json"
p.write_text(pos.to_json())
return p- Step 2: 写失败的测试
Create tests/cli/test_backtest.py:
import json
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def _bars_csv(tmp_path, symbol="000001"):
from czsc.mock import generate_symbol_kines
p = tmp_path / f"{symbol}.csv"
generate_symbol_kines(symbol, "30分钟", "20200101", "20240101").to_csv(p, index=False)
return p
def test_backtest_with_bars_file_json(tmp_path, position_file):
bars = _bars_csv(tmp_path)
r = runner.invoke(app, ["backtest", str(position_file), "--bars", str(bars), "--json"])
assert r.exit_code == 0, r.output
data = json.loads(r.stdout)
assert "symbols" in data and "portfolio" in data
assert "000001" in data["symbols"]
assert "年化收益" in data["portfolio"]
def test_backtest_html_report(tmp_path, position_file):
bars = _bars_csv(tmp_path)
out = tmp_path / "report.html"
r = runner.invoke(app, ["backtest", str(position_file), "--bars", str(bars),
"--html", str(out), "--json"])
assert r.exit_code == 0, r.output
assert out.exists() and out.stat().st_size > 0
def test_backtest_requires_data_source(position_file):
r = runner.invoke(app, ["backtest", str(position_file), "--json"])
assert r.exit_code != 0
assert json.loads(r.stderr)["error"]["message"]- Step 3: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_backtest.py -v
Expected: FAIL。
- Step 4: 写实现
Replace czsc/cli/backtest.py:
/* Detailed source-code truncated for AI context efficiency. */- Step 5: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_backtest.py -v
Expected: PASS(三个用例)。
- Step 6: Commit
git add czsc/cli/backtest.py tests/cli/test_backtest.py tests/cli/conftest.py
git commit -m "feat(cli): backtest(positions+数据源→逐symbol回测+组合+HTML)"Task 6: `research` 命令组(run / replay / config)
Files:
Modify:
czsc/cli/research.pyTest:
tests/cli/test_research.pyStep 1: 写失败的测试
Create tests/cli/test_research.py:
import json
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def test_research_config_derives(tmp_path):
r = runner.invoke(app, ["research", "config",
"30分钟_D1_表里关系V230101_向上_任意_任意_0", "--json"])
assert r.exit_code == 0, r.output
data = json.loads(r.stdout)
assert "signals_config" in data and "freqs" in data
assert data["freqs"] == ["30分钟"]- Step 2: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_research.py -v
Expected: FAIL。
- Step 3: 写实现
Replace czsc/cli/research.py:
/* Detailed source-code truncated for AI context efficiency. */注:run_replay 的精确签名在 Task 实施时用 uv run --no-sync python -c "import inspect,czsc;print(inspect.signature(czsc.run_replay))" 确认;若 res_path 形参名不同,相应调整关键字。
- Step 4: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_research.py -v
Expected: PASS。
- Step 5: Commit
git add czsc/cli/research.py tests/cli/test_research.py
git commit -m "feat(cli): research run / replay / config"Task 7: `plot` 命令组(czsc / trader / signals)
Files:
Modify:
czsc/cli/plot.pyTest:
tests/cli/test_plot.pyStep 1: 写失败的测试
Create tests/cli/test_plot.py:
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def _bars_csv(tmp_path):
from czsc.mock import generate_symbol_kines
p = tmp_path / "k.csv"
generate_symbol_kines("000001", "30分钟", "20230101", "20240101").to_csv(p, index=False)
return p
def test_plot_czsc_writes_html(tmp_path):
p = _bars_csv(tmp_path)
out = tmp_path / "c.html"
r = runner.invoke(app, ["plot", "czsc", str(p), "--freq", "30分钟", "-o", str(out)])
assert r.exit_code == 0, r.output
assert out.exists() and out.stat().st_size > 0
def test_plot_signals_writes_html(tmp_path):
p = _bars_csv(tmp_path)
cfg = tmp_path / "cfg.json"
cfg.write_text('[{"name": "30分钟_D1_表里关系V230101_向上_任意_任意_0"}]')
out = tmp_path / "s.html"
r = runner.invoke(app, ["plot", "signals", str(p), "--signals-config", str(cfg),
"--freq", "30分钟", "-o", str(out)])
assert r.exit_code == 0, r.output
assert out.exists() and out.stat().st_size > 0- Step 2: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_plot.py -v
Expected: FAIL。
- Step 3: 写实现
Replace czsc/cli/plot.py:
/* Detailed source-code truncated for AI context efficiency. */注:plot trader 需要构造 CzscTrader 对象(依赖 strategy + bars),实现复杂度高于本计划其它命令,v1 先实现 plot czsc 与 plot signals(覆盖案例 2 单周期 + 案例 3);plot trader(案例 2 多周期)作为后续增量,实现时参考 docs/examples/13_lightweight_charts_html.py 构造 trader 后调 plot_czsc_trader。
- Step 4: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_plot.py -v
Expected: PASS。
- Step 5: Commit
git add czsc/cli/plot.py tests/cli/test_plot.py
git commit -m "feat(cli): plot czsc / signals(案例 2、3)"Task 8: `bench` 命令
Files:
Modify:
czsc/cli/bench.pyTest:
tests/cli/test_bench.pyStep 1: 先确认 examples/17 的可复用入口
Run: uv run --no-sync python -c "import ast,sys; print(open('docs/examples/17_perf_benchmark.py').read()[:2000])"
目标:确认其有无可直接 import 的函数;若无(纯 __main__ 脚本),则在 bench 命令内内联最小版:mock 生成 N 年 K 线 → 计时 CZSC(bars) 构造 + 逐根 c.update(bar) → 输出 bars/s。
- Step 2: 写失败的测试
Create tests/cli/test_bench.py:
import json
import pytest
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
@pytest.mark.slow
def test_bench_json_reports_throughput():
r = runner.invoke(app, ["bench", "--years", "1", "--freq", "30分钟", "--json"])
assert r.exit_code == 0, r.output
data = json.loads(r.stdout)
assert data["czsc_construct"]["bars_per_sec"] > 0- Step 3: 跑测试,确认 fail(带 --run-slow)
Run: uv run --no-sync pytest tests/cli/test_bench.py -v --run-slow
Expected: FAIL。
- Step 4: 写实现
Replace czsc/cli/bench.py:
/* Detailed source-code truncated for AI context efficiency. */- Step 5: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_bench.py -v --run-slow
Expected: PASS。
- Step 6: Commit
git add czsc/cli/bench.py tests/cli/test_bench.py
git commit -m "feat(cli): bench(CZSC 吞吐量基准,案例 4)"Task 9: `schema` 元命令
Files:
Modify:
czsc/cli/schema.pyTest:
tests/cli/test_schema.pyStep 1: 写失败的测试
Create tests/cli/test_schema.py:
import json
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def test_schema_json_lists_commands():
r = runner.invoke(app, ["schema", "--json"])
assert r.exit_code == 0, r.output
data = json.loads(r.stdout)
cmds = {c["command"] for c in data}
assert "czsc signals list" in cmds
assert "czsc backtest" in cmds
entry = next(c for c in data if c["command"] == "czsc signals list")
assert any(p["name"] == "json_out" or p.get("opts") for p in entry["params"]) or entry["params"]- Step 2: 跑测试,确认 fail
Run: uv run --no-sync pytest tests/cli/test_schema.py -v
Expected: FAIL。
- Step 3: 写实现
Replace czsc/cli/schema.py:
/* Detailed source-code truncated for AI context efficiency. */- Step 4: 跑测试,确认 pass
Run: uv run --no-sync pytest tests/cli/test_schema.py -v
Expected: PASS。
- Step 5: Commit
git add czsc/cli/schema.py tests/cli/test_schema.py
git commit -m "feat(cli): schema 元命令(LLM 自发现)"Task 10: 全量验收(acceptance)
Files:
Create:
tests/cli/test_acceptance.pyStep 1: 写端到端验收测试
Create tests/cli/test_acceptance.py:
"""端到端验收:模拟 LLM 用 schema 发现 → 调用核心命令全链路。"""
import json
from typer.testing import CliRunner
from czsc.cli import app
runner = CliRunner()
def test_schema_then_call_chain(tmp_path, position_file):
# 1. schema 自发现
schema = json.loads(runner.invoke(app, ["schema", "--json"]).stdout)
cmds = {c["command"] for c in schema}
assert {"czsc signals list", "czsc analyze", "czsc backtest", "czsc data mock"} <= cmds
# 2. 造数
csv = tmp_path / "k.csv"
r = runner.invoke(app, ["data", "mock", "--symbol", "000001", "--freq", "30分钟",
"--sdt", "20200101", "--edt", "20240101", "-o", str(csv)])
assert r.exit_code == 0
# 3. analyze
a = json.loads(runner.invoke(app, ["analyze", str(csv), "--freq", "30分钟", "--json"]).stdout)
assert a["fx_list"] and a["bi_list"]
# 4. backtest
b = json.loads(runner.invoke(app, ["backtest", str(position_file), "--bars", str(csv), "--json"]).stdout)
assert "年化收益" in b["portfolio"]- Step 2: 跑全套 CLI 测试
Run: uv run --no-sync pytest tests/cli/ -v
Expected: 全部 PASS(bench 默认跳过)。
- Step 3: 跑全仓回归 + ruff
Run:
uv run --no-sync pytest -q
uv run --no-sync ruff check czsc/cli/ tests/cli/
uv run --no-sync ruff format --check czsc/cli/ tests/cli/Expected: 测试无回归;ruff 无报错(有则 ruff format czsc/cli/ tests/cli/ 修复后重跑)。
- Step 4: 验证 console_scripts 真入口
Run:
uv run --no-sync python -c "from czsc.cli import app; import typer; typer.main.get_command(app)"
uv run --no-sync czsc schema --json | python -c "import sys,json; print(len(json.load(sys.stdin)), 'commands')"Expected: czsc 命令可用,schema 输出 ≥ 9 条命令。
- Step 5: 慢测试全量
Run: uv run --no-sync pytest tests/cli/ --run-slow -q
Expected: 含 bench 全 PASS。
- Step 6: Commit
git add tests/cli/test_acceptance.py
git commit -m "test(cli): 端到端验收(schema 发现 → 全链路调用)"自检(plan reviewer 已内联修正)
- spec 覆盖:signals list/doc(T3)、analyze(T4)、backtest+案例1(T5)、research run/replay/config(T6)、data mock/quality(T2)、plot czsc/signals+案例2/3(T7,
plot trader标注为后续增量)、bench+案例4(T8)、schema(T9)、打包入口(T0)、I/O 约定(T1)、验收(T10)。✅ - placeholder 扫描:每步均有真实代码与精确命令;
run_replay签名、examples/17 入口标注为「实施时用一行命令确认」,非 placeholder(给了确认命令与兜底方案)。 - 类型一致性:
_io.emit/fail/error_boundary/load_bars_df/freq_to_cn/resolve_bars_for_symbol在 T1 定义,后续 task 一致引用;_PositionsStrategy仅 T5 内部使用。
已知后续增量(非 v1 阻塞)
plot trader(案例 2 多周期联立):需构造CzscTrader,参考docs/examples/13_lightweight_charts_html.py。- backtest 多 position 权重聚合口径目前为等权 mean;如需市值/自定义加权,后续加
--weight-agg选项。 --symbol真实连接器(tushare/ccxt)需各自 token/网络,CI 用--bars文件路径覆盖;连接器路径用@pytest.mark.slow+ 真实凭证在本地验证。
--- METRICS ---
- Files Extracted: 18
- Estimated Token Budget: ~26393 tokens
- Canonical Reference: https://codewiki.google/github.com/waditu/czsc