# wbt v0.9.0
> Release date: 2026-09-09
> Crate tag: `crate-v0.9.0` · Python tag: `v0.9.0`
## Summary
MINOR release (0.x 阶段,含 BREAKING):新增 Rust 持仓风险分析
`calculate_position_risk` 与实例 HTML 报告 API `generate_html_report`;
统一回撤/组合收益口径并将回撤基线纳入初始本金;收紧输入校验;
序列化导出语义明确为 wire format v2(`full` 默认 `False`);
结果快照改为只读冻结结构。自 v0.8.2 起 12 个提交,含 SKZ-731
流水线发版前深度审核(SKZ-732)的 4 项修复(PR #50)。
## BREAKING CHANGES
0.x 阶段按 SemVer 以 MINOR 引入,升级前请逐项核对:
1. **序列化 `full` 默认翻转**:`to_msgpack` / `to_json` / `dump_msgpack` /
`dump_json` 的 `full` 参数默认值从 `True` 改为 `False`——默认导出仅含
基础字段,需要 `verdict` / `verdict_recent` / `drawdowns` / `key_trades` /
`yearly_returns` / `rolling` / `segment_comparison` / `curves_voladj`
审核字段时须显式传 `full=True`。旧用法 `wb.to_json()` → 新用法
`wb.to_json(full=True)`。语义详见 [D04 交换格式](../d04-wire-schema.md)。
2. **结果快照只读冻结**:`BacktestResult` 的快照字段(`stats` / `verdict`
等)嵌套 dict 变为 `MappingProxyType`、list 变为 tuple,
`json.dumps(result.stats)` 会抛 `TypeError`。旧用法
`json.dumps(result.stats)` → 新用法 `json.dumps(result.to_dict())`。
迁移指引见 [D03 快照边界](../d03-snapshots.md)。
3. **输入校验收紧**:非法输入统一抛带列名的 `ValueError`(pandas 缺列原为
`KeyError`);数值字符串与布尔值不再作为数值列接受;非法 `weight_type`
跨入口统一拒绝。
4. **回撤基线纳入初始本金(D01)**:最大回撤的峰值基线包含初始本金,
开局即下跌会计入回撤——`stats` 侧与 `is_good_strategy` 侧口径一致,
与旧版相比开局下跌序列的最大回撤数字会变大。
5. **`wb.symbols` 顺序**统一为排序序(此前为内部计数排序后的遇见序)。
## Added
- `calculate_position_risk`:按时间序的 Rust 持仓风险分析(SKZ-721)。
- `WeightBacktest.generate_html_report(...)`:基于已有实例免重传原始数据
生成自包含 HTML 报告并返回输出路径(SKZ-512)。
- 序列化 wire format v2:envelope 含 `format_version: 2` 与 `full` 标记,
读取端同时兼容 v1(原样返回、不伪造数据),旧读取端会拒绝 v2。
- 指标名集中映射 `wbt.metrics`:机器名(`annual_returns` 等)/ 中文全名 /
历史简称兼容查找,HTML 与 Plotly 表格共用同一映射。
## Fixed
- 组合收益与 alpha、审核(verdict)聚合口径统一(#48)。
- 抵消(和为 0)与恒定日收益序列保留有效绩效指标,不再产出 NaN(#39)。
- 整数权重触发 panic、空标的归类错误(#38,SKZ-723)。
- 非有限权重/价格在公共入口统一拒绝(#38)。
- `is_good_strategy` 回撤峰值基线与 stats 侧口径分裂(SKZ-732 修复,
PR #50):`local_max_drawdown_abs` 峰值基线对齐初始本金口径。
- `digits ≥ 309` 或负值时权重静默变 NaN/全零、绕过输入校验的盲区
(PR #50):`WeightBacktest::new` 入口校验 `digits ∈ 0..=10`。
- 冻结快照下 `plot_verdict` 年度明细表 trace 静默消失(PR #50):守卫
改按结构判断(list/tuple + Mapping)。
- 快照字段 docstring 丢失:8 个快照属性恢复 `help()` / IDE 可见。
- 清理私有相关系数实现并记录 API 成本基线(#47)。
## Compatibility
- 公共 API 只增不删(`__all__` 28 个符号,新增 `calculate_position_risk`);
既有签名除上列 BREAKING 项外不变。
- stats 类输出的中文字段名与顺序硬约束保持不变。
- msgpack/JSON 旧版(v1)文件继续可读。
- pyo3 0.28 / numpy 0.28 / polars 0.53 依赖绑定关系不变;
`requires-python >= 3.10` 不变。
## Tests
- Rust:`cargo test --lib` 253 通过;`cargo fmt` / `cargo clippy
-D warnings` 干净;`cargo publish --dry-run` 通过。
- Python:pytest 1048 通过;ruff format/check、basedpyright 零告警。
- 新增回归:开局下跌计入回撤、`calc_underwater` 逐序列等值、digits
边界校验、冻结快照下 `plot_verdict` 年度表产出、wire v2 交叉读取
(Python 生成 fixture 供 Rust 读取)。
- CI:main `ff64b11` 全矩阵绿(Rust Tests ×3 平台、Rust Lint、Python
Lint、Python Type Check、Python Test ×7)。
## §4 LLM full-repo review
- Scope: `v0.8.2..ff64b11` 全部 12 个提交(59 文件 +9389/-347)逐提交
深读,另对 D01/D03/D04 口径变更做数值推演与交叉复核。
- Model: Claude(SKZ-731 发版流水线第一步,SKZ-732)。
- Conclusion: 4 项代码修复 + 文档同步,已随 PR #50 squash 合入 main
(SKZ-733 独立复现全部门禁后合并);无阻塞性遗留,GO。
- 已知问题(不阻塞,见 SKZ-732 交付评论):
- `_profile_position_risk` 暴露在正式 wheel(测试基建,被 release
wheel 上的 `assert hasattr` 锁定,收紧需连测试一起设计);
- 回测阶段 `InvalidInput` 抛 `PyException` 而非 `ValueError`(与构造
阶段异常映射不一致,涉及面广,建议单独 PR);
- `position_risk` 接受数值字符串 weight(`"0.5"` → 0.5),既有测试
锁定为设计契约(`Decimal` 同样放行),维持现状。