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 @@ -57,6 +57,7 @@
- [x] episode 摘要已输出扣减 T+1 open gap 的多头真实换月净收益。
- [x] episode 摘要已输出 open 与 settle 两种 T+1 净收益口径。
- [x] `run_contract_episode_net_backtest()` 已支持 T+1 open/settle 净收益回测摘要。
- [x] `contracts net-backtest` CLI 已接入 T+1 open/settle 净收益回测。
- [ ] 将 T+1 open/settle 执行缺口作为默认成交模型接入主 CLI backtest。
- [x] `stress` 命令可诊断负油价/零价事件是否落入研究窗口并标注 v2 处理要求。
- [ ] 负油价/危机尾部事件进一步接入真实回测压力场景与 protocol v2。
Expand All @@ -82,4 +83,4 @@

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

当前通过:109 tests OK。
当前通过:110 tests OK。
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
- CLI `web serve` 已接入动态 payload_factory,每次请求重新加载本地数据;
- `generate_v2_formal_report()` 增加 `generated_at` 时间戳;
- `contract_episode_return_summary()` 增加 settle 净真实换月收益;
- `run_contract_episode_net_backtest()`:基于 T+1 open/settle 净收益的合约 episode 回测摘要。
- `run_contract_episode_net_backtest()`:基于 T+1 open/settle 净收益的合约 episode 回测摘要;
- `contracts net-backtest` CLI:从合约 CSV 运行 T+1 open/settle 净收益回测。

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

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ date,instrument,symbol,contract_month,close,volume,open_interest,open,settle
goratio contracts inspect --csv /path/to/contracts.csv --json
goratio contracts backtest --csv /path/to/contracts.csv --roll-adjusted --json
goratio contracts portfolio --csv /path/to/contracts.csv --json
goratio contracts net-backtest --csv /path/to/contracts.csv --execution open --json
```

同一份合约 CSV 也可桥接为现有研究管线可消费的 `RawMarketData`,从而用真实主力合约链而不是连续指数进入 `analyze`/`backtest` 流程。另有 `build_roll_adjusted_series()` 可把主力链调整为“换月无虚假跳空”的连续收益序列,供跨换月收益计算使用;其调整后绝对价格不能用于真实金油比水平。桥接函数也支持 `roll_adjusted=True`,直接生成带换月无跳空调整的 RawMarketData。`roll_aware_contract_return()` 则显式模拟换月结算与重开仓,可用于验证真实合约链持有期收益。`contract_episode_return_summary()` 可将一组 episode 的连续序列收益与真实换月收益对比。若合约 CSV 提供 `open`/`settle`,`t1_open_settle_gap()` 可计算信号日收盘到下一交易日 open/settle 的执行缺口;episode 对比摘要也会输出 T+1 open gap 统计,并给出扣减 open gap 后的多头真实换月净收益。
Expand Down
46 changes: 46 additions & 0 deletions src/goratio/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
build_contract_series,
contract_csv_to_raw_market_data,
read_contract_csv,
run_contract_episode_net_backtest,
)
from .margin import summarize_batch_portfolio
from .dataset import DataQualityError, prepare_market_data
Expand Down Expand Up @@ -132,6 +133,13 @@ def build_parser() -> argparse.ArgumentParser:
contracts_portfolio.add_argument("--initial-capital", type=float, default=100000.0)
contracts_portfolio.add_argument("--json", action="store_true", dest="as_json")

contracts_net = contracts_subcommands.add_parser("net-backtest", help="用合约 CSV 运行 T+1 open/settle 净收益 episode 回测")
contracts_net.add_argument("--csv", type=str, required=True)
contracts_net.add_argument("--horizon", type=int, choices=(63,126,252), default=126)
contracts_net.add_argument("--cost-bps", type=float, default=20.0)
contracts_net.add_argument("--execution", choices=("open","settle"), default="open")
contracts_net.add_argument("--json", action="store_true", dest="as_json")

plugin = commands.add_parser("plugin", help="查看静态插件白名单")
plugin_subcommands = plugin.add_subparsers(dest="plugin_command", required=True)
plugin_list = plugin_subcommands.add_parser("list", help="列出白名单插件")
Expand Down Expand Up @@ -454,6 +462,44 @@ def main(
f"最终权益 {_format_number(report['equity']['final_equity'], 2)}\n"
)
return 0
if args.command == "contracts" and args.contracts_command == "net-backtest":
records = read_contract_csv(args.csv)
raw = contract_csv_to_raw_market_data(args.csv)
prepared = prepare_market_data(
Comment on lines +466 to +468
raw,
period="10y",
completed_before=completed_before,
provenance="user_contract_csv",
cache_stale=False,
)
episodes = build_forward_episodes(
prepared.history.points,
prepared.selected.points,
horizon=args.horizon,
)
report = run_contract_episode_net_backtest(
records,
episodes,
cost_bps=args.cost_bps,
execution=args.execution,
)
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"合约 CSV T+1 {args.execution} 净收益回测:episode {report['episode_count']};"
f"成本后均值 {_format_number(report['mean_net_after_cost_return'])}\n"
)
return 0
if args.command == "contracts":
records = read_contract_csv(args.csv)
report = build_contract_series(records)
Expand Down
27 changes: 27 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,5 +397,32 @@ def test_contracts_portfolio_runs_from_contract_csv(self) -> None:
self.assertEqual(exit_code, 0)
self.assertEqual(payload["simulation"]["episode_count"], 0)

def test_contracts_net_backtest_runs_from_contract_csv(self) -> None:
with tempfile.TemporaryDirectory() as directory:
csv_path = Path(directory) / "contracts.csv"
csv_path.write_text(
"date,instrument,symbol,contract_month,close,volume,open_interest\n"
"2024-01-02,gold,GC,2024-02,2000,100,50\n"
"2024-01-03,gold,GC,2024-02,2010,80,40\n"
"2024-01-02,oil,CL,2024-03,75,1000,500\n"
"2024-01-03,oil,CL,2024-03,80,900,400\n",
encoding="utf-8",
)
output = io.StringIO()
exit_code = main(
[
"contracts", "net-backtest", "--csv", str(csv_path),
"--horizon", "63", "--json",
],
today=lambda: date(2024, 1, 4),
stdout=output,
stderr=io.StringIO(),
)

payload = json.loads(output.getvalue())
self.assertEqual(exit_code, 0)
self.assertEqual(payload["execution"], "open")
self.assertEqual(payload["episode_count"], 0)
Comment on lines +411 to +425

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