# Technical Documentation: waditu/czsc

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/waditu/czsc)

### Overview (`README.md`)

# czsc - 缠中说禅技术分析工具

[](https://pepy.tech/project/czsc)
[](https://pepy.tech/project/czsc)
[](https://pepy.tech/project/czsc)
[](https://www.python.org/downloads/)
[](https://pypi.org/project/czsc/)

**[项目文档](https://s0cqcxuy3p.feishu.cn/wiki/wikcn3gB1MKl3ClpLnboHM1QgKf)** |
**[投研数据共享](https://s0cqcxuy3p.feishu.cn/wiki/wikcnzuPawXtBB7Cj7mqlYZxpDh)** |
**[信号函数编写规范](https://s0cqcxuy3p.feishu.cn/wiki/wikcnCFLLTNGbr2THqo7KtWfBkd)** |
**[DEVIN生成的文档](https://deepwiki.com/waditu/czsc/1-overview)**

<table>
  <tr>
    <td align="center">
  <strong>胜可知 · 大模型量化策略研究平台（内测）</strong><br>
  <sub>使用大模型辅助量化策略研究，当前重点支持时序量价择时策略</sub><br><br>
  <a href="https://quant.shengkezhi.com/"><img alt="立即注册 · 领取 500 元额度" src="https://img.shields.io/static/v1?label=%E7%AB%8B%E5%8D%B3%E6%B3%A8%E5%86%8C&message=%E9%A2%86%E5%8F%96%20500%20%E5%85%83%E9%A2%9D%E5%BA%A6&color=2ea44f&style=for-the-badge"></a><br>
  <sub>邀请码：<code>5862EDBH2H9CDTGZ</code></sub><br><br>
  <a href="https://docs.shengkezhi.com/new-user-roadmap">新手路线</a> · <a href="https://docs.shengkezhi.com/research-methodology">研究方法</a> · <a href="https://s0cqcxuy3p.feishu.cn/minutes/obcn6m8m44515zvn24ltmu3y">使用案例</a> · <a href="https://www.shengkezhi.com/">产品介绍</a>
    </td>
  </tr>
</table>

> **1.0.X 版本开始，缠论核心算法（分型、笔、中枢等）已全部迁移到 Rust 实现，通过 PyO3 扩展（`czsc._native`）暴露给 Python。** 需要了解旧 Python 实现逻辑的，可查看 [0.9.X](https://github.com/waditu/czsc/tree/v0.9.69) 版本。

> [czsc_skills](https://github.com/zengbin93/czsc_skills)

> 源于[缠中说缠博客](http://blog.sina.com.cn/chzhshch)，原始博客中的内容不太完整，且没有评论，以下是网友整理的原文备份

* 备份网址1：http://www.fxgan.com
* 备份网址2：https://chzhshch.blog

* 已经开始用czsc库进行量化研究的朋友，欢迎[加入飞书群](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=0bak668e-7617-452c-b935-94d2c209e6cf)，快点击加入吧！
* [B站视频教程合集（持续更新...）](https://space.bilibili.com/243682308/channel/series)


## 架构概览

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 安装预编译版本（推荐）：

```bash
pip install czsc -U
```

使用 `uv` 安装（推荐开发环境）：

```bash
uv pip install czsc
```

从源码构建（需要 Rust 工具链和 maturin）：

```bash
# 安装 Rust：https://rustup.rs/
# 安装 maturin
pip install maturin

# 克隆并构建
git clone https://github.com/waditu/czsc.git
cd czsc
maturin develop --release
```

> **Rust 构建环境约束**：底层依赖 `pyo3` / `pyo3-stub-gen` 0.22 要求 Python ≥ 3.10。
> 当系统默认 Python 低于 3.10 时，请通过环境变量显式指定：
>
> ```bash
> export PYO3_PYTHON=$(which python3.12)   # 或任意 3.10+ 的解释器
> ```
>
> 否则 `cargo build` / `cargo test` 会在 `crates/czsc-python/build.rs` 提前 panic 并给出修复建议。
> 用 `uv sync --extra dev` 走 UV 流程时，UV 会自动选择项目声明的 Python，不需要额外设置。


## 快速开始

### 核心缠论分析

```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 线合成与多级别分析

```python
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分钟']
```

### 信号生成

```python
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)
```

### 权重回测

```python
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)  # 回测统计汇总
```

### 策略研究

```python
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/')
```

### 缠论可视化与回测报告

```python
# 缠论 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 版本使用说明和案例](https://s0cqcxuy3p.feishu.cn/wiki/X0mAwSZyqiuebek2EUacbyPMnDd)。

| 序号 | 案例 | 一句话介绍 |
|------|------|------------|
| 1 | [基于 Event 的策略回测 + wbt HTML 报告](https://s0cqcxuy3p.feishu.cn/wiki/Li7ewsbReiQlWokEEVpcvLjznJU) | 5 行命令跑通 Event → Position → 回测 → 可交互 HTML 报告的完整闭环。 |
| 2 | [lightweight_charts 缠论可视化](https://s0cqcxuy3p.feishu.cn/wiki/VF7Gwq3N8iuplckG1Tmcmp1Qn3d) | 几行代码把缠论结构（分型/笔/线段/中枢）导出为自包含离线 HTML，便于分享与嵌入。 |
| 3 | [把信号函数画到 K 线主图（plot_czsc_signals）](https://s0cqcxuy3p.feishu.cn/wiki/ULxhwFyVkiavqhkZ2Xkc0N3fnYc) | 把信号触发点叠在 K 线主图上，直观调试 signals_config 与多周期联立。 |
| 4 | [性能基准测试：在本地复现 CZSC / CzscTrader 吞吐量](https://s0cqcxuy3p.feishu.cn/wiki/ReRfw9WUjiVgEckZbMxcT87WnRc) | 一行命令本地评估吞吐量，配套关键指标速查表，用于容量规划与加速效果核对。 |
| 5 | [信号函数模块深入介绍](https://s0cqcxuy3p.feishu.cn/wiki/WxU8wKTBViekKbkCyKzcwa7SnJd) | 系统讲解 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`](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）与信号-事件-交易体系，其余环节通过这三个项目解耦协同。


## 开发环境搭建

```bash
# 使用 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](https://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)》，然后在 [issues](https://github.com/waditu/czsc/issues) 中报告 Bug


## 缠论精华

> 学了本ID的理论，去再看其他的理论，就可以更清楚地看到其缺陷与毛病，因此，广泛地去看不同的理论，不仅不影响本ID理论的学习，更能明白本ID理论之所以与其他理论不同的根本之处。

> 为什么要去了解其他理论，就是这些理论操作者的行为模式，将构成以后我们猎杀的对象，他们操作模式的缺陷，就是以后猎杀他们的最好武器，这就如同学独孤九剑，必须学会发现所有派别招数的缺陷，这也是本ID理论学习中一个极为关键的步骤。

> 真正的预测，就是不测而测。所有预测的基础，就是分类，把所有可能的情况进行完全分类。有人可能说，分类以后，把不可能的排除，最后一个结果就是精确的。
> 这是脑子锈了的想法，任何的排除，等价于一次预测，每排除一个分类，按概率的乘法原则，就使得最后的所谓精确变得越不精确，最后还是逃不掉概率的套子。
> 对于预测分类的唯一正确原则就是不进行任何排除，而是要严格分清每种情况的边界条件。任何的分类，其实都等价于一个分段函数，就是要把这分段函数的边界条件确定清楚。
> 边界条件分段后，就要确定一旦发生哪种情况就如何操作，也就是把操作也同样给分段化了。然后，把所有情况交给市场本身，让市场自己去当下选择。
> 所有的操作，其实都是根据不同分段边界的一个结果，只是每个人的分段边界不同而已。因此，问题不是去预测什么，而是确定分段边界。


## 原文整理

* [缠中说禅重新编排版《论语》（整理版）](https://blog.csdn.net/baidu_25764509/article/details/109517775)
* [缠中说禅交易指南](https://blog.csdn.net/baidu_25764509/article/details/109598229)
* [缠中说禅技术原理](https://blog.csdn.net/baidu_25764509/article/details/109597255)
* [缠中说禅图解分析示范](https://blog.csdn.net/baidu_25764509/article/details/110195063)
* [缠中说禅：缠非缠、禅非禅，枯木龙吟照大千（整理版）](https://blog.csdn.net/baidu_25764509/article/details/110775662)
* [缠中说禅教你打坐（整理版）](https://blog.csdn.net/baidu_25764509/article/details/113735170)


## 资料分享

* 链接：https://pan.baidu.com/s/1RXkP3188F0qu8Yk6CjbxRQ
* 提取码：vhue

---

### Overview (`crates/czsc-core/README.md`)

# czsc-core

> 💡 **大多数用户应优先使用 facade crate [`czsc`](https://crates.io/crates/czsc)**：
> `cargo add czsc` 一行拿到全部公共 API，本 crate 中的核心类型已在 facade
> 顶层 re-export。仅当你**只想要缠论核心算法**、不需要工具/信号/trader 时
> 才单独依赖本 crate。

缠论（CZSC，缠中说禅技术分析理论）核心数据结构与算法的 Rust 实现。

提供分型（`FX`）、笔（`BI`）、中枢（`ZS`）、`CZSC` 分析器等核心类型，以及 K 线
包含关系处理（`remove_include`）、笔识别（`check_bi`）等基础算法。

## 用法

```toml
[dependencies]
czsc-core = "1.0"
```

```rust
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-python` crate 聚合到 `czsc._native` 扩展。

## 项目主页

- 仓库：<https://github.com/waditu/czsc>
- Python 包：[`czsc` on PyPI](https://pypi.org/project/czsc/)

---

### 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`](https://crates.io/crates/czsc-core) /
> [`czsc-utils`](https://crates.io/crates/czsc-utils) /
> [`czsc-trader`](https://crates.io/crates/czsc-trader) 等内部使用，也可被
> 其它需要 anyhow ↔ enum 自动桥接的 Rust 项目复用。

CZSC 工作区的过程式派生宏（procedural derive macros）。

当前提供 `#[derive(CZSCErrorDerive)]`：为枚举错误类型自动实现

- `From<anyhow::Error>` —— 把任意 `anyhow::Error` 装进枚举的 `#[from]` 变体
- `serde::Serialize` —— 让错误结构跨 FFI / API 边界可序列化为字符串

## 用法

```toml
[dependencies]
czsc-derive = "1.0"
anyhow      = "1"
thiserror   = "2"
serde       = { version = "1", features = ["derive"] }
```

```rust
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 依赖名变化。

## 项目主页

- 仓库：<https://github.com/waditu/czsc>

---

### 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`](https://crates.io/crates/czsc-signals) 内部使用；
> 仅当你想为自己的 Rust 项目集成 czsc 风格的 inventory 信号注册系统时才
> 需要直接依赖。

[`czsc-signals`](https://crates.io/crates/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`），实际输出必须与模板一致。

```toml
[dependencies]
czsc-signal-macros = "1.0"
czsc-signals       = "1.0"
inventory          = "0.3"
```

```rust
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(/* ... */) -> /* ... */ { /* ... */ }
}
```

## 项目主页

- 仓库：<https://github.com/waditu/czsc>

---

### Overview (`crates/czsc-signals/README.md`)

# czsc-signals

> 💡 **大多数用户应优先使用 facade crate [`czsc`](https://crates.io/crates/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(...)]`](https://crates.io/crates/czsc-signal-macros)
属性宏在编译期自动注册到 inventory，运行期可按字符串配置（如
`"日线_D1#10_看多V240520"`）解析、调用。

## 用法

```toml
[dependencies]
czsc-signals = "1.0"
```

```rust
// 通过 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`](https://crates.io/crates/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 直接零拷贝调用。

## 用法

```toml
[dependencies]
czsc-ta = "1.0"
```

```rust
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）。

## 项目主页

- 仓库：<https://github.com/waditu/czsc>

---

### Overview (`crates/czsc-trader/README.md`)

# czsc-trader

> 💡 **大多数用户应优先使用 facade crate [`czsc`](https://crates.io/crates/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`](https://pypi.org/project/wbt/)
crate 提供，`czsc-trader` 只负责生成信号与持仓权重序列。

## 用法

```toml
[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`](https://crates.io/crates/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 股 / 期货市场交易时段判断

## 用法

```toml
[dependencies]
czsc-utils = "1.0"
```

```rust
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。

## 项目主页

- 仓库：<https://github.com/waditu/czsc>

---

### Overview (`crates/czsc/README.md`)

# czsc

[](https://crates.io/crates/czsc)
[](https://docs.rs/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 同源但接口面不同。

## 用法

```toml
[dependencies]
czsc = "1.0"
```

```rust
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：

```rust
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`](https://crates.io/crates/czsc-core) | 缠论核心数据结构与算法 |
| [`czsc-utils`](https://crates.io/crates/czsc-utils) | BarGenerator / freq_data / trading_time |
| [`czsc-ta`](https://crates.io/crates/czsc-ta) | 技术分析算子 |
| [`czsc-signals`](https://crates.io/crates/czsc-signals) | 30+ 量化信号函数 |
| [`czsc-trader`](https://crates.io/crates/czsc-trader) | 交易引擎与信号编译 |
| [`czsc-derive`](https://crates.io/crates/czsc-derive) | 错误派生宏（内部用） |
| [`czsc-signal-macros`](https://crates.io/crates/czsc-signal-macros) | 信号注册宏（内部用） |

## 项目主页

- 仓库：<https://github.com/waditu/czsc>
- Python 包：<https://pypi.org/project/czsc/>

[`czsc-core`]: https://crates.io/crates/czsc-core
[`czsc-utils`]: https://crates.io/crates/czsc-utils
[`czsc-ta`]: https://crates.io/crates/czsc-ta
[`czsc-signals`]: https://crates.io/crates/czsc-signals
[`czsc-trader`]: https://crates.io/crates/czsc-trader

---

### Overview (`scripts/auto-czsc-quant/README.md`)

# auto-czsc-quant

`auto-czsc-quant` 是一个脚本级实验编排器，用来跑通“大模型改 Position 配置 + CZSC 自动评分”的闭环。

当前实现不改 `czsc` 核心包；它可以读取人工候选 JSONL，也可以调用 `.env` 中配置的 Anthropic 兼容 LLM 生成完整 `position` 候选，然后用 mock、Tushare 或标准 feather 行情完成校验、回测、评分和实验记录。

## 快速运行

从仓库根目录执行：

```bash
PYTHONPATH=scripts/auto-czsc-quant uv run python -m auto_quant.cli run \
  --config scripts/auto-czsc-quant/configs/example.json
```

运行后会生成：

```text
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，一行一个完整候选：

```json
{"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 示例：

```bash
PYTHONPATH=scripts/auto-czsc-quant uv run python -m auto_quant.cli run \
  --config scripts/auto-czsc-quant/configs/example.tushare-llm.json
```

`.env` 中默认读取：

```text
TUSHARE_TOKEN=...
ANTHROPIC_BASE_URL=...
ANTHROPIC_API_KEY=...
ANTHROPIC_MODEL=...
```

## CLI

```bash
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 应当**只减不增**：

```bash
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/`](./examples)。
除明确标注 Tushare 的真实数据案例外，所有脚本仅依赖 `czsc` 顶层包（`czsc.mock` 提供模拟 K 线 / 权重数据，无需外部行情源），可以直接：

```bash
# 普通脚本
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`](./examples/01_quick_start.py) | `czsc.mock.generate_symbol_kines` · `format_standard_kline` · `CZSC` | 数据准备、构造分析对象、增量 update 推进 |
| 02 | [`02_chan_structures.py`](./examples/02_chan_structures.py) | `FX` · `BI` · `ZS` · `Mark` · `Direction` | 分型/笔/中枢的属性、力度（power/SNR/slope/angle）、未完成笔（ubi） |

### 第二组：多级别与信号

| #  | 文件 | 核心 API | 你将学到 |
|----|------|----------|----------|
| 04 | [`04_bar_generator.py`](./examples/04_bar_generator.py) | `BarGenerator` · `freqs_sorted` | 用低周期 K 线实时合成高周期 K 线，构造多级别 CZSC |
| 05 | [`05_signals.py`](./examples/05_signals.py) | `CzscSignals` · `generate_czsc_signals` · `get_signals_config` | 三种信号生成方式 + 反推信号配置/涉及周期 |

### 第三组：事件驱动策略

| #  | 文件 | 核心 API | 你将学到 |
|----|------|----------|----------|
| 06 | [`06_event_position.py`](./examples/06_event_position.py) | `Event` · `Position` · `Operate` · `Event.is_match` | 信号 → 事件（all/any/not 逻辑组合） → 持仓 |
| 07 | [`07_strategy_backtest.py`](./examples/07_strategy_backtest.py) | `CzscStrategyBase` · `tactic.backtest` · `tactic.replay` · `tactic.save_positions` | 完整的策略类定义 + 内存回测 + 落盘回放 + 持仓序列化（产物：`_output/07_strategy_backtest/`） |

### 第四组：回测与离线可视化

| #  | 文件 | 核心 API | 你将学到 |
|----|------|----------|----------|
| 08 | [`08_weight_backtest.py`](./examples/08_weight_backtest.py) | `WeightBacktest` · `daily_performance` · `top_drawdowns` | 时序权重回测的完整链路 + 核心绩效指标 |
| 13 | [`13_event_weight_backtest.py`](./examples/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`](./examples/13_lightweight_charts_html.py) | `plot_czsc` · `plot_czsc_trader` | 缠论 K 线 + 多周期联立，自包含 HTML（无需服务端） |
| 15 | [`15_lightweight_signals_html.py`](./examples/15_lightweight_signals_html.py) | `plot_czsc_signals` | 信号叠加版本，含 signal timeline + tooltip |

### 第六组：真实数据研究（需 `TUSHARE_TOKEN`）

| #  | 文件 | 核心 API | 你将看到 |
|----|------|----------|----------|
| 14 | [`14_tushare_event_backtest.py`](./examples/14_tushare_event_backtest.py) | `get_raw_bars` · `CzscStrategyBase` · `WeightBacktest` | 单标的 30 分钟 Event 策略回测与 HTML 报告 |
| 18 | [`18_tushare_daily_event_universe.py`](./examples/18_tushare_daily_event_universe.py) | `generate_czsc_signals` · `adjust_holding_weights` · `WeightBacktest` | 日线股票池事件选股与 HTML 报告 |

运行真实数据案例前，请设置 `TUSHARE_TOKEN`；缺少令牌时脚本会明确退出，不影响本地 mock 案例验证。

### 第七组：性能基准

| #  | 文件 | 核心 API | 你将看到 |
|----|------|----------|----------|
| 17 | [`17_perf_benchmark.py`](./examples/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 扩展的完整类型声明）
- 信号开发规范：[飞书文档 - 信号函数编写规范](https://s0cqcxuy3p.feishu.cn/wiki/wikcnCFLLTNGbr2THqo7KtWfBkd)
- 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__` 列表

---

## 目录

1. [缠论核心类型（Rust 实现）](#1-缠论核心类型rust-实现)
2. [缠论核心算法函数（Rust 实现）](#2-缠论核心算法函数rust-实现)
3. [交易器与信号管理](#3-交易器与信号管理)
4. [策略门面](#4-策略门面)
5. [研究 / 回测 / 优化入口](#5-研究--回测--优化入口)
6. [数据格式转换](#6-数据格式转换)
7. [技术指标（TA）算子](#7-技术指标ta算子)
8. [探索性数据分析（EDA）](#8-探索性数据分析eda)
9. [回测与绩效（来自 wbt）](#9-回测与绩效来自-wbt)
10. [缓存与数据管理](#10-缓存与数据管理)
11. [IO 工具](#11-io-工具)
12. [交易工具](#12-交易工具)
13. [通用工具函数](#13-通用工具函数)
14. [K 线质量检查](#14-k-线质量检查)
15. [日志与告警工具](#15-日志与告警工具)
16. [可视化](#16-可视化)
17. [飞书集成（fsa）](#17-飞书集成fsa)
18. [数据连接器（connectors）](#18-数据连接器connectors)
19. [模拟数据（mock）](#19-模拟数据mock)
20. [环境变量（envs）](#20-环境变量envs)
21. [包元信息](#21-包元信息)

---

## 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` |

**使用方式**：
```python
from czsc.utils.plotting.lightweight import plot_czsc, plot_czsc_trader, plot_czsc_signals
```

---

## 17. 飞书集成（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` |

**使用方式**：
```python
from czsc.connectors import ts_connector
# 或
from czsc.connectors.ts_connector import get_raw_bars
```

---

## 19. 模拟数据（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` |

**使用方式**：
```python
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 -c` import 不报错）；删除的示例同步从 `docs/examples.md` 索引里删掉
- [ ] **`docs/migration/`**：历史迁移说明里引用的旧 API/路径，与当前实际"已删除/已改名"的事实对齐
- [ ] **公开 docstring**：`czsc/` 顶层导出对象、`crates/*/src/lib.rs` 公开 item 的 docstring 中的代码示例可执行
- [ ] **API 文档（飞书 / readthedocs）链接的有效性**：本次若有大规模重构，外链文档需手动确认未误导

快速定位漂移点的命令：

```bash
# 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 | 算法修正、性能优化、文档/类型提示修正、内部重构（不影响外部行为） | — |

判定决策树：

1. 用户**升级后会报错或行为变化**吗？→ **MAJOR**
2. 用户能**用上新东西**且老代码不动也跑得通吗？→ **MINOR**
3. 只是**修了已有功能的 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`，发布前必须显式跑）：
  ```bash
  uv 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-<plat>.whl`（abi3 单 wheel 覆盖 3.10–3.13）

---

## 6. 发布动作（按顺序）

> 任何一步失败都**先排查再前进**，不要靠重发覆盖。tag 一旦发布到 PyPI 不可重用。

1. [ ] **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>` 实测
2. [ ] **Rust crates dry-run**（push tag 时自动跑，可提前 workflow_dispatch 验证）：
   - Actions → `Publish Rust crates` → 默认 dry-run；查看日志确认 `cargo publish --workspace --dry-run` 通过
3. [ ] **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 签名
4. [ ] **Rust crates 真发**：tag push **不会自动真发**，需手动：
   - Actions → `Publish Rust crates` → `workflow_dispatch` → `do_publish=true`
   - 若中途撞 crates.io rate-limit（新 crate ~1/10min），用 `start_layer` / `end_layer` 断点续发
5. [ ] 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 失败。完整命令：
  ```bash
  TMPD=$(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"
  ```
  常见失败：(a) **SemVer prerelease 解析**：crates.io 同时有 `1.0.0` stable 与 `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 前跑一遍：

```bash
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 安装下限加了防回退守卫；未来新告警仍需重新审查，不能复用本表作为永久豁免。

## 可执行追溯

```bash
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 不是项目版本。

```bash
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。顺序变换只用于证明隔离，不是修复手段。

```bash
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），执行命令如下；数量有交叉，不累加：

```bash
"$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 产物

```bash
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 内容与独立环境验证

```bash
"$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 输入/事件与结果：

```bash
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](https://github.com/waditu/czsc/actions/runs/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 包清单](https://pkgs.alpinelinux.org/package/v3.23/community/x86_64/rustup) 核对。
容器日志中的实际 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 源码构建说明](https://arrow.apache.org/docs/developers/python.html)；届时必须重新验证，
不能使用 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) -> str`
- `czsc.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,amount`
- `czsc.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 内定义薄子类注入 positions
- `strat.backtest(bars) -> ResearchResult`；`res.holds_df()` 列 `dt,pos,price,n1b,symbol,pos_name`
- `czsc.WeightBacktest(w, fee_rate=2e-4).stats -> dict`（25 项：`年化收益/夏普比率/最大回撤/日胜率`…）；`w` 列 `dt,symbol,weight,price`
- `wbt.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__.py`
- Test: `tests/cli/__init__.py`、`tests/cli/test_cli_skeleton.py`

- [ ] **Step 1: 安装 typer 依赖**

编辑 `pyproject.toml`，在 `[project].dependencies` 列表末尾（`"tenacity",` 之后）加一行：
```toml
    "typer>=0.12.0",
```
在 `[project.optional-dependencies]` 之前（约 line 73 附近，dependencies 数组闭合 `]` 之后）新增：
```toml
[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`：
```python
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`：
```python
"""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`：
```python
from __future__ import annotations

import typer

app = typer.Typer(no_args_is_help=True)
```

`czsc/cli/analyze.py`：
```python
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**

```bash
git add pyproject.toml czsc/cli/ tests/cli/
git commit -m "feat(cli): typer 入口骨架 + 子命令占位"
```

---

## Task 1: `_io.py` 共享层

**Files:**
- Create: `czsc/cli/_io.py`
- Test: `tests/cli/test_io.py`

- [ ] **Step 1: 写失败的测试**

Create `tests/cli/test_io.py`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_data.py`

- [ ] **Step 1: 写失败的测试**

Create `tests/cli/test_data.py`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_signals.py`

- [ ] **Step 1: 写失败的测试**

Create `tests/cli/test_signals.py`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_analyze.py`

- [ ] **Step 1: 写失败的测试**

Create `tests/cli/test_analyze.py`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_backtest.py`、`tests/cli/conftest.py`（Position fixture）

- [ ] **Step 1: 写 Position fixture（测试基础设施）**

Create `tests/cli/conftest.py`：
```python
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`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_research.py`

- [ ] **Step 1: 写失败的测试**

Create `tests/cli/test_research.py`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_plot.py`

- [ ] **Step 1: 写失败的测试**

Create `tests/cli/test_plot.py`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_bench.py`

- [ ] **Step 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`：
```python
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**

```bash
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.py`
- Test: `tests/cli/test_schema.py`

- [ ] **Step 1: 写失败的测试**

Create `tests/cli/test_schema.py`：
```python
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**

```bash
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.py`

- [ ] **Step 1: 写端到端验收测试**

Create `tests/cli/test_acceptance.py`：
```python
"""端到端验收：模拟 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:
```bash
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:
```bash
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**

```bash
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
