Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
- [x] `evidence` CLI 已接入 `evidence_gates`。
- [x] `formal_v2.generate_v2_formal_report()` 已作为正式验收报告入口。
- [ ] 完成外部评审/日期戳后的正式冻结验收流程。
- [x] `formal` CLI 可输出正式验收前 JSON 报告。
- [ ] 输出双因子 v2 的 episode 级正式样本外成本后检验报告文档。
- [ ] 从诊断回测升级为真实合约级回测:换月、保证金、市场冲击、T+1 open/settle。
- [ ] 正式预注册成本后回测门槛与家族检验(已具备模块入口)。
Expand All @@ -69,4 +70,4 @@

当前测试:`PYTHONPATH=src python3 -m unittest discover -s tests -v`

当前通过:100 tests OK。
当前通过:101 tests OK。
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
- `run_position_simulation()`:对一组 episode 批量运行真实主力链持仓估算;
- `batch_equity_summary()`:基于批量模拟生成简单资金曲线摘要;
- `formal_v2.generate_v2_formal_report()`:协议 v2 正式验收前的可审计报告入口;
- `margin_utilization_summary()`:批量持仓保证金占用率统计。
- `margin_utilization_summary()`:批量持仓保证金占用率统计;
- `formal` CLI:输出双因子 v2 正式验收前报告。

## [0.1.0-rc1] - 2026-09-02

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ goratio evidence --period 10y --cost-bps 20 --roll-cost-bps 5 --json

`formal_v2.generate_v2_formal_report()` 可作为协议 v2 正式验收前的可审计报告入口,汇总三期限 status 与总体状态。

```bash
goratio formal --period 10y --cost-bps 20 --roll-cost-bps 5 --json
```

只读 Web 工作台以本地 HTML 导出形式开始落地:

```bash
Expand Down
37 changes: 37 additions & 0 deletions src/goratio/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
)
from .dataset import DataQualityError, prepare_market_data
from .episode_study import run_episode_evidence_bundle
from .formal_v2 import generate_v2_formal_report
from .evidence_gates import run_v2_evidence_bundle
from .episodes import (
build_forward_episodes,
Expand Down Expand Up @@ -165,6 +166,18 @@ def build_parser() -> argparse.ArgumentParser:
help="每笔 episode 附加的换月价差成本(基点)",
)

formal = commands.add_parser("formal", help="输出双因子 v2 正式验收前报告")
formal.add_argument("--period", choices=("3y", "5y", "10y"), default="10y")
formal.add_argument(
"--source",
choices=("cn_public", "yahoo_futures"),
default="cn_public",
)
formal.add_argument("--json", action="store_true", dest="as_json")
formal.add_argument("--timeout", type=float, default=10.0)
formal.add_argument("--cost-bps", type=float, default=20.0)
formal.add_argument("--roll-cost-bps", type=float, default=0.0)

evidence = commands.add_parser("evidence", help="运行双因子 v2 成本后 episode 组合证据门槛")
evidence.add_argument("--period", choices=("3y", "5y", "10y"), default="10y")
evidence.add_argument(
Expand Down Expand Up @@ -704,6 +717,30 @@ def main(
f"in_window={event['in_analysis_window']}\n"
)
return 0
if args.command == "formal":
report = generate_v2_formal_report(
prepared,
cost_bps=args.cost_bps,
roll_cost_bps=args.roll_cost_bps,
)
if args.as_json:
stdout.write(
json.dumps(
report,
ensure_ascii=False,
indent=2,
sort_keys=True,
allow_nan=False,
)
+ "\n"
)
else:
stdout.write(
f"协议:{report['protocol']};总体状态:{report['overall_status']}\n"
)
for horizon, status in report["horizon_status"].items():
stdout.write(f" {horizon} 交易日:{status}\n")
return 0
if args.command == "evidence":
report = run_v2_evidence_bundle(
prepared,
Expand Down
16 changes: 16 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,5 +355,21 @@ def test_contracts_backtest_runs_from_contract_csv(self) -> None:
self.assertEqual(payload["source_id"], "contract_csv")
self.assertIn("不构成投资建议", payload["disclaimer"])

def test_formal_command_returns_v2_report(self) -> None:
with tempfile.TemporaryDirectory() as directory:
output = io.StringIO()
exit_code = main(
["formal", "--source", "cn_public", "--period", "5y", "--json"],
loader=self.make_loader(Path(directory)),
today=lambda: date(2024, 1, 4),
stdout=output,
stderr=io.StringIO(),
)

payload = json.loads(output.getvalue())
self.assertEqual(exit_code, 0)
self.assertEqual(payload["protocol"], "goratio-2a-v1")
self.assertEqual(payload["overall_status"], "insufficient_data")

if __name__ == "__main__":
unittest.main()